[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

💻 CS/시스템프로그래밍

[시스템프로그래밍] POSIX Threads

inu 2019. 12. 10. 17:42
반응형

이것은 한낱 대학생이 교수의 수업을 듣고 작성한 개인저장용 복습 문서입니다.

 

그렇지만, 물론 지적과 수정은 환영합니다.


Why threads?

 

- asynchronous events 효율적으로 처리 할 수 있음.
- 공유 메모리 멀티 프로세서에서 parallel performance(병렬적 성능)을 얻을 수 있습니다.


What are threads?

 

- 하나의 실행단위, 실행흐름

- multiple processes와는 다르게 서로 공유하는 메모리 공간이 있어, pipe나 sockets등을 사용할 필요가 없다.

- thread 각각이 자체 CPU에서 돌아가고, 서로 메모리를 공유하는 것처럼 보인다.

- 멀티 프로세스에 비해 훨씬 빠르다. (fork() vs pthread_create() 시, pthread_create가 훨씬 빠르다)


Processes

 

- 프로세스는 서로 독립적이다.

- 많은 state 정보를 가지기 때문에, context switch를 하는데 시간이 오래걸린다.

- 별도의 메모리 주소를 가진다.

- 시스템이 제공하는 IPC 메커니즘(interprocess communication mechanisms)을 통해서만 통신이 가능하다.


Thread management

 

POSIX thread functions
– pthread_cancel : 다른 thread를 종료
– pthread_create : thread 생성
– pthread_detach : thread의 release resources를 설정
– pthread_equal : 두 개의 thread ID의 일치여부 판단
– pthread_exit : exiting process없이 thread 종료
– pthread_kill : thread에게 signal전송
– pthread_join : thread의 작업완료 wait
– pthread_self : 자신의 thread ID를 찾는다.

• 대부분의 함수가 성공시 0을 리턴하고 실패하면 nonzero error값을 리턴한다. errno을 세트하지는 않는다.
EINTR을 리턴하지 않고, 또한 interrupt되더라도 재시작되지 않는다.


Referencing threads by ID

 

#include <pthread.h>
pthread_t pthread_self(void);
pthread_t pthread_equal(thread_t t1, thread_t t2);

 

• pthread_self
- 현재 코드를 돌리는 자신의 thread ID 리턴
• pthread_equal
- 두개의 값 비교
- 같으면 0이 아닌값을, 틀리면 0을 리턴


Creating a thread

 

#include <pthread.h>
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr,
void *(*start_routine)(void *), void *restrict arg);

 

• 별도의 작업없이 바로 실행되는 thread생성 
• Parameters
– ‘thread’: 새로 만드려는 thread의 ID
– ‘attr’: 속성값, 보통 NULL값을 많이 준다.
– ‘start_routine’: 실행하려는 함수의 이름
– ‘arg’: ‘start_routine’에 들어갈 파라미터
• Return values
– 0 if successful, nonzero if unsuccessful


Detaching

 

#include <pthread.h>

int pthread_detach(pthread_t thread);

 

 thread 내부옵션을 변경하여 해당 thread가 resource를 release하도록 한다.

만약 thread가 detached thread가 아니라면, 해당 thread는 exit될때 resource를 release하지 않는다.

– 해당 함수를 통해 detached thread가 되면, exit할 때 그들의 정보를 기록하지 않는다.

(이미 resource를 release했기 때문에)
– Returns 0 if successful, nonzero if unsuccessful


Joining

 

#include <pthread.h>

int pthread_join(pthread_t thread, void **value_ptr);

 

• target thread가 종료될때까지 현재의 calling thread를 suspend한다.
– detached 처리되지 않은 thread의 resources는 다른 thread가 pthread_join를 사용하거나 전체 process가 종료되지 않는 이상 release되지 않음.
• Parameters
– ‘thread’: a target thread
– ‘value_ptr’: 리턴값이 들어갈 포인터의 포인터. null이면 상태값을 찾지도 않는다.
• Return values
– 0 if successful, nonzero if unsuccessful
• Example
– pthread_join(pthread_self()); (deadlock 생성)


Example of creation/joining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void monitorfd(int fd[], int numfds) { /* create threads to monitor fds */
    int error, i;
    pthread_t *tid;
    if ((tid = (pthread_t *)calloc(numfds, sizeof(pthread_t))) == NULL) {
        perror("Failed to allocate space for thread IDs");
        return;
    }
 
    for (i = 0; i < numfds; i++/* create a thread for each file descriptor */
    if (error = pthread_create(tid + i, NULL, processfd, (fd + i))) {
        fprintf(stderr, "Failed to create thread %d: %s\n", i, strerror(error));
        tid[i] = pthread_self();
    }
 
    for (i = 0; i < numfds; i++) {
        if (pthread_equal(pthread_self(), tid[i]))
            continue;
        if (error = pthread_join(tid[i], NULL))
            fprintf(stderr, "Failed to join thread %d: %s\n", i, strerror(error));
    }
free(tid)
 

- fd의 배열과 그 개수를 입력받는 함수

- 먼저 pthread_t의 공간만큼 동적으로 메모리를 할당하고

- 반복문을 배열의 수만큼 돌면서 thread를 만든다. 각 thread는 tid + i의 id를 가지고, processfd를 진행한다.

- 파라미터는 fd + i. 즉, i번째 fd이다.

- 후의 반복문에서 자신을 뜻하는 thread는 제외하고 존재하는 모든 thread의 리턴값 확인한다. (저장x)

- 그리고 그 값을 하나씩 체크하며 오류가 났을땐 문구를 출력한다. 그리고 할당했던 공간을 free한다.


Exiting

 

#include <pthread.h>
void pthread_exit(void* value_ptr);

 

• calling thread를 종료한다.
– ‘return’ 은 결국 암시적으로 pthread_exit를 사용한다.
– ‘value_ptr’: pthread_join이 해당 값을 받을 수있도록 저장되는 곳


Cancellation

 

#include <pthread.h>
int pthread_cancel(pthread_t thread);

 

• 다른 thread가 cancel되도록 한다.
– Returns 0 if successful, nonzero if unsuccessful
– target thread’s state and type에 따라 해당 cancel이 pending될수도 있다.
• PTHREAD_CANCEL_ENABLE: 취소요청을 받아들임
• PTHREAD_CANCEL_DISABLE: 요청 pending

 

#include <pthread.h> 

int pthread_setcancelstate(int state, int *oldstate);


• calling thread의 cancellability state를 바꿔준다.
– ‘state’: PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DISABLE
– ‘oldstate’: 원래 상태값
– Returns 0 if successful, nonzero if unsuccessful


Passing parameters and returning values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h> 
#include <unistd.h>
#include "restart.h"
 
void *copyfilemalloc(void *arg)  { /* copy infd to outfd with return value */
   int *bytesp;
   int infd;
   int outfd;
 
   infd = *((int *)(arg));
   outfd = *((int *)(arg) + 1);
   if ((bytesp = (int *)malloc(sizeof(int))) == NULL)
      return NULL;
   *bytesp = copyfile(infd, outfd);
   r_close(infd);
   r_close(outfd);
   return bytesp; 
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 
 
– 프로그램 12.5 : copyfilemalloc
복사된 총 바이트 수를 반환하기위한 메모리 공간을 할당
호출 프로그램은 해당 공간을 free해야함
만약 byte count가 정적 스토리지 클래스를 가진 변수에 저장되고이 정적 변수에 대한 포인터를 반환하면?
하나의 thread에서만 작동하고, thread가 두 개인 경우 모두 바이트 수를 동일한 위치에 저장하는 문제발생
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <errno.h>
#include <fcntl.h>
#include <pthread.h> 
#include <stdio.h>
#include <string.h> 
#include <sys/stat.h>
#include <sys/types.h>
#define PERMS (S_IRUSR | S_IWUSR)
#define READ_FLAGS O_RDONLY
#define WRITE_FLAGS (O_WRONLY | O_CREAT | O_TRUNC)
 
void *copyfilemalloc(void *arg);
 
int main (int argc, char *argv[]) {        /* copy fromfile to tofile */
   int *bytesptr;
   int error; 
   int fds[2];
   pthread_t tid;
 
   if (argc != 3) {
      fprintf(stderr, "Usage: %s fromfile tofile\n", argv[0]);
      return 1;
   }
   if (((fds[0= open(argv[1], READ_FLAGS)) == -1||
       ((fds[1= open(argv[2], WRITE_FLAGS, PERMS)) == -1)) {
      perror("Failed to open the files");
      return 1;
   }
   if (error = pthread_create(&tid, NULL, copyfilemalloc, fds)) {
      fprintf(stderr, "Failed to create thread: %s\n", strerror(error));
      return 1;
   }
   if (error = pthread_join(tid, (void **)&bytesptr)) {
      fprintf(stderr, "Failed to join thread: %s\n", strerror(error));
      return 1;
   }
   printf("Number of bytes copied: %d\n"*bytesptr);
   return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

– 프로그램 12.4 : callcopymalloc

file descriptor를 받아 프로그램 12.5의 일을 수행하고, 해당 thread에서 값을 받아 출력하는 프로그램

 

– 프로그램 12.4 와 12.5의 문제점

– thread는 반드시 자신의 자원을 clean해주어야 한다. 
– 단일 정수를 처리하기 위해 사용하기에는 너무 공간활용이 다이나믹하다. 


Passing parameters and returning values(2)

 

– 파라미터 자체에 return value 저장공간을 만들어놓는 방식

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include "restart.h"
 
void *copyfilepass(void *arg)  {
   int *argint;
 
   argint = (int *)arg;
   argint[2= copyfile(argint[0], argint[1]);
   r_close(argint[0]);
   r_close(argint[1]);
   return argint + 2
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

copyfilepass

3개짜리 배열을 인자로 받아, 해당 배열의 [2]의 공간에

copyfile에 대한 return값을 저장한다.

그리고 해당 포인터인 argint + 2를 리턴한다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <errno.h>
#include <fcntl.h>
#include <pthread.h> 
#include <stdio.h>
#include <string.h> 
#include <sys/stat.h>
#include <sys/types.h>
#define PERMS (S_IRUSR | S_IWUSR)
#define READ_FLAGS O_RDONLY
#define WRITE_FLAGS (O_WRONLY | O_CREAT | O_TRUNC)
void *copyfilepass(void *arg);
 
int main (int argc, char *argv[]) {
   int *bytesptr;
   int error; 
   int targs[3];
   pthread_t tid;
 
   if (argc != 3) {
      fprintf(stderr, "Usage: %s fromfile tofile\n", argv[0]);
      return 1;
   }
 
   if (((targs[0= open(argv[1], READ_FLAGS)) == -1||
       ((targs[1= open(argv[2], WRITE_FLAGS, PERMS)) == -1)) {
      perror("Failed to  open the files");
      return 1;
   }
   if (error = pthread_create(&tid, NULL, copyfilepass, targs)) {
      fprintf(stderr, "Failed to create thread: %s\n", strerror(error));
      return 1;
   }
   if (error = pthread_join(tid, (void **)&bytesptr)) {
      fprintf(stderr, "Failed to join thread: %s\n", strerror(error));
      return 1;
   }
   printf("Number of bytes copied: %d\n"*bytesptr);
   return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

callcopypass

file descriptor가 담기는 targs가 방이 3개이다.

해당 0,1 부분에만 값을 넣고 2는 바워둔다음 파라미터로 활용한다.

이 후 과정은 callcopymalloc과 유사하다.


Wrong parameter passing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <pthread.h> 
#include <stdio.h>
#include <string.h> 
#define NUMTHREADS 10
 
static void *printarg(void *arg) { 
   fprintf(stderr, "Thread received %d\n"*(int *)arg);
   return NULL;
}
 
int main (void) {        /* program incorrectly passes parameters to threads */
   int error;
   int i;
   int j;
   pthread_t tid[NUMTHREADS];
   
   for (i = 0; i < NUMTHREADS; i++
      if (error = pthread_create(tid + i, NULL, printarg, (void *)&i)) {
         fprintf(stderr, "Failed to create thread: %s\n", strerror(error));
         tid[i] = pthread_self();
      }
   for (j = 0; j < NUMTHREADS; j++) {
      if (pthread_equal(pthread_self(), tid[j]))
         continue;
      if (error = pthread_join(tid[j], NULL))
         fprintf(stderr, "Failed to join thread: %s\n", strerror(error));
   }
   printf("All threads done\n");
   return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

– 10개의 thread를 생성하고 output을 만든다.
– index i의 포인터값을 파라미터로 넘겨준다.
• 그 결과 0~9의 값이 아니라, 조금 다른 값이 나온다.

(시스템환경에 따라 다를 수 있지만, 나는 2~10의 값이 출력되었다.)
• for문으로 i값이 계속 변동되고 있는데, 그 값을 활용한 것이 문제이다. (i값이 thread로 넘어간 뒤에 늘어난 것이다.)

 

-> 해결 : sleep(1)을 반복문 사이에 주어, thread가 생성도리 여유를 준다.


Thread safety

 

POSIX는 자체적으로 thread 여러 곳에서 호출해도 괜찮은 함수를 정의하고 있다.

이를 Thread safety한 함수라고 한다.

이 외의 함수는 충돌을 일으킬 위험이 있다.

 

이전에 학습한 asynchronous safety는 signal handler에서 사용해도 문제가 발생하지 않는 함수를 정의한 것이다.

이는 thread safety와 유사해보이지만 다른 개념이다.

 

반응형