이것은 한낱 대학생이 교수의 수업을 듣고 작성한 개인저장용 복습 문서입니다.
그렇지만, 물론 지적과 수정은 환영합니다.
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
|
복사된 총 바이트 수를 반환하기위한 메모리 공간을 할당
호출 프로그램은 해당 공간을 free해야함
하나의 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와 유사해보이지만 다른 개념이다.
'💻 CS > 시스템프로그래밍' 카테고리의 다른 글
WSL (Windows Subsystem for Linux) 사용하기 (0) | 2020.11.19 |
---|---|
유닉스 시작하기 (0) | 2020.11.19 |
[시스템프로그래밍] Times and Timers (0) | 2019.12.07 |
[시스템프로그래밍] Signals (0) | 2019.12.06 |
[시스템프로그래밍] UNIX Special Files (0) | 2019.12.01 |