> For the complete documentation index, see [llms.txt](https://toothlessdev.gitbook.io/main/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://toothlessdev.gitbook.io/main/computer-science/network-programming/ch18.md).

# Ch18 멀티스레드 소켓통신

## 📖 쓰레드 Thread

### ✏️ Multi Process VS Thread

멀티 프로세스를 구현하기 위해 프로세스의 생성에는 많은 리소스가 소모됩니다\
또한 프로세스간 Context Switching 으로 인해 시스템 성능이 저하 될 뿐만 아니라, 독립적인 메모리 공간을 사용하기 때문에 프로세스간 데이터 공유가 힘들다는 단점이 있습니다.

반면 스레드는 프로세스보다 가볍고, 메모리 공유가 가능하다는 장점이 있습니다

### ✏️ Thread

스레드는 하나의 프로세스 내부에서 동작하고,\
Data 와 Heap 영역을 공유하기 때문에, 쓰레드 사이 데이터 교환이 쉽게 이루어집니다.

### ✏️ pthread\_create() 쓰레드의 생성

```c
#include <pthread.h>
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
// thread : 생성할 쓰레드 ID 저장을 위한 포인터
// attr : (NULL) 쓰레드에 부여할 특성 정보 (기본적인 쓰레드 생성)
// start_routine : 쓰레드가 처리할 함수
// arg : 함수 포인터에 전달할 인자의 포인터

pthread_create(&thread_id, NULL, thread_function, (void*)&thread_params);
```

예시)

```c
int main() {
    pthread_t thread_id;
    int thread_params = 5;
    
    if(pthread_create(&thread_id, NULL, thread_function, (void*)&thread_params) != 0) {
        puts("pthread_create() Error");
        return -1;
    }
    //...
    sleep(10); // 프로세스 종료시 스레드가 종료되는 것을 방지하기 위해 대기
    return 0;
}

void* thread_function(void *arg) {
    int count = *((int*) arg);
    for(int i = 0; i < count ; i++) {
        sleep(1);
        printf("Thread is Running..\n");
    }
    return NULL;
}
```

### ✏️ pthread\_join() 쓰레드의 종료 대기

프로세스가 종료되면 해당 프로세스 내에서 생성된 쓰레드가 소멸되기 때문에,\
`pthread_join()` 을 사용해 thread 의 종료를 기다릴 (blocking) 수 있습니다.

```c
int pthread_join(pthread_t thread, void **status);
// thread : 종료를 기다릴 thread ID
// status : 쓰레드 함수가 반환하는 값이 저장될 포인터 변수의 주소

// 성공시 0 반환
```

예시)

```c
int main() {
    pthread_t thread_id;
    void *thread_return;
    // ...
    if(pthread_join(thread_id, &thread_return) != 0) {
        printf("pthread_join() ERROR!\n");
        return -1;
    }
    printf("Thread Return Message : %s\n", (char*)thread_return);
    free(thread_return);
}
```

## 📖 Worker Thread Model

Worker Thread Model 은 MultiThread 환경에서,\
쓰레드에게 일을 시키고, 결과를 취합하는 쓰레드 구성 모델입니다.

이때 여러 개의 쓰레드가 공유 메모리 (전역변수) 에 접근하게 되는데\
이 공유 메모리 변수에 동기화 문제가 발생 할 수 있습니다

이는 쓰레드의 리소스 할당이 순차적으로 발생하지 않기 때문입니다.

### ✏️ Critical Section

Critical Section (임계영역) 은 여러 쓰레드가 접근가능한 공유자원 또는 코드를 말하고\
어느 한 순간에는 하나의 쓰레드만 사용 가능한 영역을 말합니다.\
보통 화장실에 비유합니다

임계 영역에 둘 이상의 쓰레드가 동시에 접근하면 동기화 문제가 발생하게 됩니다.

동기화 문제를 해결하기 위해

> 1. Mutex (Mutual Exclusion)
> 2. Semaphore

를 사용합니다

## 📖 Mutex 기반의 동기화

### ✏️ Mutex

Mutex 는 여러 쓰레드가 공유 자원, 임계영역에 동시에 접근하는 것을 허용하지 않는 동기화 기법입니다.

먼저 `lock` 을 획득하고 공유자원을 사용 합니다\
대기중인 thread 는 `lock` 을 획득 하기 전까지 대기합니다\
`lock` 을 획득한 thread 는 사용이 끝나면 `lock` 을 반납합니다

### ✏️ pthread\_mutex\_t, lock, unlock

```c
#include<pthread.h>

pthread_mutex_t mutex;
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
```

를 사용하면 Mutex 를 생성하고 소멸시킬 수 있습니다

```c
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
```

를 사용하여 lock 을 걸고 해제 할 수 있습니다

예)

```c
pthread_mutex_t mutex;
int shared_resource;

void* thread_func1(void *arg);
void* thread_func2(void *arg);

int main() {
    pthread_t thread_id[NUM_THREAD];
    pthread_mutex_init(&mutex, NULL); // MUTEX 생성
    
    // ...
    
    pthread_mutex_destroy(&mutex);
    return 0;
}

void *thread_func1(void *arg){
    pthread_mutex_lock(&mutex);
    // 임계영역 : shared_resource 에 대한 작업을 한번에 하나의 쓰레드만 접근 가능
    pthread_mutex_unlock(&mutex);    
}
```

## 📖 Semaphore 기반의 동기화

### ✏️ Semaphore

```c
#include <semaphore.h>

sem_t sem;
int sem_init(sem_t *sem, int pshared, unsigned int value);
// pshared == 0 : 하나의 프로세스 내에 존재하는 쓰레드 들의 동기화
// pshared != 0 : 둘 이상 프로세스에 의해 접근 가능한 세마포어 생성
// value : 세마포어 초기값 지정

int sem_destroy(sem_t *sem);
```

를 사용해 세마포어를 생성하고 소멸시킬 수 있습니다

### ✏️ Semaphore 의 획득과 반환

```c
// 세마포어 값 감소
int sem_wait(sem_t *sem);

// 세마포어 값 증가
int sem_post(sem_t *sem);
```

세마포어 값이 0 이면 임계영역에 진입하지 못하고,\
세마포어 값이 0 이상이면 임계영역에 진입할 수 있다.

따라서

```c
sem_wait(&sem);
// 임계 영역
sem_post(&sem);
```

와 같이 임계 영역을 구성하면\
`sem_wait()` 을 호출하고, 임계 영역에 진입한 쓰레드가 `sem_post()` 를 호출하기 전까지\
다른 쓰레드가 임계 영역에 진입하지 못한다

### ✏️ Semaphore 로 함수 호출 순서 제어

<figure><img src="/files/ouOWhK7FyCPzASEaHtrr" alt=""><figcaption></figcaption></figure>

두 개의 세마포어를 사용하면 함수 호출의 순서를 제어 할 수 있다

예)

```c
void *read(void *arg);
void *accu(void *arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(int argc, char *argv[]){
    pthread_t t1, t2;
    sem_init(&sem_one, 0, 0); // sem_one 초기값 0 
    sem_init(&sem_two, 0, 1); // sem_two 초기값 1 - read 가 먼저 수행
    
    pthread_create(t1, NULL);
    pthread_create(t2, NULL);
    
    sem_destroy(&sem_one);
    sem_destroy(&sem_two);
    return 0;
}

void *read(void *arg){
    for(int i = 0; i < 5; i++){
        printf("Input : ");
        
        sem_wait(&sem_two); // sem_two 가 1 이므로 read 를 호출하는 t1 은 진입가능 후 감소
        scanf("%d", &num);
        sem_post(&sem_one); // sem_one 을 0 에서 1로 증가
    }
}

void *accu(void *arg){
    int sum = 0;
    for(int i = 0; i < 5 ; i++) {
        sem_wait(&sem_one); // sem_one 초기값이 0 이므로 t2 blocking
        sum += num;
        sem_post(&sem_two);
        printf("sum : %d\n", sum);
    }
    printf("result : %d\n", sum);
    return NULL;
}
```
