8

저는 Java로 동시 프로그래밍을 배우고 Game of Life에 대한 시뮬레이션을 작성하고 있습니다. 여기 Conway의 국경 세포에서의 경합 게임을위한 다중 스레드 Java 프로그램

내가 생각하고 무엇을 :

  • 사용 INT는 [] [] 세포의 상태를 저장
  • 파티션 t 세그먼트에 INT [] []와 t 작업자 스레드를 사용하는
  • t 스레드는 세그먼트에서 읽고 세그먼트의 모든 셀에 대해 새 값을 계산하고 셀을 업데이트합니다.
  • 계산이 끝나면 다른 근로자가 완료 할 수있는 장벽을 기다립니다.
  • 장벽을 넘으면 기본 스레드가 UI를 업데이트합니다.
  • 근로자는 다음 상태를 계산하기 위해 진행합니다.

이제 세그먼트의 공통 경계에서 경합이 발생합니다. 스레드가 이전 값을 읽기 전에 경계 셀의 상태를 덮어 쓰면 이웃의 계산이 잘못 될 것입니다.

내 옵션에는 어떤 것이 있습니까?

  • runnable 대신 callable을 사용하고 작업자 스레드가 새 값을 반환하도록합니다 (세그먼트 자체를 업데이트하는 대신). 주 스레드는 장벽을 넘은 후 행렬을 업데이트 할 수 있습니다. 이 옵션에는 작업자 스레드가 반환 한 결과를 행렬에 복사하는 작업이 포함됩니다.
  • 두 개의 장벽을 사용하십시오. 작업자 스레드는 인접 셀 세그먼트의 테두리 셀 복사본을 만들고 첫 번째 장벽을 기다립니다. 이 장벽이 통과되면 다음 상태를 계산하고 세그먼트를 제자리에서 업데이트합니다. 그런 다음 그들은 두 번째 장벽에서 기다립니다. 주 스레드가 UI를 업데이트합니다.

내 질문은 복사 된 데이터를 포함하거나 그 위의 두 가지 옵션이 더 효율적입니다하지 않습니다 경계 세포 에서 경쟁 다루는 다른 방법이있다? ReaderWriterLock, 휘발성 변수 또는 다른 동기화 메커니즘을 사용하고 있습니까?

업데이트 : 지금까지 double buffering solution by Peter이 가장 깨끗합니다. 그러나 나는 질문이있다. 두 배열이 공유 데이터이고 동기화 (동기화 된 액세스 또는 휘발성 변수)를 사용하지 않으므로 가시성 문제가 발생하지 않습니까?? 여러 CPU가 배열 값을 캐시하고 각 반복마다 배열의 일부만 업데이트 할 수 있습니까? 그런 다음 스레드는 테두리 셀에 오래된 값을 가져옵니다. 이것이 가능한가? 그렇지 않다면, 왜. 그렇다면 어떻게 해결할 수 있습니까? declaring two arrays volatile will not make their individual elements volatile입니다.

+0

일반적인 int 대신 AtomicInt를 사용하려고합니다. –

+0

장점? 과도한 동기화가되지 않나요? – Helen

+0

int를 사용해야하는 이유는 무엇입니까? boolean을 사용하여 저장하는 것이 더 논리적이고 효율적이지 않습니까? – Pool

답변

1

실제 질문에 답변하지 않지만 내 권장 사항은 작업자 스레드에서 업데이트하는 대신 새 값을 반환하는 것입니다. 나는 한걸음 더 나아가서 "aggregator"가 작업자 스레드의 결과를 새로운 보드 상태 배열로 결합시킨 다음 첫 번째 것을 버릴 것입니다. 내 추론은 논리의 전반적인 가독성을 향상시킬 것입니다. 걱정할 필요가있는 "글로벌 상호 작용"이 거의 없기 때문입니다.

내가 말하고자하는 것은, 강한 이유가있을 때를 제외하고 기능적 방식으로 프로그램하는 것을 선호하기 때문에 약간 편견이 있습니다.

+0

+1 보너스로 : 경합 없음! :-D –

1

나는 다음과 같은 접근 시도 할 것입니다 :

  • 는 근로자가 계산을 수행 가지고,하지만 단지 내부 셀에 값을 다시 작성합니다.
  • 테두리 셀의 경우 결과를 저장하십시오.
  • 계산이 완료되면 장벽에서 기다리십시오.
  • 모든 근로자가 첫 번째 장벽에 도달하면 그 때를 놓고 각자가 경계 세포를 쓸 수있게하십시오. n x m 타일 필요한 UI 업데이트

저장소 2 * (n + m - 1) 동안 제 장벽에서

  • 기다림 때문에 일반적으로 더 큰 타일 (8 × 8 이상) 캐시 값 비례 적은 메모리를 필요로한다.

  • 5

    2 개의 int [] [] 배열을 사용하는 것이 좋습니다. 그것들을 A와 B라고 부르 자. A는 모든 홀수의 "진드기"의 값을 보유하고 B는 짝수의 진드기를 보유 할 것이다.

    초기 상태가 어떻게 되든지간에 A를 초기화하십시오. 그런 다음 스레드를 풀어 각 셀의 다음 상태를 계산하고 결과를 B의 해당 셀에 놓습니다. 모든 스레드가 완료되면 새 상태가 B가됩니다. 이제 B를 사용하여 다음 상태를 계산하십시오 각 셀의 결과를 A에 저장합니다. 언제든지 하나의 배열은 읽기 전용이고 다른 배열은 쓰기 전용이므로 아무런 경쟁이 없습니다.

    장점 :

    • 당신이 지금하는 일에 비해 데이터 없음 복사. 셀당 하나의 쓰기 만 발생합니다.
    • 알고리즘이 간단하기 때문에 가장자리/구석의 경우를 걱정할 필요가 없습니다.
    • 진행중인 메모리 할당이 없습니다. 시작시 두 배열을 할당하십시오.

    단점 :

    • 당신은 두 배의 메모리를 할당해야합니다.
    +1

    +1 더블 버퍼링의 경우 –

    +0

    이것은 좋은 것처럼 보입니다. 하지만 공유 데이터의 가시성에 대한 질문이 있습니다. 업데이트를 참조하십시오. – Helen

    0

    방금 ​​java.util.concurrent.Exchanger<V>을 우연히 발견했습니다. 교환 지점 역할을합니다. 인접한 스레드 사이에서 셀 상태 목록을 교환하는 데 사용할 수 있습니다. 그것은 인접한 근로자들 사이에서만 동기화해야하기 때문에 장벽보다 낫습니다.

    0

    이중 버퍼링으로 캐싱 문제에 관한 업데이트에 답변하려면 문제가되지 않습니다. CPU에는 일관된 캐시가 있으며 다른 CPU 캐시에서 데이터가 변경된시기를 알고 있습니다.

    +0

    확인. 그러나 가시성은 여전히 ​​Java 메모리 모델로 인해 문제가됩니다.업데이트의 링크가 보여 주듯이 배열 참조를 volatile로 선언하고 참조를 덮어 쓰게됩니다. 이것은 또한 CPU가 값을 캐시 할 수 없거나 휘발성 참조가 수정되면 버릴 수 있음을 의미합니다. – Helen

    +0

    그 링크를 잘 읽으려면 어레이 참조가 아니라 휘발성이되도록 배열 참조 만 필요합니다. 당신은 FrontBuffer와 BackBuffer 레퍼런스를 가질 수 있고 각각의 반복을 교환 할 수 있습니다. 이 방법으로 버퍼의 내용은 여전히 ​​CPU 캐시를 통과 할 수 있습니다. 항목은 휘발성이 아니므로 CPU 레지스터를 잘 활용할 수 있으며 이전 데이터 작업에 문제가 없습니다. –

    +0

    실제로, 휘발성 배열 참조를 해결하면 CPU 레지스터에 유지하는 대신 메모리에서 메모리 주소를 찾아 사용하는 속도가 느려질 수 있습니다. 아마도 각 반복의 시작 부분에서 일시적인 비 휘발성 참조로 휘발성 참조를 복사 할 수 있습니다. 어쨌든 반복 중에 변경하지 않기 때문입니다. –