2017-02-20 1 views
2

큰 배열 (3e9 요소)의 데이터가 있고 여러 스레드에서 해당 값을 업데이트 중입니다. 방금 경쟁 조건이 있다는 것을 알았습니다.데이터의 일부를 잠금으로 사용할 수 있습니까?

요소가 서로 독립적이므로 전체 기능을 잠글 필요가 없다고 생각하면 과 data[234]의 업데이트를 동시에 안전하게 수행 할 수 있습니다.

data[]에있는 각 요소의 최상위 비트가 절대로 사용되지 않을 것입니다. 그 비트에 GCC 기본 내장 잠금을 구현하는 것이 안전합니까?

내 코드는 다음과 같지만 교착 상태가 발생하는 것으로 보입니다.

const unsigned short LOCK_MASK = 1<<15; 
unsigned short * lock = &(data[position]); 
unsigned short oldLock, newLock; 

//lock 
do { 
    oldLock = *lock; 
    newLock = oldLock^LOCK_MASK; 
} while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock)); 

//update data[position] here 
... 
... 
... 

//unlock 
*lock ^= LOCK_MASK; 

나는 또한이 게시물 (Lightweight spinlocks built from GCC atomic operations?)를 읽고 내 디자인에 내 data

편집volatile을 추가 한, 0 해제 의미하며, 1 수단은 귀하의 코드 번호를 포함

+0

잠금을 획득하기 전에 모든 읽기 작업에 동기화가 필요합니다. 특히,'oldLock = * lock; '은 잘못되었습니다. 원자 적이어야합니다. 그대로,'(oldLock & LOCK_MASK)'가 한 번 true 일 경우 옵티마이 저는'* lock'이 변경되지 않는다고 가정합니다. –

+1

좀 더 보수적 인 접근법은 적절한 수의 실제 뮤텍스 (또는 스핀 록 또는 사용하려는 메커니즘)를 할당하고이를 사용하여 데이터에 대한 액세스를 동기화하는 것입니다. 예 : M 개의 뮤텍스 배열을 가지고 있다면 데이터 배열에 값 #i를 읽거나 쓰기 전에 각 스레드가 뮤텍스 # (i % M)을 잠그도록하십시오. 뮤텍스에 대한 경합이 여전히 받아 들일 정도로 희귀하다는 점에서 가장 작은 값을 찾을 때까지 M의 값을 조정하십시오. –

답변

1

잠겨 oldLock = *lock을 포함하고 *lock ^= LOCK_MASK, 비트의 잠금을 해제하면 릴리스 장벽이 없어서 다른 코어와 업데이트를 동기화하지 못합니다.

쓰기 액세스를 위해 배열 세그먼트를 잠그는 것 외에도 읽기 및 쓰기가 동기화되어야하므로 읽기 액세스를 위해 해당 세그먼트를 잠글 필요가 있다는 점에 유의하십시오.

GCC 기본 내장 잠금을 구현하는 것이 안전합니까? 당신이 (잠금 해제, 읽기 잠겨 X의 N은을 고정 쓰기) 읽기에 대해 별도의 상태를 표현하고 쓰기 액세스를하려면

여러 비트가 필요합니다.

const unsigned short LOCK_MASK = 1<<15; 

void lock_array_segment(int position) 
{ 
    unsigned short *lock = &data[position]; // global array 
    unsigned short oldLock, newLock; 

    do { 
     oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED); 
     newLock = oldLock | LOCK_MASK; // set bit 

    } while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock)); 
} 


void unlock_array_segment(int position) 
{ 
    unsigned short *lock = &data[position]; // global array 
    unsigned short oldLock, newLock; 

    oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED); 
    newLock = oldLock & ~LOCK_MASK; // clear bit 

    __atomic_store_n (lock, newLock, __ATOMIC_RELEASE); 
} 

__sync_bool_compare_and_swap에 대한 문서는 대부분의 경우 말한다, 이러한 내장 명령은 다음과 같습니다 : 2 개 상태로 잠금 단일 비트 제한, 당신의 코드를 기반으로 구현 될 수을 잠금 해제 잠금 전체 장벽으로 간주. 여기에 인수 장벽이 필요하므로 덮어야합니다.

접근 방식이 스핀 록을 기반으로하기 때문에 오랫동안 읽기 잠금을 유지하려는 경우 제대로 작동하지 않습니다. 이 경우 잠금이 필요한 데이터 배열의 각 세그먼트에 대해 별도의 뮤텍스를 사용하는보다 직접적인 방법을 고려하십시오. 여러 독자에게 액세스 권한을 부여하려면 std::shared_mutex (C++ 17) 또는 boost::shared_mutex을 사용하는 것이 좋습니다.

+0

전에는 __atomic_ * 지침을 알지 못했습니다! 감사 – bbvan

0

보다 표준적인 잠금 방법을 고려해야합니다 (C++11 또는 그 이상).

아마 적어도 Pthread tutorial (여기에 설명 된 개념)을 읽어보십시오.

C++에서 atomic operationsthread support을 읽으십시오.

연속적인 1024 (또는 몇 가지 다른 2의 제곱) 요소의 각 세그먼트마다 뮤텍스가 있다고 생각할 수 있습니다.

producer-consumer 접근 방식을 고려해 볼 수 있습니다.

관련 문제