멀티 스레드 엔진의 스레드 간 메시지 전달을 덜 성가 시게하려면 어떻게해야합니까?


18

현재 작업중 인 C ++ 엔진은 생성 (프로시 저럴 컨텐츠 생성을위한), 게임 플레이 (AI, 스크립트, 시뮬레이션), 물리학 및 렌더링과 같은 여러 개의 큰 스레드로 나뉩니다.

스레드는 스레드에서 스레드로 전달되는 작은 메시지 오브젝트를 통해 서로 통신합니다. 스테핑하기 전에 스레드는 모든 수신 메시지 (변환, 오브젝트 추가 및 제거 등)에 대한 업데이트를 처리합니다. 때때로 하나의 스레드 (Generation)가 무언가 (Art)를 작성하고 영구적으로 소유하기 위해 다른 스레드 (Rendering)로 전달합니다.

프로세스 초기에 몇 가지 사항을 발견했습니다.

  1. 메시징 시스템이 번거 롭습니다. 새로운 메시지 타입을 생성한다는 것은 기본 Message 클래스를 서브 클래 싱하고, 해당 타입을위한 새로운 열거 형을 생성하고, 스레드가 새로운 타입의 메시지를 해석하는 방법에 대한 논리를 작성하는 것을 의미합니다. 개발 속도가 빠르며 오타 스타일 오류가 발생하기 쉽습니다. (Sidenote-이 작업을 수행하면 동적 언어가 얼마나 훌륭한 지 알 수 있습니다!)

    더 좋은 방법이 있습니까? 이것을 자동으로 만들기 위해 boost :: bind와 같은 것을 사용해야합니까? 내가 그렇게하면, 말을 할 수있는 능력을 잃어 버리거나 종류 나 그 밖의 것에 따라 메시지를 분류하는 것이 걱정됩니다. 그런 종류의 관리가 필요한지 확실하지 않습니다.

  2. 첫 번째 요점은 이러한 스레드가 많은 통신을하기 때문에 중요합니다. 메시지 작성 및 전달은 작업을 수행하는 데있어 큰 부분입니다. 그 시스템을 간소화하고 싶지만 다른 유용한 패러다임에도 열려 있습니다. 이것을 쉽게하기 위해 고려해야 할 다른 멀티 스레드 디자인이 있습니까?

    예를 들어, 자주 쓰지 않지만 여러 스레드에서 자주 읽는 리소스가 있습니다. 모든 스레드가 액세스 할 수 있도록 뮤텍스로 보호되는 공유 데이터를 가질 수 있다는 생각에 개방적이어야합니까?

멀티 스레딩을 염두에두고 처음부터 무언가를 디자인 한 것은 이번이 처음입니다. 이 초기 단계에서 실제로 그것이 실제로 잘 진행되고 있다고 생각하지만, 확장과 새로운 작업을 구현할 때의 내 효율성에 대해 걱정하고 있습니다.


6
여기에 하나의 직접적인 질문이 없으므로이 게시물은이 사이트의 Q & A 스타일에 적합하지 않습니다. 게시물을 질문별로 하나씩 별도의 게시물로 분리하고 모호한 팁이나 조언 대신 실제로 발생하는 특정 문제에 대해 질문을 다시 집중하도록 권장합니다.

2
좀 더 일반적인 대화를 원한다면 gamedev.net 포럼에서이 게시물을 사용해 보는 것이 좋습니다 . Josh가 말했듯이 "질문"은 하나의 특정 질문이 아니기 때문에 StackExchange 형식으로 수용하기가 매우 어렵습니다.
Cypher

의견을 보내 주셔서 감사합니다! 더 많은 지식을 가진 사람이 한 번에 여러 문제를 해결할 수있는 단일 리소스 / 경험 / 패러다임을 가질 수 있기를 바랐습니다. 하나의 큰 아이디어가 내 다양한 ​​문제를 내가 놓친 것으로 한 가지로 합성 할 수 있다는 느낌을 받고 있으며, 내가 알 수있는 것보다 더 많은 경험을 가진 사람을 생각하고있었습니다. !
랩터 미트

"팁"유형의 질문은 해결할 특정 문제가 없음을 의미하기 때문에 제목을 메시지 전달에보다 구체적으로 변경했습니다. 요즘에는 "실제 질문이 아닙니다"라고 말하고 싶습니다.
Tetrad

물리와 게임 플레이를 위해 별도의 스레드가 필요합니까? 그 둘은 서로 얽혀있는 것 같습니다. 또한 각각의 의사 소통 방법과 누구와 의사 소통을하는 방법을 알기 란 어렵습니다.
Nicol Bolas

답변:


10

더 넓은 문제에 대해서는 스레드 간 통신을 최대한 줄일 수있는 방법을 찾는 것이 좋습니다. 가능하면 동기화 문제를 완전히 피하는 것이 좋습니다. 이는 데이터를 이중 버퍼링하여 단일 업데이트 대기 시간을 도입하지만 공유 데이터 작업을 크게 완화함으로써 달성 할 수 있습니다.

옆으로, 서브 시스템에 의한 스레딩이 아니라 스레드 스폰 또는 스레드 풀 을 사용하여 작업별로 분기하는 것을 고려 했습니까? ( 스레드 풀링에 대해서는 특정 문제와 관련 하여이 내용을 참조하십시오 .) 이 짧은 논문 은 풀 패턴의 목적과 사용법을 간결하게 설명합니다. 유익한 답변을 참조하십시오또한. 언급했듯이 스레드 풀은 확장 성을 보너스로 향상시킵니다. 그리고 새로운 게임이나 엔진을 작성할 때마다 서브 시스템 기반 스레드가 제대로 작동하도록하는 것과 달리 "한 번만 쓰고 어디서나 사용"입니다. 견고한 타사 스레드 풀링 솔루션도 많이 있습니다. 스레드 생성 및 스레드 삭제의 오버 헤드를 완화해야하는 경우 스레드 생성으로 시작하여 나중에 스레드 풀로 작업하는 것이 더 간단합니다.


1
특정 스레드 풀링 라이브러리에 대한 권장 사항이 있습니까?
imre

응답 해 주셔서 대단히 감사합니다. 첫 번째 요점에 관해서는-나는 그것이 좋은 생각이고 아마도 내가 들어갈 방향이라고 생각합니다. 현재 더블 버퍼링해야 할 것이 무엇인지 아직 모릅니다. 시간이 지남에 따라 굳어 지므로이 점을 명심하겠습니다. 당신의 두 번째 요점-제안에 감사드립니다! 예, 스레드 작업의 이점은 분명합니다. 나는 당신의 링크를 읽고 그것에 대해 생각합니다. 그것이 나를 위해 작동하는지 / 그것이 나를 위해 작동하게하는 방법을 100 % 확신하지는 않지만 확실히 진지하게 생각할 것입니다. 감사!
랩터 미트

1
@imre는 Boost 라이브러리를 확인하십시오-그들은 미래를 가지고 있습니다.
조나단 디킨슨

5

다른 멀티 스레드 디자인에 대해 물었습니다. 내 친구가이 방법에 대해 말했는데 꽤 멋지다고 생각했습니다.

아이디어는 모든 게임 엔터티의 사본이 2 개가 있다는 것입니다 (폐기 스럽습니다). 하나는 현재 사본이고 다른 하나는 과거 사본입니다. 현재 사본은 쓰기 전용 이며 과거 사본은 읽기 전용 입니다. 업데이트 할 때 엔티티 목록의 범위를 원하는만큼 많은 스레드에 할당합니다. 각 스레드는 지정된 범위의 현재 사본에 대한 쓰기 액세스 권한을 가지며 모든 스레드는 엔티티의 모든 과거 사본에 대한 읽기 액세스 권한을 가지므로 잠금없이 과거 사본의 데이터를 사용하여 지정된 현재 사본을 업데이트 할 수 있습니다. 각 프레임 사이에서 현재 사본이 과거 사본이되지만 역할 교환을 처리하려고합니다.


4

우리는 C #에서만 동일한 문제를 겪었습니다. 새로운 메시지를 작성하는 것이 쉬움 (또는 부족함)에 대해 오랫동안 열심히 생각한 후에는 코드 생성기를 만드는 것이 최선이었습니다. 메시지 내용에 대한 설명 만 주어지면 메시지 클래스, 열거 형, 자리 표시 자 처리 코드 등을 생성합니다.이 코드는 거의 매번 거의 동일하며 실제로 오타가 발생하기 쉽습니다.

나는 이것에 완전히 만족하지는 않지만 모든 코드를 직접 작성하는 것보다 낫습니다.

공유 데이터와 관련하여 가장 좋은 대답은 물론 "의존"입니다. 그러나 일반적으로 일부 데이터를 자주 읽고 많은 스레드가 필요로하는 경우 공유 할 가치가 있습니다. 스레드 안전을 위해, 당신의 최선의 방법은 만드는 불변 하지만이 질문을 밖으로 있다면, 뮤텍스는 할 수 있습니다. C #에는ReaderWriterLockSlim 이러한 경우를 위해 특별히 설계된 클래스가 있습니다. C ++에 해당하는 것이 확실합니다.

아마도 첫 번째 문제를 해결할 수있는 스레드 통신에 대한 또 다른 아이디어 는 메시지 대신 핸들러 를 전달하는 것입니다. C ++ 에서이 작업을 수행하는 방법을 잘 모르겠지만 C #에서는 delegate객체를 다른 스레드로 보내고 (메시지 대기열에 추가) 수신 스레드에서 실제로이 대리자를 호출 할 수 있습니다. 이로써 "임시"메시지를 즉석에서 생성 할 수 있습니다. 나는이 아이디어를 가지고 놀았고 실제로는 실제로 시도하지 않았으므로 실제로 나쁜 것으로 판명 될 수 있습니다.


모든 훌륭한 정보에 감사드립니다! 핸들러에 대한 마지막 비트는 바인딩 또는 펑터를 사용하여 함수를 전달하는 것에 대해 언급 한 것과 비슷합니다. 나는 아이디어와 비슷합니다-그것을 시도하고 그것이 짜증나거나 멋진 지 볼 수 있습니다 : D CallDelegateMessage 클래스를 만들고 내 발가락을 물에 담그는 것으로 시작할 수 있습니다.
랩터 미트

1

저는 일부 스레드 게임 코드의 디자인 단계에만 있으므로 실제 경험이 아닌 내 생각 만 공유 할 수 있습니다. 그 말로, 나는 다음 줄을 따라 생각하고 있습니다.

  • 대부분의 게임 데이터는 읽기 전용 액세스를 위해 공유되어야합니다 .
  • 일종의 메시징을 사용하여 데이터 쓰기가 가능합니다.
  • 다른 스레드가 데이터를 읽는 동안 데이터 업데이트를 피하기 위해 게임 루프에는 읽기와 업데이트라는 두 가지 단계가 있습니다.
  • 읽기 단계에서 :
  • 모든 공유 데이터는 모든 스레드에 대해 읽기 전용입니다.
  • 스레드는 스레드 로컬 스토리지를 사용하여 항목을 계산하고 기본적으로 큐에 배치 된 명령 / 메시지 개체 인 업데이트 요청을 생성하여 나중에 적용 할 수 있습니다.
  • 업데이트 단계에서 :
  • 모든 공유 데이터는 쓰기 전용입니다. 데이터는 알 수없는 / 불안정한 상태로 가정됩니다.
  • 여기서 업데이트 요청 개체가 처리됩니다.

이론 상으로는 (확실하지는 않지만) 읽기 및 업데이트 단계에서 최소한의 동기화로 여러 스레드가 동시에 실행될 수 있다고 생각합니다. 읽기 단계에서는 공유 데이터를 쓰는 사람이 없으므로 동시성 문제가 발생하지 않습니다. 업데이트 단계는 더 까다 롭습니다. 동일한 데이터 조각 에 대한 병렬 업데이트 는 문제가 될 수 있으므로 동기화가 순서대로 이루어집니다. 그러나 다른 데이터 세트에서 작동하는 한 임의의 수의 업데이트 스레드를 계속 실행할 수 있습니다.

결국,이 접근법은 스레드 풀링 시스템에 적합하다고 생각합니다. 문제가되는 부분은 다음과 같습니다.

  • 업데이트 스레드 동기화 (여러 스레드가 동일한 데이터 세트를 업데이트하지 않도록하십시오).
  • 읽기 단계에서 스레드가 실수로 공유 데이터를 쓸 수 없도록하십시오. 프로그래밍 실수의 여지가 너무 많을 까봐 두려운 데 디버그 도구가 얼마나 많은 양을 쉽게 잡을 수 있는지 잘 모르겠습니다.
  • 즉시 읽을 수있는 중간 결과에 의존 할 수없는 방식으로 코드 작성 즉, x += 2; if (x > 5) ...x가 공유되면 쓸 수 없습니다 . x의 로컬 복사본을 만들거나 업데이트 요청을 생성하고 다음 실행시 조건부 만 수행하면됩니다. 후자는 상용구 코드를 보존하는 많은 추가 스레드 로컬 상태를 의미합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.