운영체제

쓰레드와 병행처리(Tread and Concurrency)

소재훈 2021. 9. 26. 17:28

 

쓰레드는 프로세스 내에 존재하는 여러개의 독립된 실행 단위이다. 독리되었다는 것은 자기 자원도 일부가지고 있지만 CPU 스케쥴링계산의 대상이 되느냐를 의마한다. 쓰레드도 하나의 스케쥴링 계산 대상이 된다.

Single-threaded and multithreaded processes.

프로세스가 하나 만들어지면 그 프로세스에 메모리, 파일, 레지스터 셋, 스택등이 할당되고 흐름이 하나만 할당된다면 single thread program, 여러개가 할당된다면 multi-threading 이라고 부른다. 중간에 쓰레드를 여러개 만들어 여러 프로그램이 동시에 실행된다.

 

위 그림의 오른쪽에서 code, data, file 등은 모든 쓰레드가 공동으로 가지는 자원이고, register, stack, PC등은 쓰레드별로 가지고 있는 자원임을 볼 수 있다.

쓰레드는 1.일반프로세스에 비해서 자원을 가지는 것이 적고, 2. 생성시간이 짧고, 3. 쓰레드간의 커뮤니케이션이 깊기 때문에 light-weight process 라고 부르기도 한다.

 

멀티쓰레딩의 장점

1. 응답성(Responsiveness): 여러개의 프로그램에 쓰레드를 하나씩 할당한다면 자원요청시 즉각적인 응답이 일어날 수 잇다.

2. 자원공유(Resource sharing): 프로세스는 서로 독립된 단위이기 때문에 자원 공유가 어려우나 쓰레드들은 프로세스안에 존재하기 때문에 자원 공유가 가능하다.

3. 경제성(Economy): 생성 속도가 더 빠르고 다른 프로세스로 흐름이 바뀔때 일어나는 Context Switching에서 쓰레드의 속도가 더빨라 시간과 공간을 효율적으로 절감할 수 있다.

4. 확장성(Scalability): 멀티코어 시스템을 확장할 때 멀티쓰레딩을 잘 짜놓았다면 확장이 쉬워진다.


Programming Challenges

멀티코어, 멀티쓰레딩을 사용하면서 새롭게 고려해야 될 문제들이 생겨났다.

  1. Identifying tasks: 컴퓨터에 여러개의 cpu가 있다고 해서 그 수에 비례해서 빨라지는 것이 아니기 때문에 최대한 그에 가깝게 빨라질 수 있도록 프로그램을 잘 짜야한다.
  2. Balace: 어떤 cpu는 놀고, 어떤 cpu는 열심히 일하고 있다면 곤란하다. cpu간의 밸런스가 잘 맞아야 throughput이 올라가게 된다.
  3. Data Splitting: 데이터를 어떻게 나누어 줄것인가.
  4. Data dependency: 어떤 작업이 이루어 지고 나서야 그다음 작업을 할 수 있는 자료간의 의존성 문제로, 동기화와 관련된 문제이다. cpu가 하나일 때보다 훨씬 복잡해졌다.
  5. testing and debugging: 이 부분이 가장 어려운 부분일지도 모른다. dbugging한다는 것은 프로그램이 어느 시점에서 제대로 output을 내고있는지 체크르하면서 프로그램이 맞다, 틀리다는 것을 판단하는 것인데 현장에서 어떤 문제가 발생했을 때 그 상황을 재현해가며 문제를 해결할 수 있다.

그런데 여러 개를 동시에 실행시키다 보면 이 재현이라는 것이 어려워 진다. 동기화(Synchronization) 처리를 해주지 않았다고 해서 항상 문제가 발생하는 것이 아니라 간혹 발생하기 때문에 debugging할때 빠져나갈 수 있다. 그러면 해당 에러를 다시 찾아낸다는 것은 굉장히 어려운 일이 된다.


Thread Libraries

쓰레드 라이브러리는 쓰레드의 생성과 관리를 위한 API를 제공한다.

  • 쓰레드 라이브러리가 유저레벨에 있다면, 쓰레드의 생성, 스케쥴링을 처리해준다. 단 스케쥴링을 처리한다고 해도 CPU를 할당 받는 것은 별도의 문제이므로 두 단계의 스케쥴링이 필요하다
  • 쓰레드 라이브러리의 기능이 커널안에 포함되어 있는 경우, 쓰레드의 생성도 전부 시스템 콜로 처리된다. 이런 것을 커널레벨 쓰레드 또는 커널레벨 라이브러리라고 이야기한다.

세가지의 쓰레드 라이브러리가 존재한다.

  1. POSIX Pthread
  2. Window
  3. Java

POSIX는 API Standard 이기 때문에 유저레벨에 구현할 수도 있고 커널레벨에도 구현할 수 있다. 둘다 가능하다.
Window는 쓰레드 기능을 제공하는데, 관리나 생성을 전부 커널레벨에서 수행한다.
Java에서는 JVM 에서 처리한다.

유닉스나 리눅스, 맥OS 에서는 Pthread 를 제공하고 리눅스는 리눅스 나름대로의 쓰레드를 생성하는 clone이라는 시스템 콜을 제공해서 쓰레드를 만드는 기능을 제공하지만 일반적으로는 Pthread 라이브러리를 커널레벨에서 제공하는 것이 일반적이다.

 

유저레벨에서 제공하게되면 장점도 있겠지만 가장 큰 단점은 속도가 트려지는 것이다. 대부분은 커널레벨에서 직접 처리해준다.

 

Pthread

Pthread_attr_init(): 쓰레드를 생성할 때 쓰레드의 속성을 정해주는 함수.
Pthread_creat(): 쓰레드를 생성해주는 함수. 쓰레드가 실행해야할 함수를 지정해줌.
우리가 많은 경우에 쓰레드를 생성한 후에 쓰레드의 작업이 끝날 때까지 기다리고 있다가 결과를 취합한다. fork() 한 후에 부모가 기다리는 것과 같이 쓰레드에서는 pthread_join()함수를 호출하면 해당 쓰레드가 끝날 때까지 대기( Ready ) 상태로 들어간다. 자바에서도 join()이 있었음. 쓰레드를 하나 생성해놓고 start 실행시킨다음 join.

이런 함수들을 유저레벨에서 구현할 수도 있고 커널레벨에서 구현할 수도 있다. 커널레벨에서 구현하면 커널에서 모든 쓰레드를 직접관리하게된다.

 

Windows Threads
윈도우도 Pthread 와 유사하다. CreateThread() 라는 함수로 쓰레드가 수행할 함수를 지정하고, join에 해당하는 것이 WaitForSingleObject()이다. 쓰레드가 생성되면 그 쓰레드의 핸들러가 종료될 때까지 기다려준다.

 

Java Threads
Java에서는 예전부터 제공한 크게 두가지 방법이 있다.

  1. Thread 클래스로부터 상속받아서 쓰레드를 만드는 방법.
  2. Runnable 인터페이스를 구현하는 방법.

둘다 run() 메서드를 구현하고 그렇게 만들어진 쓰레드를 start()를 함으로서 실행한다.
Runnable한 경우에는 인스턴스를 만든 다음에 쓰레드를 한번 더 다시 호출 함으로써 쓰레드를 생성하므로 한 스텝이 더 추가 되지만 다른 곳에서도 상속받을 수 있으므로 훨씬 더 많이 쓰인다.
Thread를 상속받았을 때는 인스턴스만 만들어서 쓰면되는데, Runnable은 인스턴스로 Thread 를 한번 더 호출해주어야 쓰레드가 돌기 시작한다.
Thread worker = new Thread(task);
worker.start(); -> 쓰레드 시작
worker.join(2000) -> 쓰레드 기다림. 2000 이라는 건 2초 기다리겠다는 의미. (Timeout설정)

 

Java Executor Framework
Thread 와 Runnable 은 자바 초창기에서부터 지원하던 기능. 쓰레드와 관련해서 java.util.concurrent 패키지에 보면 스레드와 관련된 추가된 기능이 있다. Executor 인터페이스를 사용하게 되면 execute 라는 메서드가 있고 실행될 스레드를 주면 start라는 메서드 호출하지 않아도 바로 실행된다. 그와 유사한 Callable 이라는 인터페이스도 제공하는데, call() 이라는 메서드가 일반적인 run()메서드에 해당한다. Excutor 와 Callable  이라는 인터페이스도 추가로 제공해서 사용자가 쉽게 스레드를 만들 수 있도록 도와주었다는 것을 기억하자.


멀티쓰레딩 모델

프로세스 에서 쓰레드를 여러 개 만들어서 사용하는데, 이 쓰레드의 생성부터 스케쥴링, 기타 쓰레드의 관리를 커널에서 직접 관리하면 커널레벨 쓰레드라고 하고, 그렇지 않고 쓰레드 라이브러리(Thread Library)가 커널 레벨에 있는 것이 아니라 유저 레벨에 쓰레드 라이브러리가 존재해서 그것이 1차적으로 관리하고, 스케쥴링이나 CPU 할당은 커널에서 관리하는 2단계로 관리가 이루어지는 유저레벨 쓰레드 도 있다.

  • Many-to One Model: 1차 적인 스케쥴링이 이루어지고 커널에서 스케쥴링이 이루어 지는데 여러 개의 유저쓰레드마다 하나의 커널쓰레드가 주어지는 것.
  • One-to-One Model: 유저 스레드마다 커널 쓰레드가 할당 되어 있다. 커널에서 직접 스케쥴링 하는 것과 똑같다. 커널에서 직접 스레드를 관리하겠다.
  • Many-to-Many Model: 위의 두개를 합친 것. 요즘에는 윈도우, 리눅스, 맥OS 전부 One-to-One Model을 써서 커널에서 직접 스케쥴링 하고 관리한다.  

'운영체제' 카테고리의 다른 글

Thrashing 과 frame 할당  (0) 2021.12.06
파일시스템 수정  (0) 2021.12.05
시스템 콜과 예외처리  (0) 2021.09.26
Context Switching과 Process Tree  (0) 2021.09.23
Process Control Block(PCB)와 스케줄링  (0) 2021.09.23