생산자-소비자

Producer–consumer problem

컴퓨팅에서 생산자-소비자 문제(Bounded-Consumer 문제라고도 함)는 1965년 이후 Edsger W. Dijkstra에 의해 기술된 일련의 문제를 말합니다.상품 생산, 물류 또는 공급망 관리에서도 문제가 알려져 있습니다.중간 스토리지는 생산 프로세스에서 포지셔닝할 수 있습니다.상품의 단기 수용으로 그들은 두 생산소 사이의 완충제 역할을 한다.중간 스토리지는 무제한 용량(무제한 버퍼)을 가질 수도 있고 제한된 용량(제한 버퍼)을 가질 수도 있습니다.중간 저장소가 가득 차면 업스트림 생산 스테이션은 생산을 중지해야 합니다.중간 저장소가 비어 있으면 다운스트림 프로덕션 스테이션은 아무 작업도 수행하지 않습니다.

Dijkstra는 Electrogica X1 및 X8 컴퓨터의 컨설턴트로 일하면서 생산자-소비자 문제에 대한 해결책을 찾았습니다.「생산자-소비자의 최초 용도는 소프트웨어, 하드웨어였습니다.상점과 주변기기 사이의 정보 전송을 담당하는 구성 요소를 '채널'이라고 불렀습니다.동기는 현재 생산자/소비자 배치로 알려진2개의 카운트 세마포에 의해 제어되었습니다.한쪽 세마포는 큐의 길이를 나타내는 세마포는 CPU에 의해 증가되고 다른 한쪽 세마포는 채널에 의해 감소(P)되어 채널과 d에 의해 증가되었습니다.두 번째 세마포어가 양이면 대응하는 인터럽트 플래그가 발생합니다.]"[1]

Dijkstra는 무제한 버퍼 케이스에 대해 다음과 같이 기술했습니다. "우리는 '생산자'와 '소비자'라고 불리는 두 가지 프로세스를 각각 고려합니다.생산자는 순환 과정이며, 생산자가 순환 과정을 거칠 때마다 소비자가 처리해야 하는 정보의 특정 부분을 생산합니다.소비자는 또한 순환 과정이며, 사이클을 거칠 때마다 생산자에 의해 생산된 정보의 다음 부분을 처리할 수 있습니다.이 목적을 위해 두 프로세스가 무제한 [2]용량을 가진 버퍼를 통해 연결된다고 가정합니다."

그는 경계 버퍼 사례에 대해 다음과 같이 썼다. "우리는 무한 용량을 가진 버퍼를 통해 결합된 생산자와 소비자를 연구했다.그 관계는 대칭이 된다. 만약 두 가지가 유한한 크기의 버퍼를 통해 결합된다면, 예를 들어 N개의 부분"[3]

또, 복수의 생산자-소비자 케이스에 대해서: 「생산자/소비자 쌍은, n개의 부분을 포함한i 정보 스트림을 개입시켜 결합하는 것을i 검토한다.우리는 …을 상정하고 있다.모든 스트림의 모든 부분을 포함해야 하는 유한 버퍼가 '합계'[4] 부분의 용량을 갖습니다.

브린치 한센과 니클라우스 워스는 곧 세마포어의 문제를 알게 되었다: "세마포어와 관련하여 같은 결론에 도달했다. 즉, 세마포어가 상위 레벨의 언어에 적합하지 않다는 것이다.대신 자연스러운 동기화 이벤트는 메시지 교환입니다."[5]

Dijkstra의 경계 버퍼

원래의 세마포 경계 버퍼 솔루션은 ALGOL 스타일로 작성되었습니다.버퍼에는 N개의 부분 또는 요소를 저장할 수 있습니다."큐잉 부분의 수" 세마포어는 버퍼 내의 채워진 위치를 카운트하고, "빈 위치의 수" 세마포어는 버퍼 내의 빈 위치를 카운트하며, 세마포어의 "버퍼 조작"은 버퍼 입력 및 취득 조작의 뮤텍스로 기능합니다.버퍼가 가득 찬 경우, 즉 빈 위치 수가 0이면 생산자 스레드는 P(빈 위치 수) 동작으로 대기합니다.버퍼가 비어 있는 경우, 즉 큐잉 부분의 수가 0이면 소비자 스레드는 P(큐잉 부분의 수) 동작으로 대기합니다.V() 작업은 세마포어를 해방합니다.그 결과 스레드는 대기 큐에서 대기 큐로 이동할 수 있습니다.P() 연산은 세마포 값을 0으로 낮춥니다.V() 연산은 세마포 [6]값을 증가시킵니다.

시작한다. 정수 번호  큐잉 부분, 번호   포지션,       완충 장치 조작;       번호  큐잉 부분:= 0;       번호   포지션:= N;       완충 장치 조작:= 1;       파진       제작자: 시작한다.               다시. 1: 생산하다 다음 분. 부분;                        P(번호   포지션);                        P(완충 장치 조작);                        더하다 부분 로. 완충 장치;                        V(완충 장치 조작);                        V(번호  큐잉 부분); 에 가다 다시. 1 끝.;       소비자: 시작한다.               다시. 2: P(번호  큐잉 부분);                        P(완충 장치 조작);                        가지고 가다 부분 부터 완충 장치;                        V(완충 장치 조작) ;                        V(번호   포지션);                        과정 부분 찍은; 에 가다 다시. 2 끝.       팬렌드 끝. 

C++ 20에서 세마포어는 언어의 일부입니다.Dijkstra의 솔루션은 최신 C++로 쉽게 작성할 수 있습니다.변수 buffer_manipulation은 뮤텍스입니다.한 스레드에서 취득한 후 다른 스레드에서 해제하는 세마포 기능은 필요하지 않습니다.lock()과 unlock() 쌍이 아닌 lock_guard() 문은 C++RAII입니다.lock_guard destructor는 예외 발생 시 잠금 해제를 보장합니다.이 솔루션은 여러 소비자 스레드 및/또는 여러 생산자 스레드를 처리할 수 있습니다.

#실패하다 <blocks> #실패하다 <138x> #실패하다 <세마포>  표준::카운트_세마포< >N> number_of_inclosing_inclosing{0}; 표준::카운트_세마포< >N> number_of_empty_filename{N}; 표준::뮤텍스 buffer_조절;  무효 제작자() {   위해서(;;) {     부분 부분 = produce_next_discripts();     number_of_empty_filename.얻다();     {       표준::lock_guard< >표준::뮤텍스> g(buffer_조절);       add_module_to_module(부분);     }     number_of_inclosing_inclosing.풀어주다();   } }  무효 소비자() {   위해서(;;) {     number_of_inclosing_inclosing.얻다();     부분 부분;     {       표준::lock_guard< >표준::뮤텍스> g(buffer_조절);       부분 = take_from_from_from_from_filename();     }     number_of_empty_filename.풀어주다();     프로세스_실행_취득된(부분);   } }  인트 주된() {   표준:: t1(제작자);   표준:: t2(소비자);   t1.합류하다();   t2.합류하다(); } 

모니터 사용

Brinch Hansen은 모니터를 다음과 같이 정의했습니다.공유 변수와 해당 변수에 대한 의미 있는 연산 집합을 나타내기 위해 모니터라는 용어를 사용합니다.모니터의 목적은 특정 [7]정책에 따라 개별 프로세스 간의 리소스 예약을 제어하는 것입니다.토니 호어는 [8]모니터의 이론적 토대를 마련했다.

제한적인 완충 장치: 모니터   시작한다. 완충 장치:배열 0..N-1  부분;     머리, 꼬리: 0..N-1;     세어보세요: 0..N;     비어 있지 않다, 가득 차지 않다: 조건.;   절차. 추가하다(x: 부분);     시작한다. 한다면 세어보세요 = N 그리고나서 가득 차지 않다.잠깐만요.;       메모 0 <=> 세어보세요 < > N;       완충 장치[꼬리] := x;       꼬리 := 꼬리 (+) 1;       세어보세요 := 세어보세요 + 1;       비어 있지 않다.신호.     끝. 추가하다;   절차. 제거한다.(결과 x: 부분) ;     시작한다. 한다면 세어보세요 = 0 그리고나서 비어 있지 않다.잠깐만요.;       메모 0 < > 세어보세요 <=> N;       x := 완충 장치[머리];       머리 := 머리 (+) 1;       세어보세요 := 세어보세요 - 1;       가득 차지 않다.신호.     끝. 제거한다.;   머리 := 0; 꼬리 := 0; 세어보세요 := 0; 끝. 제한적인 완충 장치; 

모니터는 변수를 포함하는 개체입니다.buffer,head,tail그리고.count순환 버퍼를 실현하기 위한 조건 변수nonempty그리고.nonfull동기화 및 메서드append그리고.remove에 액세스 합니다.모니터 동작 대기는 세마포 동작 P 또는 획득에 해당하고 신호는 V 또는 해제에 해당합니다.동그라미로 둘러싸인 연산(+)은 모듈로 N을 취합니다.제시된 Pascal 스타일의 유사 코드는 Hoare 모니터를 나타냅니다.Mesa 모니터는while count대신if count. 프로그래밍 언어 C++ 버전은 다음과 같습니다.

학급 Bounded_buffer {   부분 완충 장치[N];    // 0 . N - 1   서명되어 있지 않다 머리, 꼬리;  // 0 . N - 1   서명되어 있지 않다 세어보세요;       // 0..N   표준::condition_displays(조건) 비어 있지 않다, 가득 차지 않다;   표준::뮤텍스 mtx; 일반의:   무효 추가하다(부분 x) {     표준::unique_lock< >표준::뮤텍스> lock(mtx);     가득 차지 않다.잠깐만요.(lock, [&]{ 돌아가다 !(N == 세어보세요); });     주장하다(0 <=> 세어보세요 & & 세어보세요 < > N);     완충 장치[꼬리++] = x;     꼬리 %= N;     ++세어보세요;     비어 있지 않다.통지_1();   }   부분 제거한다.() {     표준::unique_lock< >표준::뮤텍스> lock(mtx);     비어 있지 않다.잠깐만요.(lock, [&]{ 돌아가다 !(0 == 세어보세요); });     주장하다(0 < > 세어보세요 & & 세어보세요 <=> N);     부분 x = 완충 장치[머리++];     머리 %= N;      --세어보세요;     가득 차지 않다.통지_1();     돌아가다 x;   }   Bounded_buffer() {     머리 = 0; 꼬리 = 0; 세어보세요 = 0;   } }; 

C++ 버전에서는 기술적인 이유로 추가 뮤텍스가 필요합니다.assert를 사용하여 버퍼 추가 및 삭제 조작의 전제 조건을 적용합니다.

채널 사용

Electrogica 컴퓨터의 최초의 생산자-소비자 솔루션은 '채널'을 사용했다.정의된 채널:송신원 및 수신처의 명시적인 명명 대신에, 통신이 행해지는 포토에 이름을 붙이는 것이 있습니다.포트 이름은 프로세스에 대해 로컬이며 포트 쌍을 채널로 연결하는 방법은 병렬 [9]명령어 선두에서 선언할 수 있습니다.브린치 한센은 조이스와 슈퍼 파스칼 프로그래밍 언어로 채널을 구현했습니다.Plan 9 운영체제 프로그래밍 언어인 Alef와 Inferno 운영체제 프로그래밍 언어인 Limbo에는 채널이 있습니다.다음 C 소스 코드는 User Space에서 Plan 9로 컴파일됩니다.

#실패하다 "u.h." #실패하다 libc.h #실패하다 "그럴 수도 있어요.h"  열거하다 { 스택 = 8192 };  무효 제작자(무효 *v) {   채널. * = v;   위해서(설치하다 i = 1; ; ++i) {     수면.(400);     인쇄물("p %d\n", i);     송신하다(, i);   } } 무효 소비자(무효 *v) {   채널. * = v;   위해서 (;;) {     설치하다 p = 인식하다();     인쇄물("\t\tc %d\n", p);     수면.(200 + 랜덤(600));   } } 무효 스레드메인(인트 argc,  **argv) {   인트 (*마크)(무효 (*fn)(무효*), 무효 *arg, 설치하다 스택);   마크 = 스레드 작성;   채널. * = 작성하다(크기(하지 않다), 1);   마크(제작자, , 스택);   마크(소비자, , 스택);   인식하다(작성하다(크기(무효*), 0));   스레덱시톨(0); } 

프로그램 진입점이 작동 중입니다.threadmain. 함수 호출ch = chancreate(sizeof(ulong), 1)채널 생성, 함수 호출sendul(ch, i)채널 및 함수 호출에 값을 전송합니다.p = recvul(ch)는 채널로부터 값을 수신합니다.Go라는 프로그래밍 언어에도 채널이 있습니다.Go의 예:

패키지 주된  수입품 (  "fmt"  "산술/랜덤"  "시간" )  변화하다 메시지 보내기 = 0  기능하다 product Message() 인트 {  시간을.수면.(400 * 시간을.밀리초)  메시지 보내기++  fmt.프린트("sendMsg = %v\n", 메시지 보내기)  돌아가다 메시지 보내기 } 기능하다 소비 메시지(수신 메시지 인트) {  fmt.프린트("\t\trecvMsg = %v\n", 수신 메시지)  시간을.수면.(시간을.지속(200+랜드.내부(600)) * 시간을.밀리초) } 기능하다 주된() {   := 만들다(찬스 인트, 3)  가세요 기능하다() {   위해서 {     <-> product Message()   }  }()  위해서 수신 메시지 := 범위  {   소비 메시지(수신 메시지)  } } 

바둑 생산자-소비자 솔루션은 소비자를 위한 주요 바둑 루틴을 사용하고 생산자를 위해 이름 없는 새로운 바둑 루틴을 만듭니다.두 개의 바둑 루틴은 채널 ch로 연결됩니다.이 채널은 최대 3개의 int 값을 큐잉할 수 있습니다.스테이트먼트ch := make(chan int, 3)채널을 만듭니다.ch <- produceMessage()채널과 스테이트먼트에 값을 송신합니다.recvMsg := range ch[10]채널로부터 값을 수신합니다.메모리 자원 할당, 처리 자원 할당 및 자원 동기화는 프로그래밍 언어에 의해 자동으로 수행됩니다.

세마포 또는 모니터 없음

Leslie Lamport는 한 생산자와 한 소비자를 위한 제한된 버퍼 생산자-소비자 솔루션을 문서화했습니다.버퍼에는 최대 b 메시지 b > = 1을 저장할 수 있다고 가정합니다.이 솔루션에서는 k를 b보다 큰 상수로 하고 s와 r을 0과 k-1 사이의 값으로 가정한 정수 변수로 합니다.처음에는 s=r이고 버퍼는 비어 있다고 가정합니다.k를 b의 배수로 선택함으로써 버퍼를 배열 B[0: b - 1]로 구현할 수 있습니다.프로듀서는 각각의 새로운 메시지를 B[s mod b]에 넣기만 하면 소비자는 B[r mod b][11]에서 각 메시지를 받습니다.

프로듀서:   L:  한다면 (s - r) 모드 k = b 그리고나서 에 가다 L fi;       놓다 메세지  완충 장치;       s := (s + 1) 모드 k;       에 가다 L; 컨슈머:   L:  한다면 (s - r) 모드 k = 0 그리고나서 에 가다 L fi;       가지고 가다 메세지 부터 완충 장치;       r := (r + 1) 모드 k;       에 가다 L; 

Lamport 솔루션에서는 스케줄러에서 대기하는 대신 스레드에서 비지 대기하는 것이 사용됩니다.이 솔루션은 불편할 때 스케줄러 스레드스위치의 영향을 무시합니다.첫 번째 스레드가 메모리로부터 변수 값을 읽고 스케줄러가 변수 값을 변경하는 두 번째 스레드로 전환되고 첫 번째 스레드가 첫 번째 스레드로 전환되면 첫 번째 스레드는 현재 값이 아닌 변수의 이전 값을 사용합니다.Atomic read-modify-write는 이 문제를 해결합니다.최신 C++ 서비스atomic다중 채널 프로그래밍을 위한 변수 및 연산.다음 비지웨이팅 C++11 솔루션은 1개의 생산자와 1개의 소비자를 위해 원자성 읽기-수정-쓰기 작업을 사용합니다.fetch_add그리고.fetch_sub원자변수로count.

열거하다 {N = 4 }; 메세지 완충 장치[N]; 표준::원자핵의< >서명되어 있지 않다> 세어보세요 {0}; 무효 제작자() {   서명되어 있지 않다 꼬리 {0};   위해서(;;) {     메세지 메세지 = product Message();     하는 동안에 (N == 세어보세요)        ; // 대기 중     완충 장치[꼬리++] = 메세지;     꼬리 %= N;     세어보세요.fetch_add(1, 표준::memory_order_memory);   } } 무효 소비자() {   서명되어 있지 않다 머리 {0};   위해서(;;) {     하는 동안에 (0 == 세어보세요)        ; // 대기 중     메세지 메세지 = 완충 장치[머리++];     머리 %= N;     세어보세요.fetch_sub(1, 표준::memory_order_memory);     소비 메시지(메세지);   } } 인트 주된() {   표준:: t1(제작자);   표준:: t2(소비자);   t1.합류하다();   t2.합류하다(); } 

순환 버퍼 인덱스 변수head그리고.tail는 스레드 로컬이므로 메모리의 일관성과는 관계가 없습니다.변수count생산자와 소비자 스레드의 비지 대기 시간을 제어합니다.

우편함 문제

생산자-소비자 문제가 발생한 지 40년이 지난 후 Aguilera, Gafni 및 Lamport는 버퍼가 비어 있는지 또는 [12]꽉 찼는지를 판단하면서 프로세스가 고정 범위 카운터(즉, 버퍼 크기와 무관한 범위)에만 액세스하도록 문제를 해결할 수 있음을 보여 주었습니다.이 효율성 척도의 목적은 FIFO 채널을 통해 상호작용하는 프로세서와 디바이스 간의 상호작용을 가속화하는 것입니다.이들은 최대값 카운터를 읽어 버퍼에 액세스하는 것이 안전한지 여부를 판단하는 솔루션을 제안했다.단, 이 솔루션에서는 무한히 증가하는 무제한 카운터가 채용되고 있습니다.단, 이들 카운터는 앞에서 설명한 체크 단계에서는 액세스되지 않습니다.

나중에, 아브라함과 암람은[13] 논의된 고정 범위 속성을 소유하는 의사 코드로 아래에 제시된 더 단순한 해결책을 제안했다.이 솔루션에서는 최대값 N의 카운터를 사용합니다.단, 버퍼가 빈지 가득한지 여부를 판단하기 위해 프로세스는 유한 범위의 싱글라이터 레지스터에만 액세스합니다.각 프로세스는 12개의 값의 단일 라이터를 소유합니다.제작자 프로세스에서는Flag_p, 및 사용자 프로세스에서 쓰기Flag_c둘 다 3필드 어레이입니다. Flag_p[2]그리고.Flag_c[2]는 "full", "empty" 또는 "safe"를 저장할 수 있습니다.이는 버퍼가 꽉 찼는지, 비어 있는지, 또는 꽉 찼지도 비어 있지도 않은지를 나타냅니다.

알고리즘의 배후에 있는 아이디어는 다음과 같습니다.프로세스는 레지스터를 통해 전달되고 제거된 모듈 N+1의 항목 수를 카운트합니다.CountDelivered그리고.CountRemoved. 프로세스가 항목을 전달하거나 제거할 때 이러한 카운터를 비교하여 버퍼 상태를 성공적으로 판단하고 이 데이터를 저장합니다.Flag_p[2]또는Flag_c[2]체크 단계에서 실행 프로세스는 다음과 같습니다.Flag_p그리고.Flag_c, 및 다음 중 어떤 값을 추정하려고 합니다.Flag_p[2]그리고.Flag_c[2]는 버퍼의 현재 상태를 반영합니다.두 가지 동기화 기술이 이 목표를 달성하는 데 도움이 됩니다.

  1. 아이템을 전달한 후 프로듀서는 다음 주소로 편지를 씁니다.Flag_p[0]그것이 읽어낸 가치Flag_c[0]아이템을 삭제한 후 사용자는 에 글을 남깁니다.Flag_c[1]값:1-Flag_p[0]따라서 조건은Flag_p[0] == Flag_c[0]제작자가 최근에 버퍼 상태를 확인한 반면,Flag_p[0] != Flag_c[0]는 그 반대입니다.
  2. 전달(제거) 작업은 다음에 쓰기로 종료됩니다.Flag_p[1](Flag_c[1])에 격납되어 있습니다.Flag_p[0](Flag_c[0])따라서 조건Flag_p[0] == Flag_p[1]생산자가 마지막 배송 작업을 마쳤음을 나타냅니다.마찬가지로 조건Flag_c[0] == Flag_c[1]는 소비자의 마지막 제거가 이미 종료되었음을 나타냅니다.

따라서 검사 단계에서 생산자가 다음을 발견하면Flag_c[0] != Flag_p[0] & Flag_c[0] == Flag_c[1]의 가치에 따라 동작합니다.Flag_c[2]및 그 이외의 경우 에 저장되어 있는 값에 따라 달라집니다.Flag_p[2]마찬가지로, 소비자가 그것을 발견했을 경우Flag_p[0] == Flag_c[0] & Flag_p[0] == Flag_p[1]의 가치에 따라 동작합니다.Flag_p[2]및 그 이외의 경우 에 저장되어 있는 값에 따라 달라집니다.Flag_c[2]아래 코드에서 대문자로 표시된 변수는 공유 레지스터를 나타내며, 두 프로세스 중 하나에 의해 작성되고 두 프로세스 모두에 의해 읽힙니다.비대문자 변수는 프로세스가 공유 레지스터에서 읽은 값을 복사하는 로컬 변수입니다.

count 전달 완료 = 0; 삭제한 수 = 0; 플래그_p[0] = 0; 플래그_p[1] = 0; 플래그_p[2] = "비었다"; 플래그_c[0] = 0; 플래그_c[1] = 0; 플래그_c[2] = "비었다";  절차. 제작자()  {     하는 동안에 (진실의) {         아이템 = product Item(프로덕트 아이템)();                  /* 확인 단계: 버퍼가 가득 찰 때까지 비지 대기 */                 따라하다 {             flag_c = 플래그_c;            한다면 (flag_c[0] != 플래그_p[0] & flag_c[0] == flag_c[1]) 응답하다 = flag_c[2];            또 다른 응답하다 = 플래그_p[2];         } 까지 (응답하다 != "풀")               /* 아이템 배송 단계 */          putItemIntoBuffer(아이템);          배달 횟수 = count 전달 완료+1 % N+1;          flag_c = 플래그_c;          플래그_p[0] = flag_c[0];          제거된 = 삭제수;          한다면 (배달된 수  제거된 == N) { 플래그_p[1] = flag_c[0]; 플래그_p[2] = "풀"; }          한다면 (배달된 수  제거된 == 0) { 플래그_p[1] = flag_c[0]; 플래그_p[2] = "비었다"; }          한다면 (0 < > 배달된 수  제거된 < > N) { 플래그_p[1] = flag_c[0]; 플래그_p[2] = "안전"; }      } }  절차. 소비자()  {     하는 동안에 (진실의) {         /* 확인 단계: 버퍼가 비어 있지 않을 때까지 비지 대기 */                 따라하다 {             flag_p = 플래그_p;            한다면 (flag_p[0] == 플래그_c[0] & flag_p[1] == flag_p[0]) 응답하다 = flag_p[2]);            또 다른 응답하다 = 플래그_c[2];          } 까지 (응답하다 != "비었다")               /* 아이템 삭제 단계 */          아이템 = removeItemFromBuffer();          삭제한 수 = 삭제한 수+1 % N+1;          flag_p = 플래그_p;          플래그_c[0] = 1-flag_p[0];          배달했다 = 배달된 수;          한다면 (배달했다  삭제수 == N) { 플래그_c[1] = 1-flag_p[0]; 플래그_c[2] = "풀"; }          한다면 (배달했다  삭제수 == 0) { 플래그_c[1] = 1-flag_p[0]; 플래그_c[2] = "비었다"; }          한다면 (0 < > 배달했다  삭제수 < > N) { 플래그_c[1] = 1-flag_p[0]; 플래그_c[2] ="안전"; }      } } 

코드의 정확성은 프로세스가 단일 원자 동작으로 어레이 전체를 읽거나 어레이의 여러 필드에 쓸 수 있다는 가정에 달려 있습니다.이 가정은 현실적이지 않기 때문에 실제로는 다음과 같이 대체해야 한다.Flag_p그리고.Flag_c(log(12)비트) 정수를 사용하여 이러한 배열의 값을 인코딩합니다. Flag_p그리고.Flag_c는, 코드의 판독성을 위해서만 어레이로서 표시됩니다.

「 」를 참조해 주세요.

레퍼런스

  1. ^ Dijkstra; 2000; EWD1303 운영체제 설계의 추억
  2. ^ Dijkstra; 1965; EWD123 순차 프로세스 협력, 섹션 4.1.일반 세마포어의 일반적인 용도.
  3. ^ Dijkstra; 1965; EWD123 순차 프로세스 협력, 섹션 4.3.경계 버퍼입니다.
  4. ^ Dijkstra; 1972; 유한 버퍼를 공유하는EWD329 정보 스트림
  5. ^ Worth; 1969; Niklaus Worth, 1969년 7월 14일 Brinch Hansen; 2004; 프로그래머 이야기, 4장 서두른 청년
  6. ^ Dijkstra; 1965; EWD123 순차 프로세스 협력, 섹션 4.3.경계 버퍼입니다.
  7. ^ Brinch Hansen; 1973; 운영체제 원리, 3.4.7.이벤트 큐
  8. ^ C.A.R. Hoare; 1974; 모니터:운영체제 구조화 개념, 4. 예: 경계 버퍼
  9. ^ Hoare; 1978; 순차 프로세스 통신, 7.3 포트 이름
  10. ^ Go, Channels 투어
  11. ^ Lamport, Leslie; 1977; 다중 프로세스 프로그램의 정확성 입증, 생산자/소비자 사례
  12. ^ 아길레라, 마르코스 K, 일라이 가프니, 레슬리 램포트."우편함 문제"분산 컴퓨팅 23.2 (2010): 113-134.
  13. ^ 아브라함, 우리, 아므람, 갈. "두 프로세스 동기화"이론 컴퓨터 사이언스 688 (2017): 2-23.

추가 정보