Intro to Race Conditions

How can I create ten threads with different starting values.

다음 코드는 0~9까지의 값을 가지고 시작하는 10개의 스레드를 만들 것이라고 예상하였습니다.
하지만 실제로 동작할 땐 1 7 8 8 8 8 8 8 8 10을 찍었습니다.  왜일까요?
#include <pthread.h>
void* myfunc(void* ptr) {
    int i = *((int *) ptr);
    printf("%d ", i);
    return NULL;
}

int main() {
    // Each thread gets a different value of i to process
    int i;
    pthread_t tid;
    for(i =0; i < 10; i++) {
        pthread_create(&tid, NULL, myfunc, &i); // ERROR
    }
    pthread_exit(NULL);
}

위 프로그램은 race condition 상태입니다. i의 값이 계속 바뀌고 있습니다. 새로운 쓰레드는 나중에 시작합니다.(위의 예에서 마지막 쓰레드의 output은 루프가 끝난 다음에 시작합니다.)

이러한 race condition을 극복하려면 각각의 쓰레드에게 각자 데이터 영역에 포인터를 주어야 합니다. 

예를 들면, 각각의 쓰레드는 id, starting value, output value를 저장하기 원할수도 있습니다.


struct T {
  pthread_t id;
  int start;
  char result[100];
};

이것들은 malloc으로 heap에 할당된 array에 저장될 수 있습니다.


struct T *info = calloc(10 , sizeof(struct T)); // reserve enough bytes for ten T structures

그 후에 각각의 element가 쓰레드에 전달됩니다.


pthread_create(&info[i].id, NULL, func, &info[i]);


Why are some functions e.g asctime, getenv, strtok, strerror not thread-safe?

이것에 대한 답을 하기 위해 thread-safe가 아닌 단순한 함수를 살펴봅시다.

char *to_message(int num) {
    char static result [256];
    if (num < 10) sprintf(result, "%d : blah blah" , num);
    else strcpy(result, "Unknown");
    return result;
}


위의 코드에서 result buffer는 global memory에 저장되어 있습니다. 이렇게 함으로써 스택의 유효하지 않은 주소를 반환할 일은 없어졌지만 전체 메모리에는 하나의 result buffer만 존재하게 됩니다. 만약 두개의 쓰레드가 동시에 이 버퍼를 사용한다면 한 쓰레드는 다른 쓰레드를 오염시킬것입니다.

TimeThread 1Thread 2Comments
1to_m(5 )
2to_m(99)Now both threads will see "Unknown" stored in the result buffer


What are condition variables, semaphores, mutexes?

위에 제시된 것들은 race condition을 막고 한 프로그램 내에서 돌아가는 쓰레드들 간의 적절한 동기화를 보장하는 synchronization lock들입니다. 추가적으로, 이러한 lock들은 개념적으로 커널 안에서 사용되는 요소들과 일치합니다.


Are there any advantages of using threads over forking processes?

쓰레드들 간에 infomation을 공유하는 것이 매우 쉽습니다. 한 프로세스 안에 쓰레드들이 같은 가상 메모리 공간에 존재하기 때문입니다. 또한 쓰레드를 만드는 것이 프로세스를 만드는 것보다 확실히 빠릅니다.

Are there any dis-advantages of using threads over forking processes?

Isolation이 없다는 것입니다. 쓰레드들은 같은 프로세스 안에 있기 때문에 하나의 쓰레드가 다른 쓰레드도 사용하는 가상 메모리에 접근할 수 있습니다. 하나의 쓰레드가 전체 프로세스를 종료시킬 수도 있습니다(0번지를 읽기 등).



Can you fork a process with multiple threads?

가능합니다. 하지만 child process는 오직 하나의 Thread만 가지고 있습니다.(fork를 호출한 thread의 clone) 다음의 간단한 예를 보면 background threads는 child process의 두번째 메세지를 출력하지 않을 것입니다.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static pid_t child = -2;

void *sleepnprint(void *arg) {
  printf("%d:%s starting up...\n", getpid(), (char *) arg);

  while (child == -2) {sleep(1);} /* Later we will use condition variables */

  printf("%d:%s finishing...\n",getpid(), (char*)arg);

  return NULL;  
}
int main() {
  pthread_t tid1, tid2;
  pthread_create(&tid1,NULL, sleepnprint, "New Thread One");
  pthread_create(&tid2,NULL, sleepnprint, "New Thread Two");
  
  child = fork();
  printf("%d:%s\n",getpid(), "fork()ing complete");
  sleep(3);
    
  printf("%d:%s\n",getpid(), "Main thread finished");
  
  pthread_exit(NULL);
  return 0; /* Never executes */
}

사실 forking 전에 Thread들을 생성하는 것은 forking할 때 다른 Thread들이 즉시 terminated되므로 위에서 보였듯이 예측되지 않는 에러를 일으킬 수 있습니다. 다른 쓰레드는 mutax를 lock한 후에(malloc을 호출해서) 다시는 unlock하지 않습니다.  고급 사용자들은 pthread_atfork를 찾아 유용하게 사용할 수도 있지만, 위와 같은 접근의 한계와 어려움을 완전히 이해하기 전까지 forking 전에 쓰레드를 생성하는 일은 피하는게 좋다고 권장합니다.



Are there other reasons where fork might be preferable to crating a thread.

분리된 프로세스를 생성하는게 유용할 때입니다.
  • security를 보장하고 싶을 때(예를 들면 chrome은 탭마다 다른 프로세스를 사용합니다)
  • 기존의 완전한 프로그램을 실행할 때 새로운 프로세스가 필요한 경우(starting GCC)
  • synchronization primitives로 동작 중이고 각각의 프로세스가 시스템의 다른 곳에서 작동 중일때



'Angrave System Programming > Intro to Pthreads' 카테고리의 다른 글

pthreads : Parallel Problems(Bonus)  (0) 2019.01.14
Pthreads : Usage in Practice(1)  (1) 2019.01.14
Pthread Introduction  (0) 2019.01.14
Posted by 몰랑&봉봉
,