본문 바로가기

CS

동시성 이슈

동기화는 근본적으로 어려운 문제라고 한다. 어떤 종류의 동시성 흐름이 공유 자원을 조작할 때 발생하는 일반적인 이슈들을 정리해 보자.

스레드 안전 (Thread safety)

쓰레드로 프로그래밍할 때, 스레드 안전성이라고 부르는 특성을 가지는 함수를 작성하도록 유의해야 함. 다수의 동시성 스레드로부터 반복적으로 호출될 때 항상 정확한 결과를 만드는 경우에만 스레드-안전이라고 한다. 병렬 프로그램에서는 단순히 lock만 신경 쓴다고 해결되지 않는 다고 한다. 만일 어떠한 함수가 스레드 안전하지 않다면 이것은 thread-unsafe라 한다. 스레드-위험한 함수의 네 가지 클래스를 다음과 같이 식별할 수 있다.

  • 클래스 1: 공유 변수를 보호하지 않는 함수들 예를 들어 보호되지 않는 전역 변수를 증가시키는 경우다. 이런 경우에는 우리가 아는 세마포어를 사용해서 P , V 같은 동기화 연산으로 보호한다. 한 가지 단점은 동기화 연산들이 이 함수를 느리게 할 것이다.
  • 클래스 2: 다중 호출에 대해서 상태를 유지하는 함수들. rand함수는 스레드 위험인 함수인데 그 이유는 현재 호출의 결과가 이전 반복 실행으로부터의 중간 결과에 의존하기 때문이다. srand를 호출해서 seed값을 가져온 후 rand를 한 개의 스레드로부터 반복해서 호출할 때, 반복되는 숫자들의 배열을 기대할 수 있지만, 다수의 스레드가 rand를 호출하면 위험하다. rand 같은 함수를 스레드 안전하게 만드는 유일한 방법은 함수를 재작성해서 static 데이터를 전혀 사용하지 않도록 하는 것이다. 이를 위해서는 호출자의 상태 정보를 인자들로 전달해야 한다. 
    unsafe 한 의사 랜덤 숫자 생성기
  • 클래스 3: 정적 변수를 가리키는 포인터를 리턴하는 함수. ctime 같은 함수들은 static 변수에 결과를 계산하고 그 후에 이 변수를 가리키는 포인터를 리턴한다. 만일 이러한 함수를 동시성 쓰레드로 호출하면 위험하다. 그 이유는 한 개의 스레드가 사용하는 결과들이 다른 쓰레드에 의해 조용하게 덮어써 지기 때문이다. 여기서는 두 가지 해결책이 있는데
    1. 한 가지는 함수를 다시 작성해서 호출자가 결과를 저장하는 변수의 주소를 전달하는 것이다. (이 방법은 공유데이터를 없앨 수 있지만, 프로그래머가 소스코드에 접근해야 함)
    2. 만약 함수 수정이 어렵다면 lock-and-copy 기술을 이용하는 것이다. 각각의 호출 위치에서 뮤텍스를 잠그고, 쓰레드-위험 함수를 호출하며, 함수가 리턴한 결과를 사적 메모리 위치로 복사하고, 그 후에 뮤텍스를 풀어준다. lock-and-copy 형태의 스레드-안전 래퍼 함수를 정의해야 한다.

ctime에 대한 쓰레드-안전 wrapper

  • 클래스 4: 쓰레드 위험 함수를 호출하는 함수들 , 만일 함수 f가 쓰레드-위험 함수 g를 호출한다면 f는 위험 함수 인가? 이건 상황마다 다르다. 만일 g가 다수 호출에 걸쳐서 상태에 의존하는 클래스 2 함수라면 f도 쓰레드 위험이고 g를 재작성해야 한다. g가 만일 1 또는 3 함수인데 공유 데이터를 뮤텍스로 보호한다면 f는 쓰레드 안전이다.

재진입성

쓰레드 안전과 reentrancy 한 함수라는 개념을 약간 구분할 필요가 있다. 재진입 가능 함수들의 집합은 쓰레드 안전 함수들의 부분집합이다. 재진입 가능 함수들은 보통 재진입 불가능 쓰레드-안전 함수들 보다 효율적이다. 왜냐하면 동기화 연산을 필요로 하지 않기 때문이다. 일부 함수의 코드를 검사하고 사전에 이것이 재진입 가능인지를 선언하는 것이 가능한가? 이건 상황에 따라 다르다. 만일 모든 함수 인자가 값으로 전달되고 모든 데이터 참조가 지역 자동 스택 변수들로 이루어진다면(즉, 정적 또는 전역 변수로 참조 안 함), 이 함수는 명시적으로 재진입 가능하며, 이 함수가 어떻게 호출되었는지에 관계없이 선언할 수 있다는 의미다.  하지만 만일 우리의 가정을 약간 완화하고, 명시적으로 재진입 가능한 함수에서 일부 매개 변수들을 참조 형태(즉 , 포인터 형태)로 전달되도록 해주며 , 그 후에 만일 호출하는 쓰레드들이 포인터를 공유되지 않은 데이터로 조심스럽게 전달하려고 한다면, 이것은 간접적으로 재진입 가능 함수를 가짐. rand_r 함수는 묵시적으로 재진입 가능이다.

rand 함수의 재진입 가능 버전

Race condition

race condition 이란 두 개 이상의 프로세스가 공통 자원을 병행적으로(concurrently) 읽거나 쓰는 동작을 할 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 같지 않고 달라지는 상황을 말한다

Race는 예제를 통해 알아보자

race

위의 코드의 결과가 입력받은 id처럼 나오기를 기대하지만 쓰레드 루틴에서 역참조 및 할당 사이에 메인에서 i를 증가시켜 버리기 때문에 아래와 같은 결과가 나온다

따라서 정수 id를 위한 별도의 블록을 동적으로 할당받는다면 위의 문제를 해결할 수 있다. 

또한 쓰레드 루틴에서 free 해줘야 한다.

아예 다른 방법으로는 i를 포인터로 넘기는 대신에 값 자체를 넘긴 후 쓰레드 루틴에서 int로 캐스팅하는 것이다.

for(i=0; i<N; i++)
	Pthread_create(&tid[i], NULL, thread , (void *) i);
    
   ------------------------------------------------------------
   
 int myid= (int) vargp;

 

'CS' 카테고리의 다른 글

프로시저  (0) 2021.10.02
전통적 동기화 문제와 데드락  (0) 2021.09.26
Nonlocal jumps  (0) 2021.09.24
Volatile 지정자  (0) 2021.09.24
dup, dup2 , 파일 서술자 복제 함수  (0) 2021.09.17