(연구 노트 - 프로세스 관리) 다중 스레드 충돌을 해결하는 방법

공유 리소스의 경우 잠금이 없으면 멀티 스레드 환경에서 뒤집힐 가능성이 매우 높습니다.


경쟁과 협력

단일 코어 CPU 시스템에서 여러 프로그램이 동시에 실행되는 착각을 실현하기 위해 운영 체제는 일반적으로 타임 슬라이스 스케줄링을 사용하여 각 프로세스가 한 번에 타임 슬라이스를 실행할 수 있도록 합니다. up, 다음 프로세스가 실행으로 전환되며, 이 타임 슬라이스의 시간이 매우 짧기 때문에 현상이동시성

 또한 운영 체제는 또한 각 프로세스에 대해 거대한 개인 가상 메모리의 환상을 만듭니다.이 주소 공간의 추상화는 각 프로그램이 자체 메모리를 갖는 것처럼 보이지만 실제로는 운영 체제가 비밀리에 여러 주소 공간을 물리적으로 다중화합니다. 메모리 또는 디스크.

프로그램에 실행 흐름이 하나만 있는 경우 단일 스레드라는 의미이기도 합니다. 물론 프로그램은 여러 개의 실행 프로세스를 가질 수 있으며, 이를 소위 멀티 스레드 프로그램이라고 합니다.스레드는 스케줄링의 기본 단위이고 프로세스는 자원 할당의 기본 단위입니다.

따라서 스레드는 코드 세그먼트, 힙 공간, 데이터 세그먼트, 열린 파일 및 기타 리소스와 같은 프로세스 리소스를 공유할 수 있지만 각 스레드에는 자체 독립 스택 공간이 있습니다.

 그런 다음 문제가 발생합니다. 여러 스레드가 공유 리소스를 놓고 경쟁하는 경우 효과적인 조치를 취하지 않으면 공유 데이터의 혼란이 발생합니다.

실험: 두 개의 스레드를 만들고 다음 코드와 같이 각각 공유 변수 i 를 1 씩 증가시키는 10,000실행합니다 .

 논리적으로 말하면 i 변수는 end 에서 20000 이어야 하지만 실제 결과는 다음과 같습니다.

 두 번 실행하면 i 값이 20000 또는 다른 결과가 될 수 있음을 알 수 있습니다.

각 실행은 오류를 생성할 뿐만 아니라 다른 결과를 제공합니다. 컴퓨터에서는 용납할 수 없는 작은 확률의 오류이지만 반드시 작은 확률로 일어날 것입니다.

왜 이런 일이 발생합니까?

왜 이런 일이 발생하는지 이해하려면 카운터 i 변수를 업데이트하기 위해 컴파일러에서 생성하는 코드 시퀀스, 즉 어셈블리 명령어가 실행되는 순서를 이해할 필요가 있습니다.

이 예제에서는 숫자 1을 i에 더하기만 하므로 해당 어셈블리 명령의 실행 프로세스는 다음과 같습니다.

단순히 숫자 1 을 추가하면 CPU가 실행 중일 때 실제로 명령이 실행된다는  것을 알 수 있습니다 . i  3 

스레드 1이 이 코드 영역에 들어와서 메모리에서 레지스터로 i의 값(이때 50이라고 가정)을 로드한 다음 레지스터에 1을 더하고 이때 레지스터의 i 값은 51이라고 가정합니다. 시간.

이제 불행한 일이 발생합니다. 클럭 인터럽트가 발생합니다 . 따라서 운영 체제는 현재 실행 중인 스레드의 상태를 스레드의 스레드 제어 블록 TCB에 저장합니다.

이제 더 나쁜 일이 발생합니다. 스레드 2가 실행되도록 예약되고 동일한 코드를 입력합니다. 또한 첫 번째 명령을 실행하여 메모리에서 i의 값을 가져와서 레지스터에 넣습니다. 이때 메모리의 i 값은 여전히 ​​50이므로 스레드 2의 레지스터의 i 값도 50입니다. 스레드 2가 다음 두 명령어를 실행한다고 가정하면 레지스터의 i 값은 +1이고 레지스터의 i 값은 메모리에 저장되므로 이때 전역 변수 i의 값은 51이다.

마지막으로 또 다른 컨텍스트 전환이 발생하고 스레드 1이 실행을 재개합니다. 두 개의 어셈블리 명령을 실행했으며 이제 다음 명령을 실행할 준비가 되었음을 기억하십시오. 이전에 스레드 1 레지스터의 i 값은 51이었으므로 마지막 명령을 실행하여 메모리에 값을 저장한 후 전역 변수 i의 값은 다시 51로 설정됩니다.

간단히 말해서 i(값 50)를 증가시키는 코드는 두 번 실행되며, 논리적으로 최종 i 값은 52가 되어야 하지만 제어할 수 없는 스케줄링 으로 인해 최종 i 값은 51입니다.

2 위의 스레드 1과 스레드 2의 실행 프로세스의 경우 다음과 같이 표현할 수 있습니다.

 상호 배타적

위에 표시된 상황을 경쟁 조건 이라고 합니다. 여러 스레드가 공유 변수를 조작하기 위해 서로 경쟁하는 경우 운이 좋지 않아 실행 중에 컨텍스트 전환이 발생하면 잘못된 결과를 얻습니다. 사실 모든 실행은 다른 결과를 얻을 수 있습니다. , 그래서 출력 결과에 불확실성이 있습니다 .

공유 변수를 작동시키는 이 코드의 멀티 스레드 실행은 경합 상태로 이어질 수 있으므로 이 코드를 임계 섹션이라고 합니다. 이 코드는 공유 리소스에 액세스하는 코드 조각이며 동시에 여러 스레드에서 실행되어서는 안 됩니다 .

우리는 이 코드가 상호 배타적이기를 바랍니다 . 즉, 쓰레드가 크리티컬 섹션에서 실행될 때 다른 쓰레드는 크리티컬 섹션에 들어가는 것을 방지해야 합니다 . 암호.                                                                 

 또한 상호 배제는 멀티스레딩에만 적용되는 것은 아닙니다. 여러 스레드가 공유 리소스를 놓고 경쟁할 때 상호 배제를 사용하여 리소스 경쟁으로 인한 리소스 혼란을 방지할 수도 있습니다.

동기화

상호 배제는 동시 프로세스/스레드에서 중요한 섹션을 사용하는 문제를 해결합니다. 크리티컬 섹션 제어에 기반한 이러한 상호 작용은 상대적으로 간단합니다.프로세스/스레드가 크리티컬 섹션에 합류하는 한 크리티컬 섹션에 진입하려는 다른 프로세스/스레드는 첫 번째 프로세스/스레드가 크리티컬 섹션을 떠날 때까지 차단됩니다.

멀티 스레딩에서 각 스레드는 반드시 순차적으로 실행되는 것은 아니며 기본적으로 독립적이고 예측할 수 없는 속도로 진행되지만 때로는 여러 스레드가 긴밀하게 협력하여 공동의 목표를 달성할 수 있기를 바랍니다.

예를 들어 스레드 1은 데이터 읽기를 담당하고 스레드 2는 데이터 처리를 담당하며 이 두 스레드는 서로 협력하고 의존합니다. 쓰레드 2는 쓰레드 1로부터 웨이크업 알림을 받지 못하면 항상 차단되고 대기합니다. 처리를 위해 데이터를 스레드 2로 보냅니다.

소위 동기화는 동시 프로세스/스레드가 서로 대기하고 일부 주요 지점에서 서로 통신해야 할 수 있음을 의미합니다.대기 및 통신 정보에 대한 이러한 종류의 상호 제한을 프로세스/스레드 동기화라고 합니다 .

추신: 동기화와 상호 배제는 서로 다른 두 가지 개념입니다.

  • 동기화: [작업 A는 작업 B보다 먼저 수행되어야 함], [작업 C는 작업 A와 작업 B가 모두 완료된 후에 수행되어야 함] 등
  • 상호 배제: [작업 A와 작업 B를 동시에 실행할 수 없음];

상호 배제 및 동기화 구현 및 사용

프로세스/스레드의 동시 실행 과정에서 프로세스/스레드 간에는 상호 배제 및 동기화와 같은 협력 관계가 있습니다.

프로세스/스레드 간의 올바른 협력을 달성하기 위해 운영 체제는 프로세스 협력을 달성하기 위한 조치와 방법을 제공해야 합니다.두 가지 주요 방법이 있습니다.

  • 잠금 : 잠금 및 잠금 해제 작업
  • 세마포어 : P, V 동작

둘 다 프로세스/스레드 상호 배제를 쉽게 실현할 수 있으며 세마포어는 잠금보다 강력하며 프로세스/스레드 동기화도 쉽게 실현할 수 있습니다.

잠그다

잠금 작업과 잠금 해제 작업을 사용하면 동시 스레드/프로세스의 상호 배제 문제를 해결할 수 있습니다.

크리티컬 섹션에 진입하려는 스레드는 먼저 잠금 작업을 수행해야 합니다. 잠금 작업을 성공적으로 통과하면 스레드는 임계 영역에 진입할 수 있으며 임계 리소스에 대한 액세스를 완료한 후 잠금 해제 작업을 수행하여 임계 리소스를 해제합니다.

 Lock의 구현에 따라 [Busy Waiting Lock]과 [No Busy Waiting Lock]으로 나눌 수 있다.

바쁜 대기 잠금 구현

Busy Waiting Lock의 구현을 설명하기 전에 최신 CPU 아키텍처 --Test-and-Set 명령 에서 제공하는 특수 원자 연산 명령을 이해해야 합니다 .

Test-and-Set 명령을 C 코드로 표현하면 다음과 같은 형식입니다.

test 및 set 지시문은 다음을 수행합니다.

  • new의 새 값으로 old_ptr 업데이트
  • old_ptr의 이전 값을 반환합니다.

핵심은 이러한 코드가 원자적으로 실행된다는 것입니다 . 이전 값을 테스트하고 새 값을 설정할 수 있으므로 이 명령을 [테스트 및 설정]이라고 합니다.

원자적 작업: 모두 실행되거나 아무 것도 실행되지 않으며 절반만 실행되는 중간 상태가 있을 수 없습니다 .

Test-and-Set 명령을 사용하여 [사용 중 대기 잠금]을 달성할 수 있습니다. 코드는 다음과 같습니다.

 이 잠금이 작동하는 이유를 이해합시다.

  • 시나리오 1: 먼저 스레드가 실행 중이고 lock() 을 호출한다고 가정하고 다른 스레드가 잠금을 보유하지 않으므로 플래그 는 0입니다. TestAndSet(flag, 1) 메서드가 호출되고 0이 반환 되면 스레드는 while 루프에서 벗어나 잠금을 획득합니다. 동시에 플래그는 원자적으로 1로 설정되어 잠금이 이미 유지되었음을 나타냅니다. 스레드가 크리티컬 섹션을 벗어나면 unlock()을 호출하여 플래그를 0으로 지 웁니다 .
  • 시나리오 2: 스레드가 이미 잠금을 보유하고 있는 경우(즉, 플래그 가 1임). 이 스레드는 lock()을 호출한 다음 TestAndSet(flag, 1)을 호출하여 이번에는 1을 반환합니다 . 다른 스레드가 계속 잠금을 유지하는 한 TestAndSet()은 반복적으로 1을 반환하고 이 스레드는 바쁘게 대기합니다 . 플래그가 최종적으로 0으로 변경 되면 스레드는 TestAndSet() 을 호출 하고 0을 반환하고 원자적으로 1로 설정하여 잠금을 획득하고 임계 영역에 들어갑니다.

분명히 잠금을 획득할 수 없을 때 스레드는 아무것도 하지 않고 항상 루프를 돌게 되므로 이를 [비지 대기 잠금]이라고 하며 스핀 잠금 이라고도 합니다 .

이것은 잠금이 사용 가능할 때까지 CPU 주기를 사용하여 항상 회전하는 가장 단순한 종류의 잠금입니다. 단일 프로세서에서는 선점형 스케줄러가 필요합니다(즉, 하나의 스레드는 다른 스레드를 실행하기 위해 시계에 의해 지속적으로 중단됨). 그렇지 않으면 회전하는 스레드가 CPU를 포기하지 않기 때문에 단일 CPU에서 스핀 잠금을 사용할 수 없습니다. .

대기 잠금 없이 구현

대기 잠금 없음: 잠금을 획득할 수 없으면 스핀이 필요하지 않습니다. 잠금을 획득하지 못한 경우 현재 스레드를 잠금 대기 큐에 넣은 다음 스케줄러를 실행하고 다른 스레드에서 CPU를 실행하도록 합니다.

이번에는 두 가지 단순 잠금 구현만 제안합니다. 물론 특정 운영 체제에서는 더 복잡하겠지만 이 예제의 두 가지 기본 요소와도 분리할 수 없습니다.

신호량

세마포어는 공유 리소스에 대한 액세스를 조정하기 위해 운영 체제에서 제공하는 방법입니다.

일반적으로 세마포어는 자원의 수를 나타내며 해당 변수는 정수(sem) 변수입니다.

또한 신호를 제어하기 위한 두 가지 원자적 운영 시스템 호출 함수 가 있습니다 .

  • P 연산: sem을 1 만큼 감소 , 빼기 후, sem < 0 이면 프로세스/스레드가 차단 대기에 들어가고, 그렇지 않으면 계속 진행하여 P 연산이 차단될 수 있음을 나타냅니다.
  • V 작업: sem 에 1을 추가하고 추가한 후 sem <= 0이면 대기 중인 프로세스/스레드를 깨우고 V 작업이 차단되지 않음을 나타냅니다.

팁 V 연산에서 sem <= 0인 이유

예: sem = 1이면 P 작업을 수행하는 세 개의 스레드가 있습니다.

  • 첫 번째 스레드 P가 작동한 후 sem = 0, 첫 번째 스레드가 계속 실행
  • 두 번째 스레드 P의 작업 후 sem = -1, sem < 0 두 번째 스레드가 차단되고 대기
  • 세 번째 스레드 P가 동작한 후 sem = -2, sem < 0 세 번째 스레드가 차단되고 대기

이때 첫 번째 쓰레드가 V 연산을 수행한 후 sem=-1, sem <=0이므로 차단되어 대기 중인 두 번째 또는 세 번째 쓰레드를 깨울 필요가 있다.

P 동작은 임계 영역에 들어가기 전이고 V 동작은 임계 영역을 벗어난 후입니다.이 두 작업은 쌍으로 나타나야 합니다 .

비유하자면 두 리소스의 세마포어는 두 개의 기차 트랙과 동일하며 PV 작동 프로세스는 다음과 같습니다.

 운영 체제는 PV 작동을 어떻게 실현합니까?

세마포어 데이터 구조 및 PV 연산 알고리즘은 다음과 같이 설명됩니다.

PV 작업의 기능은 운영 체제에서 관리하고 구현하므로 운영 체제에서 PV 기능의 실행을 원자적으로 만들었습니다.

PV 연산은 어떻게 사용하나요?

세마포어는 크리티컬 섹션의 상호 배제 액세스 제어를 실현할 수 있을 뿐만 아니라 스레드 간에 이벤트를 동기화할 수도 있습니다.

세마포어는 중요 섹션의 상호 배제 액세스를 실현합니다.

공유자원의 종류별로 세마포어 s가 설정되며 초기값은 1 로 중요한 자원이 점유되지 않음을 나타낸다.

크리티컬 섹션에 들어가는 작업이 P(s)V(s) 사이에 있는 한 프로세스/스레드 상호 배제가 실현될 수 있습니다.

 이때 크리티컬 영역에 진입하려는 쓰레드는 먼저 뮤텍스 세마포어에 대해 P 연산을 수행한 후 크리티컬 리소스에 대한 접근을 완료한 후 V 연산을 수행해야 한다. 뮤텍스 세마포어의 초기 값은 1이므로 첫 번째 스레드가 P 연산을 수행한 후 s 값은 0이 되어 크리티컬 리소스가 비어 있고 크리티컬 섹션에 진입하기 위해 스레드에 할당될 수 있음을 나타냅니다.

이때 두 번째 쓰레드가 크리티컬 섹션에 들어가고자 한다면 역시 P 연산을 먼저 실행해야 하고 그 결과 s가 음수 값이 되어 크리티컬 리소스를 점유했다는 의미이므로 두 번째 쓰레드는 차단된다.

그리고 첫 번째 쓰레드가 V 연산을 수행할 때까지 크리티컬 리소스를 해제하고 s 값을 0으로 되돌린 후 두 번째 쓰레드를 깨워 크리티컬 섹션에 진입하게 하고 크리티컬 리소스 접근이 끝나면 V 연산을 수행한다. 다시 작동하여 s가 초기 값 1로 돌아갑니다.

두 개의 동시 스레드에 대해 뮤텍스 세마포어의 값은 각각 다음을 나타내는 1, 0 및 -1의 세 가지 값만 취합니다.

  • 뮤텍스 세마포어가 1이면 크리티컬 섹션에 진입한 쓰레드가 없다는 뜻이다.
  • 뮤텍스 세마포어가 0이면 스레드가 크리티컬 섹션에 진입했음을 의미합니다.
  • 뮤텍스 세마포어가 -1이면 하나의 쓰레드가 크리티컬 섹션에 진입하고 다른 쓰레드는 들어가기를 기다린다는 뜻이다.

상호 배제 세마포어를 통해 임계 영역에서 항상 하나의 스레드만 실행되고 상호 배제 효과가 달성됨을 보장할 수 있습니다.

세마포어는 이벤트 동기화를 구현합니다.

동기화하는 방법은 초기값이 0 인 세마포어를 설정하는 것입니다 .

 어머니가 처음 아들에게 요리하겠느냐고 물었을 때 아들에게 밥을 먹느냐고 묻는 것과 같은 실행이었으며 초기 값이 0이었으므로 이 때 -1로 바뀌어 아들이 밥을 먹을 필요가 없음을 나타냅니다. 먹다, 그래서 마더 스레드는 대기 상태에 들어갔다. P(s1)  s1  s1 

아들이 배고프면 실행되어 세마포어 가 -1에서 0으로 바뀌어 이때 아들이 밥을 먹어야 한다는 것을 나타내어 차단된 모실을 깨우고 모실을 요리하기 시작한다. V(s1) s1 

그러면 아들쓰레드가 실행되는데 이는 어머니에게 식사가 다 되었는지 묻는 것과 같으며,   초기값이 0  이므로  이때 -1이 되어 어머니가 요리를 다 하지 않았음을 나타내며 아들쓰레드는 대기 중. P(s2)s2s2

드디어 엄마가 요리를 끝내고 실행하니 세마포어 가 -1에서 다시 0으로 바뀌어서 기다리던 아들쓰레드를 깨웠고, 깨어나면 아들쓰레드가 밥을 먹기 시작한다. V(s2)s2 

생산자-소비자 문제

 생산자-소비자 문제 설명:

  • 생산자가 데이터를 생성한 후 버퍼에 배치됩니다.
  • 소비자는 처리를 위해 버퍼에서 데이터를 가져옵니다.
  • 언제든지 하나의 생산자 또는 소비자만 버퍼에 액세스할 수 있습니다.

문제 분석을 통해 다음과 같은 결론을 내릴 수 있습니다.

  • 오직 하나의 스레드만이 버퍼를 작동할 수 있으며, 이는 버퍼 작동이 상호 배제가 필요한 중요한 코드임을 나타냅니다 .
  • 버퍼가 비어 있으면 소비자는 생산자가 데이터를 생성할 때까지 기다려야 하고, 버퍼가 가득 차면 생산자는 소비자가 데이터를 가져올 때까지 기다려야 합니다. 생산자와 소비자가 동기화되어야 한다고 설명합니다 .

그런 다음 세 개의 세마포어가 필요합니다.

  • 상호 배제 세마포어 뮤텍스 : 버퍼에 상호 배타적으로 액세스하는 데 사용되며 초기화 값은 1입니다.
  • Resource semaphore fullBuffers : 소비자가 버퍼에 데이터가 있는지 묻고 데이터가 있으면 데이터를 읽는 데 사용되며 초기 값은 0입니다(처음에 버퍼가 비어 있음을 나타냄).
  • Resource semaphore emptyBuffers : 생산자가 버퍼에 공간이 있는지 물어보기 위해 사용하며, 공간이 있으면 데이터를 생성하고 초기화 값은 n(버퍼 크기);

특정 구현 코드:

 소비자 스레드가 초기에 P(fullBuffers)를 실행하면 세마포어 fullBuffers 의 초기 값이 0이므로 fullBuffers 의 값은 이때 0에서 -1로 변경되어 버퍼에 데이터가 없음을 나타내며 소비자는 기다릴 수만 있습니다.

그런 다음 생산자가 P(fullBuffers) 를 실행하여 하나의 빈 슬롯을 줄입니다. 다른 생산자 스레드가 현재 크리티컬 섹션에서 코드를 실행하고 있지 않으면 생산자 스레드가 데이터를 버퍼에 넣을 수 있습니다. , V(fullBuffers) 를 실행하면 세마포 fullBuffers가 -1에서 0으로 변경되어 소비자 스레드가 데이터를 차단하고 대기하고 있으므로 차단된 대기 소비자 스레드가 깨어날 것임을 나타냅니다.

소비자 쓰레드가 깨어난 후, 현재 다른 소비자 쓰레드가 데이터를 읽고 있지 않다면 크리티컬 섹션에 직접 들어가 버퍼에서 데이터를 읽을 수 있다. 마지막으로 크리티컬 섹션을 그대로 두고 빈 슬롯 수에 1을 더합니다.


고전적인 동기화 문제

식사하는 철학자 문제

 철학자 문제 진술:

  • 원탁에 둘러앉아 국수를 먹는 5명의 철학자
  • 이 테이블에는 포크가 5개뿐이며 두 철학자 사이에 포크가 하나씩 있습니다.
  • 철학자들은 함께 생각하는 것이 아니라 생각하는 동안 배고플 때 먹고 싶어한다.
  • 그러나이 철학자들은 국수를 기꺼이 먹기 위해 두 개의 포크가 필요합니다. 즉, 왼쪽과 오른쪽에있는 포크를 가져와야합니다.
  • 식사 후에는 포크를 다시 제자리에 놓으십시오.

질문: 아무도 포크를 받지 않도록 철학자의 행동이 질서 있게 수행되도록 하는 방법은 무엇입니까?

옵션 1

세마포어 방법, 즉 PV 연산을 사용하여 다음을 해결하려고 합니다.

 위의 절차는 매우 자연스러워 보입니다. 포크를 들고 P를 사용하여 작동합니다. 즉, 포크가 있으면 직접 사용하고 포크가 없으면 다른 철학자가 포크를 다시 놓을 때까지 기다리십시오.

 그러나 이 솔루션에는 극단적인 문제가 있습니다. 5명의 철학자가 동시에 왼쪽에 있는 포크를 집는다고 가정하면 테이블에 포크가 없으므로 아무도 오른쪽에 있는 포크를 가져갈 수 없습니다. 모든 철학자는 P(fork[(i+1)%N]) 문이 차단되고 교착 상태가 발생한 것이 분명합니다.

옵션 II

계획 1은 동시에 왼쪽 포크를 놓고 경쟁하여 교착 상태가 발생하므로 포크를 가져오기 전에 뮤텍스 세마포어를 추가합니다. 코드는 다음과 같습니다.

 위 프로그램에서 상호 배제 세마포어의 기능은 철학자가 임계 영역에 들어가는 한, 즉 그가 포크를 잡으려고 할 때 다른 철학자들은 움직일 수 없다는 것입니다 .

Solution 2는 철학자들이 순서대로 식사를 하도록 하되, 한 번에 한 명의 철학자만 식사를 할 수 있고, 테이블에는 5개의 포크가 있다. 최상의 솔루션이 아닙니다.

세 번째 솔루션

솔루션 1의 문제점은 모든 철학자가 동시에 왼쪽 나이프와 포크를 잡을 가능성이 있기 때문에 철학자가 왼쪽 나이프와 포크를 동시에 잡는 것을 방지하고 분기 구조를 채택하고, 철학자의 수에 따라 다른 행동을 취하십시오.

즉, 짝수 철학자는 [왼쪽 포크를 먼저 취하고 그 다음에 오른쪽 포크를 취]하고, 홀수 철학자는 [우측 포크를 먼저 취한 다음 왼쪽 포크를 취] 하게 하십시오 .

 위의 프로그램에서 P를 조작할 때 왼쪽 포크와 오른쪽 포크를 집는 순서는 철학자의 수에 따라 다릅니다. 또한 V 동작은 차단되지 않기 때문에 분기가 필요하지 않습니다.

 옵션 3은 교착 상태가 없고 두 사람이 동시에 식사를 할 수 있음을 의미합니다.

옵션 4

다음은 배열 상태를 사용하여 각 철학자의 세 가지 상태인 먹는 상태, 생각하는 상태 및 배고픈 상태(포크를 잡으려고 함)를 기록하는 또 다른 실현 가능한 솔루션입니다 .

그러면 철학자는 두 이웃이 모두 식사를 하지 않는 경우에만 식사 상태에 들어갈 수 있습니다 .

i번째 철학자의 이웃은 LEFT 및 RIGHT 매크로로 정의됩니다.

  • 왼쪽: (i+5-1) % 5
  • 오른쪽: (i+1) % 5

예를 들어 i가 2이면 LEFT는 1이고 RIGHT는 3입니다.

구체적인 구현 코드는 다음과 같습니다.

 위의 프로그램은 각 철학자에 대해 하나씩 세마포어 배열을 사용하므로 필요한 포크가 점유된 동안 식사를 원하는 철학자는 차단됩니다.

각 프로세스/스레드는  smart_person 기능을 기본 코드로 실행하는 반면 다른  프로세스/스레드  는   별도의 프로세스/스레드가 아닌 일반 기능일 뿐입니다.take_forksput_forkstest

 리더-라이터 문제

앞선 "식사하는 철학자 문제"는 I/O 장치와 같이 제한된 배타적 액세스로 경쟁하는 문제와 같은 모델링 프로세스에 유용합니다.

또한 데이터베이스 액세스 모델을 설정하는 잘 알려진 "reader-writer" 문제가 있습니다.

Reader는 데이터를 읽을 수만 있고 수정할 수는 없지만 Writer는 데이터를 읽고 수정할 수 있습니다.

Reader-Writer 문제 설명:

  • "읽기-읽기" 허용: 동시에 여러 리더가 동시에 읽을 수 있습니다.
  • "읽기-쓰기" 상호 배제: 작성자가 없을 때 독자가 읽을 수 있고 독자가 없을 때 작성자가 쓸 수 있음
  • "쓰기-쓰기" 상호 배제: 작가는 다른 작가가 없을 때만 쓸 수 있습니다.

옵션 1

세마포어를 사용하여 문제를 해결합니다.

  • 세마포어 wMutex: 쓰기 작업을 제어하는 ​​상호 배제 세마포어, 초기값은 1입니다.
  • 독자 수 rCount: 읽고 있는 독자의 수로, 0으로 초기화됩니다.
  • 세마포어 rCountMutex: rCount 판독기 카운터 포인트의 상호 배타적인 수정을 제어합니다. 초기 값은 1입니다.

코드 구현:

 위의 구현은 독자 우선 전략으로, 읽고 있는 독자가 있는 한 후속 독자가 직접 들어갈 수 있기 때문에 독자가 계속 진입하면 작가는 기아 상태에 놓이게 됩니다.

옵션 II

독자 우선 전략이 있기 때문에 자연스럽게 작가 우선 전략이 있습니다.

  • 작가가 쓸 준비가 되어 있는 한, 작가는 가능한 한 빨리 쓰기 작업을 수행해야 하며 후속 독자는 차단해야 합니다.
  • 계속 쓰는 작가가 있으면 독자는 굶주린다

계획 1을 기반으로 다음 변수를 추가합니다.

  • Semaphore rMutex : 판독기가 들어가도록 제어하는 ​​상호 배제 세마포어, 초기값은 1;
  • Semaphore wDataMutex : 기록기의 쓰기 작업을 제어하는 ​​상호 배제 세마포, 초기 값은 1입니다.
  • Writer count wCount : 작가의 수를 기록하며, 초기값은 0입니다.
  • 세마포어 wCountMutex : wCount 상호 배제 수정 제어, 초기 값은 1입니다.

구현 코드는 다음과 같습니다.

 여기서 rMutex 의 역할 은 처음에 데이터를 읽는 다수의 reader가 있고 모두 reader queue에 들어가는데 이때 writer가 오고 P(rMutex) 를 실행한 후에 는 후속 reader가 막혀서 들어갈 수 없다. on rMutex reader queue이고 writer가 도착하면 모두 writer queue에 들어갈 수 있으므로 writer의 우선 순위가 보장됩니다.

동시에 첫 번째 작성자는 P(rMutex) 를 실행한 후 바로 쓰기를 시작할 수 없으며 리더 큐에 들어가는 모든 리더가 읽기 작업을 완료할 때까지 기다려야 하며 V(wDataMutex) 를 통해 작성자의 쓰기 작업을 깨울 수 있습니다 .

세 번째 솔루션

독자 우선 전략과 작가 우선 전략 모두 기아를 유발할 것이기 때문에 공정한 전략을 구현합시다.

공정한 전략:

  • 같은 우선 순위;
  • 작성자와 독자는 상호 배타적인 액세스를 제공합니다.
  • 한 명의 작성자만 중요한 섹션에 액세스할 수 있습니다.
  • 여러 독자가 중요한 리소스에 동시에 액세스할 수 있습니다.

특정 코드 구현:

 Scheme 1의 독자 우선 전략을 비교하면 독자 우선 전략에서는 후속 독자가 도착하는 한 독자는 독자 대기열에 들어갈 수 있지만 작성자는 독자가 도착하지 않을 때까지 기다려야 함을 알 수 있습니다.

리더가 도착하지 않으면 리더 큐는 비어 있게 됩니다. 즉, rCount = 0 입니다. 이때 라이터는 쓰기 작업을 수행하기 위해 크리티컬 섹션에 들어갈 수 있습니다.

여기서 플래그 의 역할 은 독자의 이러한 특수 권한을 방지하는 것입니다(판독자가 도착하는 한 독자 큐에 들어갈 수 있음).

예를 들면, 일부 독자는 처음에 데이터를 읽고 모두 독자 대기열에 들어가는데 이때 작가가 와서 작업을 수행하여 후속 독자가 차단되어  독자 대기열에 들어갈 수 없도록 하여 점차 독자를 만들게 됩니다. queue empty. ,   0으로 줄어듭니다. P(falg) flag rCount

라이터는 즉시 쓰기를 시작할 수 없으며(현재 리더 큐가 비어 있지 않기 때문에) 세마포어에서 차단됩니다. 리더 큐 의 모든 리더가 읽기를 완료한 후 마지막 리더 프로세스가 실행되어 라이터를 깨웁니다. 그냥 지금, 쓰기 그렇지 않으면 쓰기 작업이 계속됩니다. wDataMutex  V(wDataMutex)

추천

출처blog.csdn.net/qq_48626761/article/details/132210855