Computer Science/시스템 프로그래밍

[시스템 프로그래밍] Chapter 12

seungwon9201 2024. 6. 10. 11:49

concurrent 한 task를 하기 위한 방법(monitoring file descriprtors)

1. 프로세스를 여러개만들자.

2. select, poll함수를 사용하자.(blocking함수) 다중스레드를 사용하지 않고 싱글스레드 기반에서 이 두 함수를 써서 서버를 구축할 수 있음. 그러나 함수하나가 실행중일 때 다른 작업을 하지 못한다는 제한적인 부분이 있음

3. nonblocking I/O with polling으로 모니터링을 할 수 있다. 얼마나 자주 모니터링을 해야 하는지 타이머에 관한 부분을 모니터링해야 한다는 단점이 있다.

4. POSIX asynchronous I/O를 사용하자. 백그라운드로 요청한 사실을 수동으로 확인하려면 timing문제가 있고 시그널로 확인하려면 시그널핸들러를 사용해야 하는데 시그널을 쓸 때 주의사항이 있었다.

5. 결론은 스레드를 여러 개 만들자.

 

다중스레드는 왜 사용하냐?

여러 독립적인 스레드를 동시에 실행해서 전체 task 퍼포먼스를 향상하기 위해서

다중스레드를 사용하면 asynchronous 한 이벤트를 효율적으로 처리할 수 있다. 또한 paralled performance를 얻을 수 있다. 

스레드를 너무 많이 만들면 퍼포먼스가 떨어질 수 있음

 

스레드라는 개념은 무엇이냐?

각각의 스레드는 실행흐름이라고 나타낸다. 

스레드는 다중 프로세스와 닮았는데, 차이점은 스레드는 공유메모리를 보유하고 있어서 os의 도움 없이 커뮤니케이션이 가능하다. 

 

스레드들은 사람눈에는 동시(simultaneously)에 실행되는 거처럼 보이는데 실제로는 concurrent 하게 실행된다. 


multitasking

싱글 프로세스에서도 가능하고 멀티 프로세스에서도 가능하다.

싱글 프로세스에서는 time-division multiplexing(여러 스레드를 번갈아가면서 실행하는 것)을 통해서 concurrent 하게 작동한다.

멀티프로세서 or 멀티코어 시스템인 경우에 실제로 프로세스들이 동시(simultaneously)에 실행이 된다. 


Psocesses vs threads

프로세스들은 독립적이다. 왜냐하면 프로세스는 서로 다른 메모리공간을 가지고 공유메모리도 없기 때문이다. 

스레드는 각자 사용하는 메모리도 갖고 공유메모리도 갖고 있다. 

 

프로세스는 스레드와 비교했을 때 관리해야 할 상태 정보가 많다. ex) cotext switch

프로세스들은 각자 분리된 분리된 주소 공간을 갖고 있다.

프로세들 까리 커뮤니케이션을 하러면 inter-process communicaton 메커니즘이 필요하다. 즉, OS의 도움이 필요하다.

스레드는 별도의 도움이 필요가 없음, 공유메모리를 사용하면 되기 때문에

 

다중스레드가 좋은 것만은 아니다. 

스레드 간의 동기화 문제를 해결해야 한다.

 

프로세스는 kernel이 scheduling 할 수 있는 가장 heavies한 유닛이다.  

스레드는 kernel이 scheduling할 수 있는 가장 lightest 한 유닛이다. 

 

프로세스는 preemptively(선점적) multitasked이다. (언제는 실행 중이다가 쫓겨날 수 있음)

 

스레드가 프로세스보다 오버헤드가 적다.

각각의 프로세스 하나당 스레드는 하나씩 기본으로 생김

OS가 preemptive 한 스케줄링 알고리즘을 사용한다면 스레드도 동일하게 사용가능하다. 

스레드는 리소스를 소유하지 않는다.(단, stack, 레지스터, 프로그램카운터, thread-local storage는 예외)

스레드는 커널스레드와 유저 스레드로 나눌 수 있다. 커널 스레드는 커널이 관리할 수 있는 스레드이고 유저 스레드는 커널이 스레드의 존재를 모르고 유저레벨의 라이브러리에 의해서 관리되는 스레드이다. 요즘엔 딱히 구분 안 함

 

User space : 애플리케이션이 사용하는 메모리 공간

kernel space : 커널이 사용하는 메모리 공간 

커널이 실행할 때 사용하는 공간과 커널이 실행할때 필요한 메모리 공간을 커널 스페이스로 분리할 수 있다. 대부분의 커널 메모리는 절대로 디스크에서 swapped out 되지 않는다.(퍼포먼스가 떨어질 수 있기 때문에)

그래서 swapp in out은  userp space에서 일어난다. 

 

 

왜 다중 프로세스를 안 쓰고 Pthread를 쓸까?
스레드를 여러 개 만드는 것이 프로세스를 만드는 것보다 오버헤드가 적어서 시간이 더 빨리 걸리기 때문

 

POSIX thread 함수들

pthread_cancel : 다른 스레드들을 종료시키는 함수

pthread_create : 스레드를 만든다

pthread_detach : 아래에 자세히 설명

pthread_equal : 스레드의 ID가 같은지 비교하는 함수

pthread_exit : 프로세스하나를 종료

pthread_kill : 스레드에게 시그널을 보내는 함수

pthread_join : 스레드를 기다리는 함수

pthrea_self : 자신의 프로세스 ID를 얻어오는 함수

성공 시 0 에러 시 -1

EINTR을 반환하지 않아서 인터럽트가 발생돼도 재시작할 필요가 없다.

 

 

 

요청을 받아들이기만 해도 0을 리턴(스레드를 만들어서 0을 리턴한게 아님)
detach <-> joinable, joinable한 스레드란 다른 스레드들이 나의 종료를 기다릴수있는 스레드이다. detach함수를 이용해서 joinable하지 않고 detach하게 만들 수 있다. 즉, 스레드들이 다른 스레드의 종료를 기다릴 수 없다는 뜻j. joinable스레드는 테스크를 완료하고 종료할때 자기가 사용한 리소스를 바로 반환하지 않는다. 왜냐하면 다른 스레드가 이 종료를 기다릴 수 있기 때문이다. 그런데 detach된 스레드는 사용한 리소스를 바로 반환해서 다른 스데드들이 기다릴 수 없게 된다.
join함수를 호출할 스레드는 첫번째 파라미터로 지정된 스레드가 종료될때까지 가디린다. 종료되면 리턴값을 두번쨰 파라미터로 리턴한다. 즉, 프로세스가 종료되기전까지 리소스를 반환하지 않는다.pthread_join(pthread_self())처럼 내가 종료될때까지 기다린다는 deadlock형태의 코드를 쓰지말자.
exit와 pthread_exit함수의 차이점 : exit함수는 프로세스를 종료시키는 것이고 스레드를 종료하고 싶으면 thread_exit함수를 호출하면 된다.
pthread_cancel 함수는 파라미터로 지정한 스레드의 동착을 중단하도록 요청하는것, 취소요청을 받을지 말지를 정하는 함수가 pthread_setcancelstate함수이다. enable은 받고 disable은 받지 않음
stecancelstate를 설정하고 난 다음에 취소 요청을 받긴할건데 나의 동작을 언제 취소시킬것이다라고 지정하는 constatnt값이 두개가 있다. asynchronous와 deferred이다. asynchrornous는 취소요청을 받은 순간 바로 중단하는 것이고 deferred는 바로 취소가 아닌 원하는 시점에 취소 하는것이다. pthread_setcanceltype은 취소할 시점을 수동으로 지정하는 함수이다. pthread_testcancel함수는 위의 상황을 하던중에 취소요청이 들어왔나 test하는 함수이다.
무조건 어레이의 포언터로 만들어야함. 왜냐하면 로컬변수는 함수가 리턴되면서 함수가 사용한 메모리공간이 없어져서 쓰레기 값이 리턴되기 때문이다. 그럼 static변수를 통해서 리턴을 받으면 어떨까? 가능은 하나 하나의 스레드에서만 작동하는 해결책이다. 앞서 복사한 내용이 덮어 써질수 있기 때문이다.

위에는 malloc함수를 통해서 동적메모리로 할당한 부분은 메인 스레드에서 참조를 한 다음에 free를 해줘야 불필요한 메모리낭비를 막을 수 있었다. 그래서 정적변수인 static으로 선언해서 결괏값을 넘겨주는 방법이 있었는데 이방법은 하나의 경스레드에서만 작동을 했다. 왜냐하면 파일복사 스레드가 여러개 생성될 경우에 모든 결과값이 하나의 static변수에 할당되기 때문이다. 

그래서 카피한 결과값을 array에 담는 방법이 나오게 되었다. 이런 식으로 한다면 별도로 리턴을 할 필요 없이 array공간을 액세스 하면 된다.  즉, 메인 스레드가 array의 포인터를 넘겨줄 때 array에다가 리턴값을 받을 수 있는 공간을 하나 더 추가해서 넘겨주자. 그러면 array파라미터의 크기는 3이고 세 번째 파라미터에 복사한 값을 카피한다. 그래서 어레이에서 직접 받을 수 있고 join함수의 두 번째 파라미터에서도 받을 수 있다. 

위의 코드는 잘못된 예시이다. 이문제의 원인은 스레드가 생겨서 파라미터를 참조할 시점에 메인스레드에 의해서 이 포인터가 가리키는 변수값이 변경이 되버렸다. pthread_create함수는 스레드를 만드는 요청을 받아도 리턴이 되기 떄문에 스레드를 늦게 생성할 수도 있다. 그래서 스레드가 생성되는 시간에 따라서 값이 달라질 수 있음. 그래서 스레드가 생성이 될때 스레드가 갖고 있는 변수를 값이 변경되는 변수로 파라미터를 지정하면 이러한 문제가 발생한다. 이러한 문제를 해결하려면 pthread_create함수를 작동할때 아래에 sleep함수를 두어서 몇초있다가 만들어지도록 하자. 그런데 지정한 시간보다 또 늦게 나올 확룰이 있다. 그렇기 때문에 다른 방법도 있다.각 스레드에게 넘겨주는 값을 절대 변경되지 않는 값을 이용하는 것이다. 변수를 10갸를 만들어서 첫번째 스레드에게는 다른 변수를 주고 두번쨰 스레드에게도 다른 변수를 주자. 그럼 변수값들은 변경될 일이 없다.

 

 

Thread safety : 하나의 리소스에 여러 스레드가 액세스 할 때 충돌이 발생하지 않는 함수

asy signal safe 한 함수 : 시그널 핸들러 안에서 안전하게 호출할 수 있는 함수, 메인 프로그램과 시그널 핸들러 부분에서 동시에 호출을 할 때 충돌문제가 발생하지 않는 함수라고 했었다.

이 두 개의 함수 중에서 어떤 것이 만들기 어려울까? asy는 다중 프로세스가 아니더라도 발생할 수 있다. 그렇기 때문에 만들기 쉬운 것은 thread safety함수이다.