📌쓰레드
- 프로세스에 도입된 새로운 개념
실행을 표현한다.
프로세스의 구성요소
프로세스가 자체적으로 멀티코어를 활용하는 방법
- 멀티쓰레드 프로그램
여러 개의 실행 지점을 갖는다.
여러 개의 PC
같은 주소 공간을 공유한다.
쓰레드 사이의 문맥 교환
- 각 쓰레드는 자신만의 PC와 레지스터 집합을 갖는다.
각 쓰레드는 자신의 쓰레드 제어 블록(TCB)을 갖는다.
- T1 쓰레드에서 T2 쓰레드로 교환할 때
1- T1의 레지스터 집합이 저장
2- T2의 레지스터 집합이 로딩
3- 주소 공간은 그대로 유지
연관된 쓰레드의 스택
- 쓰레드마다 하나의 스택을 가지고 있다.
- 쓰레드는 스택 빼고 모두를 공유한다.
- 1번 쓰레드가 new를 사용하면 2번 쓰레드가 언제든 접근이 가능하다. delete도 가능하다 open, write도 가능하다.
- 운영체제는 쓰레드를 구별하지 않기 때문에 하나의 쓰레드가 다른 쓰레드를 어질러 놓을 수 있다. 따라서 서로의 쓰레드를 만들 때, 함수로 위치를 알려주고 각각의 쓰레드가 각자의 위치에서 일을 하도록 한다.
예제: 쓰레드 생성
- 위 예제에서 문제점은 A가 먼저 출력이 될지 B가 먼저 출력이 될지 모른다는 것이다. 즉 똑같은 입력, 다른 결과가 나오기 때문에 프로그래밍이 어렵다.
- 따라서 어디부터 실행하는지 함수, 함수포인터, 람다 등으로 알려줘야 한다.
- 컴파일러가 실행 시 별도의 쓰레드를 붙여준다.
- join : 쓰레드가 종료할때까지 기다린다. 없으면 하나 종료 후 바로 꺼지기 때문에 프로그램 오류
- 프로세서가 사라지면 쓰레드도 죽는다.
- C++11의 표준 API가 존재하며 이전에는 OS마다 서로 다른 API를 사용해야 했지만 C++11은 모두 사용 가능
쓰레드 생성 API
- 쓰레드 클래스를 이용하며 매개변수로 생성된 쓰레스가 실행할 함수를 넘겨준다. 없어도 된다.
쓰레드 종료 API
- 쓰레드가 할 일을 다 했으면 쓰레드 함수에 리턴을 해야 한다. 종료를 했는지 안했는지를 확인해야 한다.
- join: 쓰레드의 멤버함수로 쓰레드 객체의 종료를 busy waiting 없이 기다린다.
문제: 데이터의 공유
- 멀티쓰레드 프로그래밍은 하나의 작업을 여러 개로 나누어 여러 개의 쓰레드에서 동시 병렬적으로 수행하는 것이다.
쓰레드 사이의 정보 교환이 필수이다. -> 전역변수를 사용한 데이터 공유를 사용한다.
예를 들어 캐릭터가 돌을 던지는 모션을 취할 때, 돌을 던지는 쓰레드, 아바타 쓰레드는 캐릭터의 위치 정보를 공유해야 한다.
예제: 데이터 공유
다음 예제는 문제 없이 잘 출력이 될까?
정답은 그렇지 않다.
이유 -> 쓰레드 사이의 작업 순서 정리가 없이 마구잡이로 실행되었기 때문이다. 따라서 순서를 맞춰 실행을 해야한다.
이러한 현상을 경쟁 조건 == 경쟁 상태 == Race Condition == Data Race라고 부른다.
경쟁 조건
예를 들어 두 쓰레드가 있다. 두 개의 쓰레드에서 모두 Counter = Counter + 1을 해준다.
그러나 위와 같이 쓰레드 1이 Counter 값을 하나 올린 후 쓰기 전에 쓰레드 2가 중간에 끼어들어 실행이 된다.
결국 여러 개의 쓰레드가 읽고 쓸 때 실행 순서가 엉망이 되고 중간에 끼어들어서 오류가 생기는 것이다.
임계 영역
- 방금의 Counter와 같이 틀린 결과가 나올 수 있는 영역을 임계 영역이라 한다.
- 공유 변수에 접근하는 코드로 여러 쓰레드에서 동시에 수행되면 안되는 부분이다.
임계 영역을 여러 수레드가 동시에 수행하면 경쟁 조건이 발생한다.
임계 영역은 원자적으로 수행해야 한다. == 상호 배제
Atomic(원자적): "토막낼 수 없는"이라는 뜻으로 임계영역이 실행될 때 다른 쓰레드가 끼어들지 못하게 (원자적으로) 수행해야 한다.
📌락
- 앞에서 살펴본 임계 영역들이 하나의 원자적 명령어인 것처럼 실행되게 하는 함수이다.
- 락은 한 번에 한 쓰레드만 갖을 수 있고 아무도 갖지 않을 때 얻을 수 있다.
- 여러 개의 명령어를 원자적으로 실행한다.
- 다음과 같이 mutex를 인클루드 하여 락 객체를 만들 수 있다.
- 락 언락으로 사용할 수 있다.
- 간단한 사용법으로 임계영역이라고 판단되는 지점에 락을 걸고 임계 변수에 대한 연산이 끝났다면 언락으로 해제한다.
- 락을 갖고 있는 쓰레드가 없다면 락을 바로 얻어 임계 영역에 진입한다.
- 락을 갖고 있는 쓰레드가 있다면 락을 얻을 때 까지 리턴하지 않는다.
- 만약 10개의 쓰레드가 락을 얻기를 기다리고 있을 때
1- 락을 랜덤하게 얻는 경우
2- 큐에 넣어 순서대로 얻는 경우
Try_Lock
- 락의 획득에 실패하였다면 false를 리턴하여 다른 일을 실행하고 획득에 성공하면 true를 리턴한다.
- 락을 가지고 있는 쓰레드가 없다고 하여 무조건 성공하는 것은 아님
- 락을 이미 가지고 있는 쓰레드에서 호출했을 경우에는 어떻게 될지 알 수 없다.
📌요약
- 여러 개의 쓰레드가 하나의 프로세스에서 일하기 위해서는
- 정보 전달
- 전역 변수 공유
- 경쟁 조건
- mutex -> 운영체제와는 관계 없음