2017-12-21 25 views
2

스레드 (gcc, Linux, x86)간에 데이터 구조체를 공유하고 싶습니다. 의 내가 스레드 A에 다음 코드 있다고 가정 해 봅시다 :x86에서 gcc로 메모리 주문을 시행하는 방법

shared_struct->a = 1; 
shared_struct->b = 1; 
shared_struct->enable = true; 

스레드 B가 enable 플래그 그 구조체 먼저 확인하는주기적인 작업입니다.

필자는 컴파일러가 스레드 A의 쓰기를 재정렬 할 수 있으므로 스레드 B가 일관성없는 데이터를 볼 수 있다고 생각합니다. ARM의 메모리 장벽에 익숙하지만 x86에서 쓰기 순서를 어떻게 보장합니까?volatile보다 나은 방법이 있습니까?

구조체에 일관성있는 상태를 설정하고 모든 내용을 메모리로 플러시하고 끝에 플래그를 설정하고 싶습니다.

+5

* 전혀 변하지 않았습니다. 휘발성이 있습니다. 원자 플래그가 있으면 C11' ' –

+0

을 사용해야합니다. 휘발성 액세스가 재정렬되지 않는 "부산물"입니다. – filo

+0

@AnttiHaapala 원자 접근은 명령 순서 재 지정과 무슨 관련이 있습니까? – Lundin

답변

3

당신은 정말 (당신은 P 스레드를 언급하기 때문에) 뮤텍스을 사용하므로 shared_struct 내부 pthread_mutex_lock mtx; 필드를 추가해야 다른에서 다음 이와 비슷하게

pthread_mutex_lock(&shared_struct->mtx); 
shared_struct->a = 1; 
shared_struct->b = 1; 
shared_struct->enable = true; 
pthread_mutex_unlock(&shared_struct->mtx); 

과 (pthread_mutex_init로 초기화하는 것을 잊지 마세요) 공유 데이터에 액세스하는 코드

또한 atomic operations을 볼 수도 있습니다 (하지만 위의 그림과 같이 mutex을 사용하는 것이 좋습니다).

일부는 pthread tutorial입니다.

race conditionsundefined behavior을 피하십시오. 나는 당신이 스레드 라이브러리 구현하지 않는은, (그리고 그것의 일부를 다음 어셈블러로 코딩되어야하며 futex(7) 사용) 그렇게하지 않는

주문 쓰기를 보장 어떻게

예를 들어 nptl(7) 구현이 pthreads(7)이고 GNU glibc (또는 musl-libc)입니다. 뮤텍스를 사용해야하며 스레드 라이브러리를 구현하는 데 시간을 낭비하고 싶지 않으므로 기존 라이브러리를 사용하십시오. (당신은, P 스레드 뮤텍스 구현 방법 등을 이해하기 호기심 경우) 당신이 그들의 소스 코드를 공부할 수 있도록 (glibc는 & musl-libc에 포함) 리눅스에서 대부분의 C 표준 라이브러리, free software 것을

알 수 있습니다.

컴파일러는 대부분하지 않은 쓰기

(확실히뿐만 아니라) 컴파일러,하지만 하드웨어 순서를 변경할 수 있습니다. cache coherence에 대해 읽어보십시오. 또한 OS가 포함될 수도 있습니다 (futex(2) 가끔 pthread mutex 루틴에 의해 호출 됨).

+0

고맙습니다 - 명백한 뮤텍스를 잊어 버렸습니다. – filo

+1

부수적으로, '휘발성'_은 하드웨어 재주문을 방지합니다. 하드웨어 나 컴파일러 모두 C 스펙에서 벗어날 수 없습니다. 휘발성 변수가 프로그래머가 지정한대로 순서가 지정되지 않도록 하드웨어가 명령을 재주문하는 시스템이 규격을 따르지 않습니다. 하드웨어 나 컴파일러 어느 쪽도 C 프로그램이 C 프로그램의 "추상 기계"의 요구에 따라 실행되도록 보장 할 수 없다면 C로 작성된 프로그램은 해당 시스템에서 사용할 수 없습니다. 프로그래머가 메모리 장벽을 처리해야하는 부담을 감수하더라도 시스템이 적합하지는 않습니다. – Lundin

+0

'pthread_rwlock '도 여기에 적합하며, IMO는 유스 케이스에 더 잘 맞으며 다중 판독기 스레드가 서로 간섭하지 않도록합니다. –

2

enable = true 만 설정할 필요가있는 경우 릴리스/획득 주문 번호 stdatomic.h을 입력하면 정확하게 원하는 내용을 얻을 수 있습니다. (x86에서 asm, normal stores/loads는 release/acquire 의미론을 가지고 있기 때문에 컴파일 시간 재 배열을 막는 것만으로도 충분합니다. 그러나 올바른 방법은 가 아니라 atomic입니다.)

그러나 독자가 수정하는 동안 enable = false을 다시 "잠금"하도록 설정하려면보다 복잡한 업데이트 패턴이 필요합니다. atomics로 수동으로 뮤텍스를 다시 만들거나 (나쁜 생각, 표준 라이브러리 뮤텍스를 사용하는 것), 또는 라이터가 업데이트 중간에 없을 때 여러 독자가 대기하지 않는 읽기 전용 액세스를 허용하는 작업을 수행하십시오.

어느 RCU a 또는 seqlock 여기 좋은 수 있습니다.

seqlock의 경우 enable = true/false 플래그 대신 일련 번호가 있습니다. 독자는 다른 구성원을 읽은 후 순서 번호를 확인한 후 "찢어진"쓰기를 감지 할 수 있습니다. (그러나 모든 구성원은 적어도 mo_relaxed을 사용하여 atomic이어야하며, 그렇지 않으면 값을 무시한 경우에도 데이터를 C에서 읽지 않고 정의되지 않은 동작을합니다. 또한 카운터를 검사하는로드에 대해 충분한 순서가 필요합니다. 아마 첫 번째 인수를 획득 한 다음 shared_struct->b로드에서 획득하여 시퀀스 번호의 두 번째로드가 그 다음에 정렬되도록하십시오. (acquire은 단방향 장벽 일뿐입니다. 느슨한로드 후에 획득로드로 인해 당신이 필요로하는 것입니다.)

RCU는 독자가 항상 완전히 대기 상태가되지 않도록하며, 현재 유효한 구조체에 대한 포인터를 참조 해제합니다. 포인터를 원자 적으로 바꾸는 것만 큼 간단합니다. 오래된 구조체를 재활용하면 복잡해질 수 있습니다. 모든 리더 스레드가 mem 블록을 읽었는지 확인해야합니다. 당신이 그것을 재사용하기 전에 ory.


은 단순히 작가를 수정하는 동안 다른 회원들에 대해 일관성없는/부분적으로 업데이트 된 값을보고 다음 enable == true을 보는 독자를 중지하지 않는 다른 구조체 멤버를 변경하고 이전 enable = false 설정. 그렇게 할 필요는 없지만 다른 스레드가 액세스 할 수 있도록 새 오브젝트를 릴리스하는 경우에만 설명하는 시퀀스는 atomic_store_explicit(&foo->enable, true, memory_order_release)으로 문제가 없습니다.

+0

'memory_order_release' 스토어는 리더의 enable 플래그의'memory_order_acquire'로드와 쌍을 이루어야합니다, 맞습니까? – caf

+0

@caf : 예. 작업 (및 seq_cst) 작업을 "동기화 - 함께"릴리스 (및 seq_cst) 작업을 얻습니다. http://preshing.com/20120913/acquire-and-release-semantics/에는 이것이 실제 기계에서 어떻게 작동하는지에 대한 좋은 세부 정보가 있으며 주문과 관련해서는 무엇을 제공하지 않습니다. –