5. Concurrent Programming

나는컴공생·2024년 6월 2일

System Programming

목록 보기
1/2

동시성 프로그래밍이 왜 어려운가?

1. Races

  • 결과값이 시스템의 임의적인 스케쥴링에 의해 결정되기 때문에 발생한다.

2. Deadlock

  • resource를 어떻게 할당하는지
  • 적절하지 않은 resource 할당이 progress가 더이상 진행할 수 없는 상태로 만든다.

3. Livelock/Starvation/Fairness

  • 외부 event 혹은 system 스케쥴링들이 progress의 다른 일들을 하지 못하게 만든다.
  • Livelock 수행은 하나, 더이상 진행 안함.

Echo Server, Client 예시


  • Server: RIO read_lineb가 EOF가 아닐때까지 시도한다.
    언제 EOF? Client가 close 할때까지
    문제발생: server는 지금 해당 client가 끝내기만을 기다려야 한다. 다른 client와 통신할 수 없다.

Iterative Servers


Client1과 connection이 이루어지고 server는 client1의 write 즉, 자신이 read할 수 있을 때까지 기다린다.
이때 client2로부터 connection request는 server의 listen에 queued된다. 그러나 server에게 client2가 write할 수는 있지만, 바로 응답을 받지 못한다.
client1가 close되고 나서야, client2를 accept, read 하고 response를 write한다.
1. Connect returns ok

  • connection이 accept 되지 않아도,
  • servers의 TCP mananger가 요청을 queues 한다.
  • **TCP listen backlog -> listen(fd, backlog=queue할 수 있는 개수)
  1. rio_writen returns ok
  • server TCP manager가 input data를 buffer에 저장한다.
  • server가 바로 read 안하지만, 버퍼에는 저장되어 있다.
    질문: rio_writen은 unbuffered 함수 아님??
  1. rio_readlineb call
  • server가 아직 write한게 없기 때문에 여기서 block된다.

iterative server의 문제


문제발생) client가 종료되지 않으면(ctrl+d를 누르지 않음),
server는 client1으로부터 data가 오기를 기다린다.(call read 상태)
-> client2가 server로부터 기다린다. (call read 상태)

동시성 server 어떻게 만들까?

  1. Process-based
  • kernel이 여러 multiple logical flow 자동적으로 끼워넣는다.
  • 각 proc가 각자의 사적 주소 공간 가짐(전체 공유 ㄴㄴ)
  • client connection request 있을 때마다 새로운 process fork,
    각 proc가 한명의 client 담당
  1. Event-based
  • programmer가 여러 multiple logical flow 수동으로 끼워넣는다.
  • 모든 flow가 같은 주소 공간 가짐 (전체 공유)
  • I/O multiplexing(다중화)
  • kernel에게 여러 식별자 주고, kernel이 여러 식별자들을 듣고 있다가 그중 하나에서 event 발생했다고 알려준다.
  1. Thread-based
  • kernel이 여러 multiple logical flow 자동적으로 끼워넣는다.
  • 모든 flow가 같은 주소 공간 가짐 (전체 공유)
  • Process 기반과 event 기반 합쳐짐
  • 성능 좋아

1: Process-based Servers

how work?


Server는 Client1의 connection accept 후, fork한다. child process에서 read를 call하고, client로부터 data를 기다린다. 따라서, client1이 data를 보내지 않아 block 시켜도 server process가 아닌 child process가 block 되기 때문에 server가 block 되지 않고,client의 connection을 accept할 수 있다.

  • 각 cleint는 독립적인 child process에 의해 다뤄진다.
  • client들 간 state가 공유되지 않는다.
  • 부모와 자식은 listenfd와 connfd의 복사본을 가진다.
    - 부모는 사용하지 않는 connfd를 닫아야한다.
    - child는 사용하지 않는 listenfd를 닫아야한다.
    (그리고 다 통신 후에는 connfd도 닫아야한다.)
    - child reaping handler 필요.
  • fork()==0일때(자식) : Close(listenfd) 필요.
  • (부모) : Close(connfd) 필요.

Issues with Process-based Servers

1. listening server process는 zombie process reap 해줘야한다.(memory leak)
2. parent process는 자신 connfd의 복사본을 꼭 close해야한다.
  • 왜냐하면 kernel은 socket과 openfile들의 reference count를 계속 참조한다.

  • fork를 하고 난 뒤, 부모와 자식은 같은 file table을 공유하므로 refcnt(connfd) =2로 변한다.

  • refcnt(connfd) = 0이 될 때까지 연결이 끊어지지 않는다.

  • 따라서 꼭 close 해줘야 한다.

장단점

장점

  • 여러 connection을 동시에 다룰 수 있다.
  • clean sharing model
    • descriptor 공유 x
    • file tables 공유 O
    • global variables 공유 x
  • simple and straightforward

단점

  • process를 관리하기 위한 추가적인 오버헤드가 발생한다.
  • 프로세스간 데이터를 공유하지 못한다.
    • IPC(InterProcessCommunication) 메카니즘을 필요로 한다.
      • FIFO's(pipes) , System V shared memory and semaphores

2. I/O Multiplexing

왜 I/O multiplexing을 사용하는가?

  • echo server에 keyboard 입력과 network의 connection 을 함께 받고 싶다.
  • single process 사용
  • kernel이 multiple descriptor들을 계속 check하고 event발생을 return한다.
  • server는 system call을 보내 kernel이 check 해야하는 desriptor들을 알려준다. 그리고 kerenl의 return이 올 때까지 block 된다.
  • kernel에게 return을 받으면 해당 event을 handle 하고 필요시 checkfd list를 다시 업데이트하여 syscall을 한다.

how 구현? : I/O multiplexing using select

select 함수

  • kernel에게 process를 suspend 시기키고, 최소 한 개 이상의 I/O events들이 발생했을 때만 application으로 control을 return하도록 한다.
  • read fd set은 bitmap으로 fd 0, 1, 2는 이미 std로 지정되어있으므로 3부터 시작한다.
    ex) 3(sockfd), 2(stderr), 1(stdout), 0(stdin)
  • FD_SET : 관찰해야하는 fd 번호 set 시키기
  • FD_CLR : 관찰 필요 없는 fd 번호 clear 시키기
  • FD_ISSET : kernel이 return 하고 나면, 어떤 I/O에서 event가 발생한 건지 체크하는 용

select.c

  • select 함수가 전역변수인 fd_set을 수정하기 때문에 이를 방지하기 위해서 ready_set을 복사한뒤 ready_set을 select의 인자로 전달한다.

issues with select.c : Blocking problem

: 사용자가 입력한 내용을 서버가 즉시 응답하지 않고 대기하는 문제
-> serverloop동안 하나의 text line을 각 시간에 echoing하기

how 구현? : I/O multiplexed event processing

  • state machine : states, input events, transitions
  • concurrent event- driven echo server 을 state machine으로 표현
  • connfd array
  • repeat: select return 값이 connfd나 listenfd 중 어떤 것이 pending 되었는지 확인
    - listenfd인경우, connfd에 새로 추가
    - connfd이 경우, pending input들을 service하기





장단점

장점

  • 한개의 logical control flow와 주소공간을 가진다.
  • debugger를 single단계를 가진다.
  • 단일 프로세스를 사용하므로 쓰레드나 프로세스 관리 오버헤드가 안 든다.

단점

  • 쓰레드나 프로세스 기반 디자인보다 복잡하다
  • 정밀한 동시성을 만들기 어렵다.
    (왜냐하면 client에게 한줄씩 보내기 때문에 http reques header들을 잘 다뤄줘야 한다.)
  • 멀티코어의 이점을 사용하지 못한다.(single thread of control)

3. Thread-based Servers

Thread란?

  • process의 control flow
  • 각 thread는 최소 한개의 context(PC, SP, stack)
  • 각 thread는 각각 shcheduled 된다.(thread는 schedule할 수 있는 단위)
  • 같은 process 내 multiple thread는 code, address space(data), operating resources(files) 들을 공유한다.

Traditional View of a Process

: Process = Process Context + code, data, and stack

vs
Process = thread + code, data, and kernel context
(program context = thread context)

A process with Multiple Threads

  • own logical control flow
  • own stack for local variables
    • but, not protected from other threads(접근가능)
  • own thread ID(TID)
  • same code, data, and kernel context(VM structure, descriptor table, brk pointer)

Threads vs Process

공통점

  • own logical control flow
  • each can run concurrently with others
  • each is context switched

다른점

  • Threads는 모든 코드와 데이터를 공유(local stack제외)
  • thread는 process보다 덜 비싸다.
    • process control(reaping, creating)이 thread control보다 두배 더 비싸다
    • linux numbers:
      • 20k cycles to create reap a process
      • 10k cycles to create and reap a thread

Logical View of Threads

-> 한 프로세스와 연관된 쓰레드끼리는 동등관계 (pool of peers)

왜 Thread가 유용한가? (중요)

case 1: Single Core Processor

  • I/O bound workload(I/O 를 CPU 보다 더 빈번하게 사용하는 경우) CPU가 I/O bound에서는 작동하지 못하고 기다려줘야하는데 thread를 사용하면 기다리는 동안 다른 thread를 실행시켜서 concurrent하게 돌려준다.
    (I/O bound workload가 항상 존재하는 건 아니지만, 우리가 사용하는 대부분 program에서는 존재)
  • time slicing을 통해 parallelism을 비슷하게 하다.
  • achieve concurrency(parallel한것처럼)

case 2: Multi Core Processor

  • true parallelism 을 가질 수 있다.(core의 개수 = thread 개수)
  • achieve parallelism

Posix Threads(Pthreads) Interface

[Posix: Portable한 Operating System Interface(Unix, Linux 위한 API)
(리눅스 위에서 돌릴 수 있는 API)]

Pthread 사용 예시("hello,world" program)


-> master thread(main())과 peer thread(thread()) 동시에 돌려줄 수 있다.

Thread-based Server Execution Model

  • 각 client => 각 peer thread
  • main과 peer thread는 모든 process state를 공유한다.(tid 제외)
  • 각 thread는 local 변수들을 위한 분리된 stack을 갖는다.

Thread-based Concurrent Echo Server ver.1


Pthread_create(&tid, NULL, thread, &connfd);
master thread stackd의 주소이자, connfd가 저장된 stack의 주소를 넘겨준다.
-> race 문제 발생: connfd가 공유되므로, 잘못 복사된다.

race문제 해결: 동적할당하여, 서로 다른 공간에 connfd 저장


main에서 connfd를 하나의 변수에 계속 덮어쓰기 하는것이 아니라 동적할당하여 하나의 공간에 할당하고 해당 주솟값을 직접 thread에 전달함으로써 thread 함수는 동적할당 된 곳을 접근한다. 그리고 thread routine내에서 free 한다.(main: int* connfd <- int connfd)

Thread-based Concurrent Echo Server ver.2

  • Pass a copy of connection file descriptor (conndp) to the client

  • thread detached mode에서 run(using pthread detach)
    • 다른 thread와 독립적으로 run
    • 종료될 때마다 kernel에 의해 자동적으로 reap 된다(main이 threadjoin 할 필요 없어짐)
    • avoid memroy leak
      • thread는 joinable(default) 이거나 detached 이다.
      • joinable : 다른 thread에 의해 reaped 되거나 killed 될 수 있다.
        • pthread_join에 의해 reap되어야함.
      • detached : 다른 thread에 의해 reaped 되거나 killed 될 수 없다.
        • automatically reaped by terntal (on termination)
      • use pthread_detach(pthread_self()) to make detached
  • thread routine 내에서 free(vargp = main thread stack의 connfd주소) 직접 free해주기
  • close connfd 해주기(중요)

장단점

장점

  • thread 간 data 공유 쉽다
  • process보다 efficient 하다.

단점

  • unintentional sharing 이 발생할 수 있다.
    - private인지 shared 되는지 알기 어렵다
    - testing으로 race problem 찾기 어렵다

0개의 댓글