2011-04-14 4 views
8

다른 스레드에서 두 가지 방법으로 작동해야하는 컨테이너 (C++)가 있습니다. 1) 요소를 추가 및 제거하고 2) 해당 멤버를 반복합니다. 분명히, 반복이 일어난 동안 요소를 제거 = 재앙.스레드 안전 모드에서 컨테이너를 반복하는 방법은 무엇입니까?

class A 
{ 
public: 
    ... 
    void AddItem(const T& item, int index) { /*Put item into my_stuff at index*/ } 
    void RemoveItem(const T& item) { /*Take item out of m_stuff*/ } 
    const list<T>& MyStuff() { return my_stuff; } //*Hate* this, but see class C 
private: 
    Mutex mutex; //Goes in the *Item methods, but is largely worthless in MyStuff() 
    list<T> my_stuff; //Just as well a vector or deque 
}; 
extern A a; //defined in the .cpp file 

class B 
{ 
    ... 
    void SomeFunction() { ... a.RemoveItem(item); } 
}; 

class C 
{ 
    ... 
    void IterateOverStuff() 
    { 
     const list<T>& my_stuff(a.MyStuff()); 
     for (list<T>::const_iterator it=my_stuff.begin(); it!=my_stuff.end(); ++it) 
     { 
      ... 
     } 
    } 
}; 

다시 B::SomeFunction()C::IterateOverStuff()가 비동기 적으로 호출 얻고있다 : 코드는 다음과 같이 보입니다. 반복 도중 my_stuff이 추가 또는 제거 작업에서 '보호'되었는지 확인하기 위해 사용할 수있는 데이터 구조는 무엇입니까?

답변

13

같은 소리는 reader/writer lock입니다. 기본적으로 하나 이상의 독자가 또는 명의 작가가있을 수 있습니다. 동시에 읽기 및 쓰기 잠금을 가질 수 없습니다.

편집 : 디자인에 맞는 사용법의 예는 약간의 변경이 필요합니다. 목록을 소유하고있는 클래스에 "iterate"함수를 추가하고 템플릿으로 만들면 함수/펑터를 전달하여 각 노드에 대해 수행 할 작업을 정의 할 수 있습니다. 이런 식으로 뭔가 (신속하고 더러운 의사 코드,하지만 당신은 요점을 파악 ...) :

class A { 
public: 
    ... 
    void AddItem(const T& item, int index) { 
     rwlock.lock_write(); 
     // add the item 
     rwlock.unlock_write(); 
    } 

    void RemoveItem(const T& item) { 
     rwlock.lock_write(); 
     // remove the item 
     rwlock.unlock_write(); 
    } 

    template <class P> 
    void iterate_list(P pred) { 
     rwlock.lock_read(); 
     std::for_each(my_stuff.begin(), my_stuff.end(), pred); 
     rwlock.unlock_read(); 
    } 

private: 
    rwlock_t rwlock; 
    list<T> my_stuff; //Just as well a vector or deque 
}; 


extern A a; //defined in the .cpp file 

class B { 
    ... 
    void SomeFunction() { ... a.RemoveItem(item); } 
}; 

class C { 
    ... 

    void read_node(const T &element) { ... } 

    void IterateOverStuff() { 
     a.iterate_list(boost::bind(&C::read_node, this)); 
    } 
}; 

또 다른 옵션 리더/라이터 잠금 공개적으로 액세스 할 수 있도록하고 올바르게 사용에 대한 책임 호출을하는 것입니다 자물쇠. 그러나 그것은 오류가 발생하기 쉽습니다.

+5

+1. Boost에는 다음과 같은 구현이 있습니다. http://www.boost.org/doc/libs/1_46_1/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.shared_mutex –

+0

@Evan, @larsmans 꽤 이해할 것입니다. 어디에 넣을까요? 전역으로 정의하고 C :: Iterate ...와 A 메소드를 넣으시겠습니까? 읽기 또는 쓰기가 수행되는지 여부를 어떻게 알 수 있습니까? –

+0

@Matt : 목록 자체와 동일한 범위/소유권을 가져야합니다. 목록이 전역이면 잠금은 전역이어야합니다. 어떤 객체가 목록을 소유하고 있다면, 그 같은 객체는 자물쇠를 소유해야한다. –

2

목록을 반환하면 해당 생성자/소멸자에서 뮤텍스를 잠 그거나 잠금 해제하는 클래스로 묶인 목록을 반환하십시오. 뮤텍스는 재귀하지 않도록

class LockedIterable { 
public: 
    LockedIterable(const list<T> &l, Mutex &mutex) : list_(l), mutex_(mutex) 
    {lock(mutex);} 
    LockedIterable(const LockedIterable &other) : list_(other.list_), mutex_(other.mutex_) { 
    // may be tricky - may be wrap mutex_/list_ in a separate structure and manage it via shared_ptr? 
    } 
    ~LockedIterable(){unlock(mutex);} 
    list<T>::iterator begin(){return list_.begin();} 
    list<T>::iterator end(){return list_.end();} 
private: 
    list<T> &list_; 
    Mutex &mutex_; 
}; 

class A { 
    ... 
    LockedIterable MyStuff() { return LockedIterable(my_stuff, mutex); } 
}; 

까다로운 부분의 라인을 따라 뭔가 복사 생성자를 쓰고있다. 또는 auto_ptr을 사용하십시오.

오, 여기에 뮤텍스보다 리더/라이터 잠금 장치가 더 나은 솔루션입니다.

+0

감사합니다. 나는이 라인을 따라 뭔가를 생각해 보았습니다. 유일한 것은 반복을 완료 한 후에 LockedIterable을 파기해야한다는 것입니다. 반복이 완료된 후에 C :: IterateOverStuff에서 더 많은 일이 발생하면 범위를 벗어나는 것을 기다리는 것만으로는 충분하지 않습니다 ... 그래서 새로운/삭제가 여기있는 옵션일까요? 마지막으로 LockedIterable에 데이터 멤버가 없으므로 복사본 구조가 간단하지 않습니까? –

+0

차라리 추가 블록 {LockedIterable l = ...;() {...}}을 사용하고 싶습니다. 이것은 당신을위한 범위를 관리합니다. 데이터 멤버가 없다는 것에 관해서는 - 코드를 지나치게 간소화했습니다 - 데이터 멤버 list_ 및 mutex_가 있습니다. mutex 잠금/잠금 해제를 유지하고 싶지 않거나 비 재귀 뮤텍스를 두 번 잠금하지 않으려는 경우 복사가 중요하지 않습니다. – Arkadiy

3

IMHO 데이터 구조 클래스에 개인 뮤텍스가 있고 클래스 메서드를 작성하여 메서드를 호출하는 코드가 무엇을하든 관계없이 모든 것이 스레드로부터 안전하도록하는 것은 실수입니다. 이것을 완전하고 완벽하게 수행하는 데 필요한 복잡성은 맨 위에 있습니다.

더 간단한 방법은 데이터에 액세스해야 할 때 호출 코드가 잠금을 담당하는 공용 (또는 전역) 뮤텍스를 만드는 것입니다.

Here이 주제에 관한 내 블로그 기사입니다.

+0

감사합니다. 점은 재현성과 어려움을 겪었습니다. –