2016-09-16 1 views
2

나는 동시성 문제에 익숙하지 않으므로 나를 맨손으로 생각한다. 스칼라 맵을 보조 저장소로 사용하는 스레드 안전 컨테이너를 만들고 싶습니다. 사용자를 기본 Map에 노출하는 대신 해당 메소드의 하위 집합 만 노출합니다.불변 콜렉션을 래핑하고 스칼라에서 스레드 안전을 유지 하시겠습니까?

class MyContainer[A] { 

    def add(thing: A): Unit = { 
    backingStore = backingStore + (thing.uuid -> thing) 
    } 

    def filter(p: A => Boolean): Option[Iterable[A]] = { 
    val filteredThings = backingStore.values.filter(p) 
    if (filteredThings.isEmpty) None else Some(filteredThings) 
    } 

    def remove(uuid: UUID): Option[A] = backingStore.get(uuid) match { 
    case optionalThing @ Some(thing) => 
     backingStore = backingStore - uuid; optionalThing 
    case None => None 
    } 

    @ volatile private[this] var backingStore = immutable.HashMap.empty[UUID, A] 

} 

... 나는 기본 백업 저장소는 불변이고 그것의 참조 volatile 경우에도 컨테이너가 스레드로부터 안전하지 않습니다 의심 ... 다음과 같은 형태가 될 것이다.

위 컨테이너의 인스턴스에 대한 액세스 권한으로 실행중인 두 개의 개별 스레드가 있다고 가정합니다. Thread 1은 기본 컬렉션을 필터링하고 몇 가지 결과를 얻습니다. 동시에 스레드 2는 항목을 제거합니다. 스레드 1에있는 결과에는 스레드 2가 제거한 항목에 대한 참조가 포함될 수 있습니까? 다른 문제가있을 수 있습니다.

위의 구현이 스레드로부터 안전하지 않다고 수정합니까? 스칼라를 사용하여 위의 스레드 안전성을 만드는 가장 관용적 인 방법은 무엇입니까?

편집 : 가능한 경우 차단 및 동기화를 피하는 것이 좋습니다. 차단/동기화를 사용해야하는 경우 휘발성 참조가 필요합니까? 불변 컬렉션의 핵심은 무엇입니까? 변경 가능한 컬렉션을 사용할 수 없었습니까?

답변

2

당신은 동시 기록 읽기의 문제가 엄격하게 명령되지 않는다는 점에서 당신이 쓰는 기록 방식을 사용하고 있지만 실제로는 문제가되지 않습니다. A가 쓰고있을 때 단순히 타이밍 문제 일뿐입니다 B가 읽는 동안 A가 B의 편집 내용을 볼 것인지에 대한 보증은 없습니다.

C와 D를 동시에 쓰는 것은 실제 문제입니다. 둘 다 같은 시작지도를 읽고, 자신의 복사본을 업데이트 한 다음 자신의 편집 내용 만 쓸 수 있습니다. 누구든지 먼저 쓰는 사람은 변경 사항을 덮어 씁니다.

(A, B)가 포함 된 시작지도와 'C'와 'D'항목을 추가하는 스레드 C와 D를 고려하여 스레드 E 및 F가지도를 읽는 동안; 이 모든 일이 동시에 일어난다. 한가지 가능한 reuslt은 :

C 맵 (A가 B)
D 맵을 판독하고 판독 (A는 B)
C는 맵 (A는 B, C)이
E 맵 (A, B, C를 읽고 쓴다)
D지도 (A, B, D) 글을
F가 (지도를 읽고 A, B, D)

'C'항목이 trnasiently 등장하고 영원히 끊어졌습니다.

신뢰할 수있는 순서로 쓰기를 수행하는 유일한 방법은 쓰기가 동시에 이루어지지 않도록하는 것입니다. locke nforce 동기화 항목을 쓰기 블록으로 사용하거나 단일 Akka 액터를 사용하여 업데이트를 수행하여 직렬화되는지 확인하십시오.

읽기와 쓰기의 순서를 신경 쓰는 경우에도 읽기를 동기화해야합니다. 그러나이 스레드에 액세스하는 스레드가 여러 개인 경우 실제로 걱정할 필요는 없습니다.

+0

@ladams; 하지만 필터와 같은 읽기 메소드의 경우에는 동기화 할 필요가 없습니다 (실제로 읽기 및 쓰기의 순서는 신경 쓰지 않는 한). – davidrpugh

+0

@ladams 만약'add'와'remove' 메소드 만 sychronize했다면, 여전히 @volatile로 백업 저장소에 대한 참조에 주석을 달 필요가 있습니까? – davidrpugh

+0

예. 추가/제거 방법을 모두 동기화해야합니다. 참조를 휘발성으로 표시하면 업데이트 가시성이 적시에 나타납니다.이 작업을하지 않으면 판독기 스레드가 다른 스레드가 업데이트 된 후 일정하지 않은 시간 동안 백업 저장소의 이전 버전에 대한 참조를 유지할 수 있습니다 잠재적으로 여러 번. 다음과 같은 두 가지 작업을 수행해야하는 상황에서 다른 스레드에 대한 가시성을 확보하려면 다음과 같이 두 가지 작업을 수행해야합니다. 작성자가 프로세서 캐시에서 주 메모리로 메모리 쓰기 (동기화 블록 종료시 발생) 힘. – Iadams

0

위의 구현이 스레드로부터 안전하지 않다고 수정합니까?

예.스레드로부터 안전하지 않습니다. 그러나 올바른 기억이 있습니다 visibility semantics.

편의상

당신은에 의해 스레드 안전 만들 수 있습니다 : 내가 추가/sychronize (즉, 쓰기) 방법을 제거해야 제대로 이해하면

class MyContainer[A <: {def uuid: UUID}] { 

    def add(thing: A): Unit = this.synchronized{ 
    backingStore = backingStore + (thing.uuid -> thing) 
    } 

    def filter(p: A => Boolean): Option[Iterable[A]] = this.synchronized{ 
    val filteredThings = backingStore.values.filter(p) 
    if (filteredThings.isEmpty) None else Some(filteredThings) 
    } 

    def remove(uuid: UUID): Option[A] = this.synchronized{ 
    backingStore.get(uuid) match { 
     case optionalThing @ Some(thing) => 
     backingStore = backingStore - uuid; optionalThing 
     case None => None 
    } 
    } 

    import scala.collection.immutable.HashMap 
    private[this] var backingStore = HashMap.empty[UUID, A] 
} 
+0

이 구문의 의미는 무엇입니까? [[A <: {def uuid : UUID}]'? 이걸 본 적이 없어요. – Samar

+0

@Samar 이것은 'uuid'라는 메소드가 포함 된 [구조 유형] (https://twitter.github.io/scala_school/advanced-types.html#structural)이 A의 상한임을 의미합니다. –

+0

계몽 된 :) 감사합니다 Yuval! – Samar

관련 문제