Chapter 10 Thread Synchronization POSIX.1c 규격은짧은면에서의잠금 (short-term locking) 을위한뮤텍스 (mutex) 라는동기화메커니즘 (synchronization mechanism) 과무한정 (unbounded durat

Similar documents
1장. 유닉스 시스템 프로그래밍 개요

10주차.key

Chap04(Signals and Sessions).PDF

Microsoft PowerPoint - 11_Thread

6주차.key

Microsoft PowerPoint - o6.pptx

Microsoft PowerPoint - o6.pptx

chap 5: Trees

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

Chapter #01 Subject

/chroot/lib/ /chroot/etc/

Infinity(∞) Strategy

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

Microsoft PowerPoint - chap01-C언어개요.pptx

제11장 프로세스와 쓰레드

본 강의에 들어가기 전

좀비프로세스 2

Chapter 4. LISTS

CompareAndSet(CAS) 함수는 address 주소의값이 oldvalue 인지비교해서같으면 newvalue 로 바꾼다. 소프트웨어 lock 을사용하는것이아니고, 하드웨어에서제공하는기능이므로더빠르고 간편하다. X86 에서는 _InterlockedCompareEx

A Hierarchical Approach to Interactive Motion Editing for Human-like Figures

<4D F736F F F696E74202D BDC3B1D7B3CEB0FA20BDC3B1D7B3CE20C3B3B8AE2E707074>

Figure 5.01

Microsoft PowerPoint - chap05-제어문.pptx

슬라이드 1

PowerPoint 프레젠테이션

슬라이드 1

Microsoft PowerPoint - StallingsOS6e-Chap05.pptx

OCW_C언어 기초

Microsoft PowerPoint - 10_Signal

ABC 11장

Microsoft Word - FunctionCall

<4D F736F F F696E74202D203137C0E55FBFACBDC0B9AEC1A6BCD6B7E7BCC72E707074>

Microsoft PowerPoint - chap13-입출력라이브러리.pptx

슬라이드 1

Microsoft PowerPoint - chap03-변수와데이터형.pptx

11장 포인터

3. 다음장에나오는 sigprocmask 함수의설명을참고하여다음프로그램의출력물과그출력물이화면이표시되는시점을예측하세요. ( 힌트 : 각줄이표시되는시점은다음 4 가지중하나. (1) 프로그램수행직후, (2) kill 명령실행직후, (3) 15 #include <signal.

PowerPoint 프레젠테이션

금오공대 컴퓨터공학전공 강의자료

<4D F736F F F696E74202D20B8AEB4AABDBA20BFC0B7F920C3B3B8AEC7CFB1E22E BC8A3C8AF20B8F0B5E55D>

Microsoft PowerPoint - chap06-2pointer.ppt

슬라이드 1

K&R2 Reference Manual 번역본

Chapter ...

Microsoft PowerPoint - SP6장-시그널.ppt [호환 모드]

Chap 6: Graphs

Microsoft PowerPoint - ch09 - 연결형리스트, Stack, Queue와 응용 pm0100

비트와바이트 비트와바이트 비트 (Bit) : 2진수값하나 (0 또는 1) 를저장할수있는최소메모리공간 1비트 2비트 3비트... n비트 2^1 = 2개 2^2 = 4개 2^3 = 8개... 2^n 개 1 바이트는 8 비트 2 2

03_queue

C# Programming Guide - Types

Microsoft PowerPoint - ch07 - 포인터 pm0415

슬라이드 1

Microsoft PowerPoint - 08-Queue.ppt

<322EBCF8C8AF28BFACBDC0B9AEC1A6292E687770>

입학사정관제도

Abstract View of System Components

Microsoft PowerPoint - 08-chap06-Queue.ppt

Lab 4. 실습문제 (Circular singly linked list)_해답.hwp

untitled

Chapter_06

C++ Programming

untitled

제1장 Unix란 무엇인가?

Microsoft PowerPoint - additional01.ppt [호환 모드]

Lab 3. 실습문제 (Single linked list)_해답.hwp

Microsoft PowerPoint - chap06-5 [호환 모드]

UI TASK & KEY EVENT

슬라이드 1

Chapter 4. LISTS

[ 마이크로프로세서 1] 2 주차 3 차시. 포인터와구조체 2 주차 3 차시포인터와구조체 학습목표 1. C 언어에서가장어려운포인터와구조체를설명할수있다. 2. Call By Value 와 Call By Reference 를구분할수있다. 학습내용 1 : 함수 (Functi

<4D F736F F F696E74202D20322DBDC7BDC3B0A320BFEEBFB5C3BCC1A6>

PowerPoint 프레젠테이션

Microsoft PowerPoint - StallingsOS6e-Chap06.ppt [호환 모드]

OCW_C언어 기초

이번장에서학습할내용 동적메모리란? malloc() 와 calloc() 연결리스트 파일을이용하면보다많은데이터를유용하고지속적으로사용및관리할수있습니다. 2

슬라이드 1

PowerPoint 프레젠테이션

JUNIT 실습및발표

PowerPoint 프레젠테이션

UI TASK & KEY EVENT

untitled

vi 사용법

chap7.key

본 강의에 들어가기 전


Microsoft PowerPoint - ch10 - 이진트리, AVL 트리, 트리 응용 pm0600

Poison null byte Excuse the ads! We need some help to keep our site up. List 1 Conditions 2 Exploit plan 2.1 chunksize(p)!= prev_size (next_chunk(p) 3

PowerPoint Presentation

System Programming Lab

adfasdfasfdasfasfadf

Frama-C/JESSIS 사용법 소개

untitled

PowerPoint 프레젠테이션

2009년 상반기 사업계획

Microsoft PowerPoint - chap10-함수의활용.pptx

fprintf(fp, "clf; clear; clc; \n"); fprintf(fp, "x = linspace(0, %d, %d)\n ", L, N); fprintf(fp, "U = [ "); for (i = 0; i <= (N - 1) ; i++) for (j = 0

Chap 6: Graphs

PowerPoint Presentation

JAVA PROGRAMMING 실습 08.다형성

프로그래밍개론및실습 2015 년 2 학기프로그래밍개론및실습과목으로본내용은강의교재인생능출판사, 두근두근 C 언어수업, 천인국지음을발췌수정하였음

Transcription:

Chapter 10 Thread Synchronization POSIX.1c 규격은짧은면에서의잠금 (short-term locking) 을위한뮤텍스 (mutex) 라는동기화메커니즘 (synchronization mechanism) 과무한정 (unbounded duration) 이벤트를기다리게하기위하여 condition variable을제공한다. 여기에, 멀티쓰레드 (Multithread) 프로그램은동기화를위해 POSIX.1b 세마포어를사용할수있다. 쓰레드프로그램에서시그널제어 (signal handling) 는시그널핸들러를전용으로사용하고있던쓰레드가바뀌었을경우바뀔수있는부가적인복잡한문제를제공한다. 이장은생산자-소비자 (producer-consumer) 문제의변형을통해서쓰레드동기화에대하여설명한다. 쓰레드는프로세스의어드레스공간 (space) 안에서생성되며정적변수 (static variable) 이나열려있는파일기술자 (open file descriptor) 등과같은자원들을공유한다. 쓰레드가이런공유된자원을사용할때, 제대로된결과를얻기위하여쓰레드간의상호작용사이에동기화가이루어져야한다. 동기화에는다른두가지종류가있다 - 잠금 (locking) 과대기 (waiting). 잠금 (locking) 은일반적으로짧은기간의자원을잡는것을말하고, 무한으로실행될수있는대기 (waiting) 는어떤이벤트가일어날때까지 condition variable을대기상태로놔두는것을말한다 (block). POSIX.1c는멀티쓰레드프로그램에서이러한동기화방법의두가지를사용할수있도록뮤텍스 (mutex) 와조건변수 (condition variable) 를제공한다. 또, 멀티쓰레드프로그램을위해세마포어도사용이가능하다. 이장은생산자-소비자 (producer-consumer) 문제측면에서쓰레드동기화의개념을설명한다. 생산자쓰레드혹은프로세스는데이터 ( 메시지같은 ) 를생성하고, 생성된데이터를 FIFO 큐에쌓아놓는다. 소비자 (consumer) 쓰레드는큐로부터데이터아이템을제거한다. 큐의크기가지정되었을경우생산자-소비자문제는제한-버퍼문제 (bounded-buffer problem) 로불린다. 생산자-소비자문제의한예로네트워크프린터매니저가있다. 여기서생산자는사용자가생성하는프린트요청이고소비자는프린터가된다. 다른예로는멀티프로세서 (multiprocessor) 시스템에서큐를스케쥴링하는것이나, WAN(wide-area network) 의중계점 (node) 에서메시지를라우팅 (routing) 하는데사용되는네트워크메시지버퍼등이있다. producers queue consumers 그림 10.1: 생산자 - 소비자문제의모형도 그림 10.1은생산자-소비자문제의모형도이다. 생산자-소비자쓰레드는큐를공유하고아이템을집어넣거나제거할때이자원 ( 큐 ) 에락 (lock) 을걸어야한다. 생산자는아이템을집어넣을경우만락을요구하여야하고아이템을집어넣는작업을할동안만락을걸고있어야한다. 비슷하게소비자는큐에서아이템을제거할때만락을걸어야하고제거한아이템을작업하기전에야락을풀어야한다. 게다가제한된크기의큐를사용한다면생산자는더많은데이터를생성하기에앞서사용가능한방 ( 큐 ) 가생길때까지기다려야한다. 잠금 (locking) 은일반적으로짧은기간 (short duration) 에대해사용을하고반면에대기 (waiting) 은긴기간에대해사용을한다. 다음은생산자-소비자를프로그래밍에있어피해야되는몇가지문제를설명한다. 생산자가버퍼에데이터를쓰는동안소비자가아이템을제거한다 (locking). 소비자가없는아이템을가져간다 (waiting). 소비자가이미가져간아이템을가져간다 (locking). 생산자가빈슬롯 ( 큐 ) 가없을때버퍼안에무엇인가를써넣는다 (waiting). 생산자가제거가되지않은아이템에덮어쓰려고한다 (locking).

더욱복잡한생산자-소비자흐름제어 (flow control) 가최고수위 (high-water) 최저수위 (low-water) 점을포함한다 (?). 큐가어떤크기에도달했을때 (high-water mark), 생산자는큐가최저수위점 (low-water mark) 에도달해빈상태가될때까지대기한다 (block). 뮤텍스, condition variable, 세마포어는다양한문제를조절하는데사용될수있다. 다음의세절은이러한기본적인것을가지고생산자와소비자를제어하고, 동기화하는방법을설명한다. 10.1절은 POSIX.1c 뮤텍스락을소개하고, 이것을이용하여생산자-소비자큐에상호배타적으로접근하는것을구현한다. 10.2절은아이템의숫자가정해졌을때생산자-소비자쓰레드를동기화시키는데세마포어를사용하고 10.3절은 POSIX.1c condition variable을설명하고, 이를이용해더욱복잡한제거조건 (requirement) 을가진생산자-소비자동기화를구현한다. 10.4 절에서는쓰레드안에서의시그널핸들링을다루고, 10.5절은쓰레드를이용한프린터서버애플리케이션을설명한다. 10.1 Mutexes 뮤텍스 (mutex) 혹은뮤텍스락 (mutex lock) 은가장간단하고효과적인쓰레드동기화메커니즘이다. 프로그램은임계영역 (critical section) 을보존하기위해뮤텍스를사용하여공유자원에대한배타적인접근을얻을수있다. 이함수를사용하기위하여프로그램은 pthread_mutex_t 타입의변수를선언해야하고, 동기화를위해이변수를사용하기전에초기화하여야한다. 일반적으로뮤텍스변수는프로세스에있는모든쓰레드가접근할수있는정적변수 (static variable) 이다. 뮤텍스는 pthread_mutex_init 함수를이용하거나 static initializer PTHREAD_MUTEX_INITIALIZER를이용해초기화된다. 예제 10.1 다음은뮤텍스 my_lock을기본속성 (default attribute) 으로초기화하는코드이다. my_lock 변수는모든쓰레드에서사용할수있어야한다. #include <stdio.h> #include <string.h> #include <errno.h> pthread_mutex_t my_lock; if (pthread_mutex_init(&my_lock, NULL)) fprintf(stderr,"could not initialize my_lock"); static initializer 방법은뮤텍스락을초기화하는데있어 pthread_mutex_init보다두가지좋은점이있다 - 일반적으로더효과적이고, 어떤쓰레드가실행되기전에정확하게한번수행되는것을보장한다. 예제 10.2 다음은뮤텍스 my_lock을 static initializer를이용해기본속성으로초기화하는코드이다. pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER; 쓰레드는 pthread_mutex_lock을이용해임계영역을보호할수있다. 임계영역이라는의미로남아있기위해서 ( 서로공유하는영역이므로다른쓰레드에서사용할수있어야함 ) 뮤텍스는다른쓰레드나혹은자기쓰레드가그영역을다시얻게하기위해 mutex를풀어줘야한다. 이말은한쓰레드가

pthread_mutex_lock을이용해락을걸고다른쓰레드에서그락을풀어서는 (pthread_mutex_unlock) 안된다는것이다. 한쓰레드가 mutex를이용해 lock을걸었다면그쓰레드는짧은동안만뮤텍스를잡고있어야한다. 쓰레드가예측할수없을정도의기간동안이벤트를기다려야한다면세마포어나 condition variable과같은다른동기화메커니즘을이용해야한다. 예제 10.3 다음은 mutex를이용하여임계영역을보호한다. pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&my_lock); /* critical section */ pthread_mutex_unlock(&my_lock); 잠금 (locking) 과해제 (unlocking) 는어느정도모든쓰레드가임계영역으로들어가기전에뮤텍스를정확하게락을함으로써만상호배타성이얻어진다는점에서어느정도자발적인것이된다.(?) 서로연관성이없는쓰레드들이락을걸지않고임계영역으로들어가는것을막을방법이없다. 어떤오브젝트에대해확실하게배타적으로접근하는방법중하나는잘정의된 (well-defined) 함수를통해서만접근을하고, 그함수안에서잠금 (locking) 을호출하는것이다. 이렇게하면잠금 (locking) 메커니즘은함수를호출하는쓰레드에대해투명하게될것이다.( 잘동작할걸!) bufin bufout 그림 10.2: 생산자 - 소비자문제에대한제한된큐의원형 - 버퍼구현 그림 10.2는 8개의슬롯을가진원형버퍼 (circular buffer) 로서큐를구현한것이고, 세개의데이터아이템이현재들어있는상황이다. bufout값은다음에꺼내져야할데이터아이템의슬롯넘버를가리키고 bufin값은다음채워져야할슬롯의넘버를가리킨다. 만일생산자와소비자쓰레드가상호배타적인방법으로 bufout과 bufin을다루지않으면생산자가아직꺼내지지않은아이템을덮어쓰거나소비자가이미사용되었던 ( 꺼냈던 ) 아이템을가져가게될것이다. 프로그램 10.1은공유된오브젝트로원형버퍼를구현한코드이다. 버퍼에서사용되는데이터구조는그영역이한정된 static 변수를이용한다 (2.1절의 internal linkage). 이코드는프로그램이 get_item과 put_item함수만이용해버퍼에접근할수있도록다른파일에작성되어있다. 프로그램 10.1 mutex lock을이용해보호된원형버퍼 #define BUFSIZE 8 static int buffer[bufsize];

static int bufin = 0; static int bufout = 0; static pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER; /* Get the next item from buffer and put it in *itemp. */ void get_item(int *itemp) pthread_mutex_lock(&buffer_lock); *itemp = buffer[bufout]; bufout = (bufout + 1) % BUFSIZE; pthread_mutex_unlock(&buffer_lock); return; /* Put item into buffer at position bufin and update bufin. */ void put_item(int item) pthread_mutex_lock(&buffer_lock); buffer[bufin] = item; bufin = (bufin + 1) % BUFSIZE; pthread_mutex_unlock(&buffer_lock); return; 프로그램 10.2는생산자쓰레드는원형버퍼에 1부터 100까지그숫자의제곱을쓰고, 소비자는그값을꺼내고꺼낸값을더하는프로그램이다. 버퍼가 mutex lock을이용하여보호가되었다고하지만생산자-소비자간의동기는정확하게이루어지지않는다. 소비자가빈슬롯으로부터데이터를꺼내거나, 생산자가슬롯이꽉찬상태에서덮어쓰기가될것이다. 프로그램 10.2 생산자-소비자문제의부적절한구현. 생산자와소비자쓰레드는동기화되지않았다. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define SUMSIZE 100 int sum = 0; void put_item(int); void get_item(int *); void *producer(void * arg1)

int i; for (i = 1; i <= SUMSIZE; i++) put_item(i*i); void *consumer(void *arg2) int i, myitem; for (i = 1; i <= SUMSIZE; i++) get_item(&myitem); sum += myitem; void main(void) pthread_t prodtid; pthread_t constid; int i, total; /* check value */ total = 0; for (i = 1; i <= SUMSIZE; i++) total += i*i; printf("the actual sum should be %d\n", total); /* create threads */ if (pthread_create(&constid, NULL, consumer, NULL)) fprintf(stderr,"could not create consumer"); else if (pthread_create(&prodtid, NULL, producer, NULL)) fprintf(stderr,"could not create producer"); /* wait for the threads to finish */ pthread_join(prodtid, NULL); pthread_join(constid, NULL); printf("the threads produced the sum %d\n", sum); exit(0); 연습 10.1 프로그램 10.2에서정확하지않은답을만드는시나리오를제공하라. 오직한쓰레드만이한시점에실행이될수있고선점우위의스케쥴링 ( 선점한놈이우선순위가된다?) 이사용되었다.

답 : 오직한쓰레드만이한시점에실행이된다고생각하자. 소비자와생산자는같은우선순위를가지고생성이된다. 그러므로이러한 compute-bound thread에서는선점이없다 (?). 소비자쓰레드가먼저시작하고실행을끝냈다. 그러나생산자가아무것도생산을하지않았기때문에버퍼로부터아무것도소비하지않게된다. 나중에생산자가자기차례가되어우선권을획득하고, 값을생성한다해도아무소용이없게된다. 연습 10.2 프로그램 10.2의코드를수정하기위하여생산자와소비자의생성을서로바꾸고생산자를먼저실행시켰다. 무슨일이일어나겠는가? 답 : 이번에는생산자가실행을끝낸다. 버퍼가 8개의슬롯만을갖고있기때문에소비자가시작되었을때버퍼안에는93부터 100까지의값만남아있게된다. 실행되고있는쓰레드혹은프로세스는 sched_yield를실행함으로써자신이다시스케쥴링리스트의맨처음이될때까지양보를한다. 선점우위 (preemptive priority) 에의해설정된 (scheduled) 쓰레드에서 sched_yield를호출한쓰레드는자기보다우선순위가높은모든쓰레드에게양보를하고, 우선순위가높은모든쓰레드가실행이되고각각이양보하거나대기상태 (block) 가될때까지양보를한다. SYNOPSIS #include <sched.h> int sched_yield(void); POSIX.1b sched_yield함수는성공했을때 0을리턴하고, 실패시 -1을리턴, errno를 set한다. 프로그램 10.2의문제점은생산자와소비자쓰레드에서로양보를하게강요함으로써부분적으로수정될수있다. 예제 10.4 프로그램 10.2에대한다음수정은생산자와소비자쓰레드에게루프를한번돌때마다양보를하도록한다. #include <sched.h> #define SUMSIZE 100 int sum = 0; void put_item(int); void get_item(int *); void *producer(void *arg1) int i; for (i = 1; i <= SUMSIZE; i++)

put_item(i*i); sched_yield(); void *consumer(void *arg2) int i, myitem; for (i = 1; i <= SUMSIZE; i++) get_item(&myitem); sum += myitem; sched_yield(); 예제 10.4에서사용된 sched_yield는생산자와소비자중오직한쓰레드만한시점에수행할수있도록엄격하게교대로동작한다. 프로그램이정확하게동작하게하기위해생산자쓰레드가먼저시작이되어야하고소비자는생산자가먼저시간을양보했을때동작해야한다. 이러한엄격한교대의방법은정해지지않은생산자와소비자의경우문제를풀수없다. sched_yield함수는 POSIX.1b의규격이고, 쓰레드는 POSIX.1c의규격이다. 그러므로, sched_yield를사용할때는 sched_yield를포함한라이브러리를검색해야한다. Sun Solaris2에서는이라이브러리를 libposix4라고부른다. man page를사용하면필요한라이브러리를알수있다. 이절에서, 예제의동작은정확하게얼마나많은쓰레드가동시에동작하느냐와그쓰레드가실행되는순서가무엇이냐에달려있다. 멀티쓰레드 (Multithreaded) 프로그램은실행순서나동시동작의레벨 (level of parallelism: 우선순위?) 에관계없이정확하게수행되어야한다. 생산자-소비자문제에서아이템과슬롯은동기를맞춰서동작해야하고프로그램은쓰레드의실행순서에독립적이어야한다. 다음절은전형적인동기화방법인 POSIX.1b 세마포어의사용에대해서설명한다. 10.2 세마포어들 (Semaphores) 생산자-소비자문제에대한전통적인세마포어솔루션은자원을나타내기위해카운트세마포어를사용한다 ( 예 : 8.3절의 POSIX.1b 세마포어 ). 생산자-소비자문제에서자원이란놈은큐안에있는아이템과 ( 제한된버퍼일경우의 ) 비어있는슬롯을말한다. 이러한자원 (resource type) 의각각은세마포어로서표현이된다. 쓰레드가특정타입의자원을필요로하였을때일치하는형태의세마포어는감소된다 (sem_wait). 쓰레드가자원을풀었을경우적절한세마포어는증가된다 (sem_post). 세마포어변수가 0 밑으로절대떨어지지않기때문에쓰레드는없는자원을사용할수없다. 항상카운팅세마포어는초기에사용이가능한리소스의수에맞게초기화된다. 프로그램 10.2은 POSIX named semaphore를이용해제한된 (bounded: 크기가 ) 생산자-소비자문제에대해정확하게동기를맞춘다. BUFSIZE로초기화된 slots 세마포어는사용이가능한빈슬롯의수를말하는것이고, 생산자에의해감소되고소비자에의해증가된다. 0으로초기화된 items 세마포어는버퍼안의아이템숫자를나타낸다. 소비자에의해감소가되고, 생산자에의해증가된다.

프로그램 10.3 세마포어에의해쓰레드동기화된생산자-소비자프로그램 #include <stdio.h> #include <string.h> #include <errno.h> #include <semaphore.h> #define SUMSIZE 100 #define BUFSIZE 8 int sum = 0; sem_t items; sem_t slots; void put_item(int); void get_item(int *); static void *producer(void *arg1) int i; for (i = 1; i <= SUMSIZE; i++) sem_wait(&slots); put_item(i*i); sem_post(&items); static void *consumer(void *arg2) int i, myitem; for (i = 1; i <= SUMSIZE; i++) sem_wait(&items); get_item(&myitem); sem_post(&slots); sum += myitem;

void main(void) pthread_t prodtid; pthread_t constid; int i, total; /* check value */ total = 0; for (i = 1; i <= SUMSIZE; i++) total += i*i; printf("the checksum is %d\n", total); /* initialize the semaphores */ sem_init(&items, 0, 0); sem_init(&slots, 0, BUFSIZE); /* create threads */ pthread_create(&prodtid, NULL, producer, NULL); pthread_create(&constid, NULL, consumer, NULL); /* wait for the threads to finish */ pthread_join(prodtid, NULL); pthread_join(constid, NULL); printf("the threads produced the sum %d\n", sum); 연습 10.3 BUFSIZE가 8이고생산자가먼저시작하는경우, 프로그램 10.3이하나의프로세서 (CPU) 만있는기계에서동작을한다면무슨일이일어나겠는가?, 선점우위의스케쥴링 (preemptive priority scheduling) 상황에서아이템을어떤순서로실행을해야하는가? 답 : 허가된동시성 (concurrency) 의레벨에달려있다. 오직한쓰레드만이한시점에동작할수있다고생각하자. 생산자가 8개의아이템을생산하고, 대기상태 (block) 에있다. 그후에소비자가처음으로아이템을얻을수있게한다. 소비자는처음 8개의아이템을얻을수있다. 그런다음생산자는그다음의 8개의아이템을생산한다, 그리고... 대기상태 (block) 가바뀌는상황은선점우위의스케쥴링의결과때문이다. 만일대기상태가바뀌지않는다면한쓰레드는제어권을포기하지않게된다. 두쓰레드가한시점에동작할수있도록한다면실행순서는커널스케쥴링메카니즘에달려있다. 연습 10.4 만일 2개의소비자쓰레드가생성이된다면프로그램 10.3은제대로동작할것인가? 답 : 아니. 하나이상의소비자쓰레드가있다면보호되어야할임계영역은 sum을수정하는일이된다. 또한두소비자가 SUMSIZE 아이템에대해작업하길시도할것이고, 생산자는 SUMSIZE 아이템모두를생성한다음빠져나가기때문에결국은대기상태 (block) 가된다.

세마포어는생산자와소비자루프가무한일때나정해진아이템개수만큼루프를돌때생산자-소비자문제를푼다. 생산자나소비자가더복잡한탈출조건에의해제어되는것은쉬운일이아니다. 생산자-소비자문제의변형인 producer-driven에서는생산자는하나이고정해지지않은개수의소비자쓰레드가존재한다. 생산자는어떤개수의아이템을큐에집어넣고빠져나간다 (exit). 소비자는아이템이다소비가되고, 생산자가빠져나갈 (exit) 때까지계속된다. 여기서가능한접근방법은생산자가동작을다끝마쳤을때플래그를설정하는것이다. 프로그램 10.4는이상황을조절하는데왜세마포어를이용하는게어려운가를보여준다. 프로그램 10.4는생산자-소비자문제에있어 producer-driven 탈출조건 (exit condition) 에관한적절하지않은해답을보여준다. 생산자는큐에 SUMSIZE 만큼의아이템을집어넣지만소비자는생산자가다끝날때까지빠져나가지않는다. 버퍼가비어있고, 생산자는 items 세마포어를기다리는상황을생각해보자. 만일생산자가다되었다고결정을하고 producer_done 플래그를설정했다고하면소비자는마지막아이템이생성되기를무한정으로기다리는상태로남아있게된다. 프로그램 10.4 producer-driven 생산자-소비자문제에대한안좋은솔루션 #include <stdio.h> #include <string.h> #include <errno.h> #include <semaphore.h> #define BUFSIZE 8 #define SUMSIZE 100 int producer_done = 0; int sum = 0; sem_t items; sem_t slots; pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER; void put_item(int); void get_item(int *); void *producer(void *arg1) int i; for (i = 1; i <= SUMSIZE; i++) sem_wait(&slots); put_item(i*i); sem_post(&items); pthread_mutex_lock(&my_lock); producer_done = 1;

pthread_mutex_unlock(&my_lock); void *consumer(void *arg2) int myitem; for ( ; ; ) pthread_mutex_lock(&my_lock); if (producer_done) pthread_mutex_unlock(&my_lock); if (sem_trywait(&items)) break; else pthread_mutex_unlock(&my_lock); sem_wait(&items); get_item(&myitem); sem_post(&slots); sum += myitem; 세마포어를이용한방법의문제점은소비자가세마포어를기다릴때가발생한다는점이다. 이렇게되면소비자는 sem_post에의해세마포어가증가되는방법외에대기상태 (block) 가되지않는방법이없다 ( 세마포어가증가하지않으면대기상태가된다.). 동작을끝낸생산자는소비자가아이템을사용할수있다고생각하게만들지않는 sem_post를할수없다 (?: 더이상아이템을사용할수없다고 sem_post를이용해알려줄수없다.). 생산자는 sem_destroy를시도할수있으나불행하게도 POSIX는세마포어가파괴되었을때세마포어를기다리는쓰레드가대기상태가되지않도록 (unblock) 하는것을보장해주지않는다. 연습 10.5 프로그램 10.5는위에서설명한문제를고치기위한프로그램이다. 무엇이잘못되었는가? 답 : 만일생산자가소비자쪽에서남아있는모든아이템을사용하기전에 producer_done 플래그를설정해버린다면, 소비자는마지막아이템을사용하지않고루프를빠져나와버린다. 프로그램 10.5 producer-driven 생산자-소비자문제에대한안좋은두번째솔루션 #include <stdio.h> #include <string.h> #include <errno.h>

#include <semaphore.h> #define BUFSIZE 8 #define MAXCONSUMERS 1 #define SUMSIZE 100 int producer_done = 0; int sum = 0; sem_t items; sem_t slots; pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER; void put_item(int); void get_item(int *); void *producer(void *arg1) int i; for (i = 1; i <= SUMSIZE; i++) sem_wait(&slots); put_item(i*i); sem_post(&items); pthread_mutex_lock(&my_lock); producer_done = 1; for (i = 0; i < MAXCONSUMERS; i++) sem_post(&items); pthread_mutex_unlock(&my_lock); void *consumer(void *arg2) int myitem; for ( ; ; ) sem_wait(&items); pthread_mutex_lock(&my_lock); if (!producer_done) pthread_mutex_unlock(&my_lock); get_item(&myitem); sem_post(&slots); sum += myitem; else

pthread_mutex_unlock(&my_lock); break; Note: 이책이인쇄될때쯤에는 POSIX.1b 세마포어의구현이이루어지지않았다. 이절의예제는 8.6과 8.7절에서설명된함수를구현하여시험하였다. 다음절은 condition variable의측면에서 shutdown 문제에대한해결점을제시한다. 10.3 Condition Variables 세마포어변수 (S) 에대한 sem_wait는 S > 0인상태에서원자적으로 (atomically) 대기를한다. 조건변수 (condition variable) 는정해지지않은속성을가진상태에서원자적으로대기하고, 이벤트의연결 (combination) 이일어날때까지대기상태로남아있게하는아주좋은메카니즘이다. Condition variable은원자적인동작을하는 waiting과 signaling(cond_wait과 cond_signal) 을갖는다. 이 wating과 signaling은세마포어오퍼레이션인 sem_wait와 sem_post와비슷하나같지는않다. 세마포어에있어서속성 S > 0을테스트하는것은 sem_wait가담당하는부분이고, 쓰레드는속성이 false일경우만대기상태 (block) 가된다. 여기서의주안점 (key point) 는테스팅과블록킹이원자적으로이루어진다는것이다. 다음의두단락을천천히그리고자세히읽어봐라. condition variable은처음읽을때는이해하기힘들다. 이두개의단락에있는정보는이절에서여러번반복된다. 한번이해가되면 condition variable은사용하기어렵지않은놈이다. 쓰레드가공유된변수의집합을포함하는어떤속성을기다리는게필요하다고생각하자. 여기서공유된변수의어떤특정쌍이같다고보자. 이변수를사용하는모든코드는임계영역의일부분이기때문에이런공유된변수는뮤텍스락 (mutex lock) 으로보호된다. 부가적인 (addtional) condition variable은쓰레드에이러한변수를포함하는속성을기다리게하는메카니즘을제공한다. 쓰레드가이공유변수중하나를바꿀때마다, 변화가일어났다고 condition variable에시그널을보낸다 (signal). 이시그널은속성이만족된다면보기위하여체크하고있는대기중인쓰레드를깨운다. 대기중인쓰레드가시그널되었을때 ( 신호를받았을때 ) 그속성 ( 변수상태 ) 를테스팅하기전에반드시 mutex를이용해락을걸어야한다. 만일속성이 false라면락을풀고다시대기상태로들어간다 (block). 뮤텍스는쓰레드가대기상태로들어가기전에해제되어야한다. 왜냐하면다른쓰레드가뮤텍스를얻을수있고, 보호가되고이쓴변수를바꿀수있게해야한다. 뮤텍스의해제와블록킹 (blocking) 은이두동작이일어나는사이다른쓰레드가변수를바꾸지않게하기위해원자적 (atomic) 으로동작을해야한다. 시그널은변수를바꿀수있고속성 ( 변수의상태 ) 가아직 true가아니라는 (?) 것을알수있는유일한수단이기때문에대기상태 (blocked) 로빠진쓰레드는시그널이왔을때마다속성을재검사해야한다. 임의의속성 ( 변수상태 ) 을동기화하기위한 condition variable을사용할때다음의단계를이용하라. a) 뮤텍스를얻어라. b) 속성 ( 변수상태 ) 를테스트하라. c) 속성이 true이면어떤일을하고뮤텍스를해제하라. d) 속성이 false이면 cond_wait를호출하고, return 될때b) 로가라.

cond_wait는원자적으로호출한쓰레드를대기상태 (block) 로만들고뮤텍스를풀어준다. 뮤텍스는속성이 true이면명시적으로 ( 즉, 코드를써서 ) 풀어줘야하고, 속성이 false이면묵시적으로 ( 자동으로 ) 풀어준다. condition variable을기다리는쓰레드가대기상태가해제되었을때 (unbloked), 대기상태해제프로세스의일부분으로써자동적으로뮤텍스를얻게된다. 만일필요하다면대기상태해제프로세스는 condition variable를기다리는것부터뮤텍스를기다리는것까지의상태를바꾼다. 항상특정한 condition variable과함께같은뮤텍스를사용해라. 예제 10.5 다음의의사코드는 condition variable, v의사용법과변수 x와 y가같은값을가질때까지쓰레드가대기하도록만드는 v와관련된뮤텍스락 m의사용을보여준다. a: lock_mutex(&m); b: while( x!= y ) c: cond_wait(&v, &m); d: /* do stuff related to x and y */ e: unlock_mutex(&m); 예제 10.5에서속성 ( 변수상태 ) 혹은쓰레드가더진행하기위해참이되어야하는조건은 x==y이다 ( 이것은 while loop안에서나타나는실제적인테스트의부정이다 ). 쓰레드는 x!=y를테스트하기전에뮤텍스, m에락을건다. 이코드를실행한쓰레드는 x가 y와같아지기전까지 d코드를실행하지못한다. 테스트는원하는속성 ( 변수상태 ) 가실제적으로 true가되는것을확실하게알수있게하기위하여딸랑 if문하나를쓰는것보다 while 루프를이용해서수행된다. 쓰레드가단순하게세마포어 S 가깨어나기만을기다릴때, 대기상태가 signal에의해인터럽트되지않는다면 S > 0이되는것이보장된다. condition variable은어떤특정한속성과연결이되어있지않다. 그래서프로그램은속성이 true인지알지못하게된다. 하지만어떤쓰레드가그 condition variable에시그널을준다면알수있게된다. 예제 10.6 다음의사코드는 condition variable에관계된시그널을보내는것 (signaling) 을보여준다. f: lock_mutex(&m); g: x++; h: cond_signal(&v); i: unlock_mutex(&m); 예제 10.6의쓰레드는 x의값을변화시키고, 대기하고있는쓰레드가속성을검사할수있도록 v에시그널을보낸다. 예제 10.5와 10.6의의사코드에서의서로톱니바퀴처럼물려있는상황은코드 ( 구문 ) 가뮤텍스락이되어있는동안실행된다는것을보여준다. 블록킹에대한실제적은메카니즘은약간복잡하다. 만일쓰레드가코드 c의 cond_wait에서대기상태가되었을때락을계속유지한상태에있다면다른프로세스는 condition variable에시그널을보내기위한코드 f에서뮤텍스락을얻을수없을것이고, 쓰레드를대기상태로부터해제할수없게된다. deadlock이발생하게된다. 이문제를해결하기위하여 condition variable의구현은쓰레드가 condition variable을대기상태로만들었을경우 cond_wait 는자동적으로뮤텍스를풀고대기상태로들어가도록보장하고있다. 이러한동작은원자적으로이루어지기때문에다른쓰레드는이쓰레드가대기상태로되기전에공유된변수를수정하거나시그널을수정할수없게된다. 쓰레드가대기상태에서해제되었을때다른것을실행하기전에뮤텍스락을다시획득해야한다. cond_wait의두번째인자는쓰레드의코드가진행되기전에얻어야될것중하나로

뮤텍스락이있다는것을말한다. 연습 10.6 쓰레드 1이예제 10.5의코드를실행하고쓰레드 2가예제 10.6의코드를실행한다고생각해보자. x와 y의초기값은각각 0과 2이다. 두쓰레드가다음과같이코드를섞어서실행을했을때무슨일이일어나겠는가?: a1, b1, c1, f2, g2, h2, i2. 어떤코드가쓰레드 1이다음에실행할코드인가? 답 : 이섞인코드에서쓰레드 1은뮤텍스락 m을얻고 x!=y를테스트한다. x가 0이고 y가 2이기때문에테스트는성공을하고쓰레드 1은 condition variable을대기상태 (block) 로만들고뮤텍스락 m을푼다. 그러면쓰레드 2는뮤텍스락을얻게되고, x의값을증가시키고 condition variable v에시그널을보내고, 뮤텍스 m을푼다. 시그널은쓰레드 1을대기상태에서해제하고코드 i2에서해제된뮤텍스락을다시얻는다. 그리고다시속성 ( 변수상태 ) 를테스트한다. 여전히 x!= y가참이기때문에쓰레드 1은 cond_wait를실행하고다시대기상태 (block) 로들어간다. 쓰레드 1에의해실행이되는다음코드는 b1과 c1이다. 이러한코드는다른쓰레드의코드에의해섞일수있다. 연습 10.7 예제 10.5와 10.6의의사코드로부터다음과같은순서의코드섞기가가능한가? a1, b1, c1, f2, g2, h2, b1, c1, i2 a1, b1, f2, g2, c1, h2 답 : 코드섞기는불가능둘다불가능하다. 처음의코드섞기에서쓰레드 1은 cond_wait로부터깨어나고, 코드진행을계속하기전에뮤텍스락을다시얻어야한다. 쓰레드 2는시그널을보낼때락을건상태이다. 그러므로섞인코드에서두번째 b1전에 i2코드가와야한다. 두번째코드섞기는쓰레드 1이코드 c1에있는 cond_wait를호출하기전까지는뮤텍스를풀지않기때문에불가능하다. 그러므로, 쓰레드 2는 c1에서뮤텍스가풀리기전에 f2 코드에서락을얻을수없다. cond_signal은속성 ( 변수상태 ) 가참이되는것을보장하지않는다. 시그널을보내는것은단지속성에포함된변수가변화되었다는것과, 속성을다시테스트하라는것을대기상태에있는프로세스에게알려주는방법일뿐이다. 예제 10.5의 b코드에있는 while 루프는속성 ( 변수상태 ) 가실질적으로참이라는것을확인하는데필요하다. 만일 while 루프테스트가성공한다면 ( 속성이 false: 루프를테스트한결과가참이라면 ), 쓰레드는 cond_wait를실행함으로써다시대기상태로들어간다. 다른쓰레드가속성에포함된변수 (x와 y) 를수정했을때, 이쓰레드는다시 cond_signal을호출함으로써대기상태에잇는쓰레드를깨울수있다. 10.3.1 Condition Variables for POSIX.1c Threads POSIX.1c 쓰레드는이절의처음에설명한것과비슷한방법의 condition variable 동기화 (synchronization) 방법을제공한다. 정적인초기화 (static initializer) 방법을이용하거나, pthread_cond_init 를호출함으로써 condition variable을초기화할수있다. 만일 attr이 NULL이면 pthread_cond_init는기본적인 (default) condition variable attribute를사용한다.

SYNOPSIS int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); POSIX.1c Condition variable은이변수가항상조건 ( 이것이속성이다 ) 과함께사용되는데서유래가되었다. 쓰레드는속성을테스트하고속성이 false(while문에대해서는참이겠지?) 이면 pthread_cond_wait를호출한다. 다른쓰레드가속성이참이 (while문에대해서는거짓 ) 되도록변수를변화시켰을때, 이쓰레드는 pthread_cond_signal을실행하여대기상태에있는쓰레드를깨운다. 또한 UNIX 시그널의전달과같은다른행동도대기상태에있는쓰레드를 pthread_cond_wait로부터돌아올수있게만들수있다. 이전에대기상태에빠졌던쓰레드는다시속성을검사하고, 속성이여전히 false라면다시 pthread_cond_wait를호출한다. 속성의검사와기다림 (wait: 대기 ) 가원자적으로동작하는것을보장해주기위해함수를호출하는쓰레드는속성을테스트하기전에반드시뮤텍스를얻어야한다. 이러한것은만일쓰레드가 condition variable에서대기상태가된다면 pthread_cond_wait는원자적으로뮤텍스를풀어주고대기상태로들어가도록구현이되었다. 다른쓰레드는이쓰레드가대기상태가되기전에는시그널을발생시킬수없다. 예제 10.7 v를 condition variable로 m을뮤텍스락이라고하자. 다음은 test_condition() 이라고정의된속성이참이면자원에접근할수있도록하는 condition variable의적절한사용법을보여준다. pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t v = PTHREAD_COND_INITIALIZER; pthread_mutex_lock(&m); while (!test_condition()) pthread_cond_wait(&v, &m); /* get resource (make test_condition return false) */ pthread_mutex_unlock(&m); /* do stuff */ pthread_mutex_lock(&m);

/* release resource (make test_condition return true) */ pthread_cond_signal(&v); pthread_mutex_unlock(&m); 예제 10.7에서쓰레드는반드시 pthread_cond_wait를호출하기전에 pthread_cond_wait함수에지정이된뮤텍스 m에락을걸어야한다. test_condition이 false를리턴하면쓰레드는 pthread_cond_wait를실행하고, 뮤텍스 m을풀고, condition variable v를대기상태 (block) 로만든다. 예제 10.7에서쓰레드가 pthread_cond_wait를실행했을때, 이쓰레드는뮤텍스 m을획득한상태이다. 쓰레드는원자적으로대기상태가되고뮤텍스를풀어주고, 다른쓰레드가뮤텍스을얻고, 속성에관계된변수를수정할수있도록만든다. 쓰레드가 pthread_cond_wait로부터성공적으로리턴했을때 ( 시그널을받았을때 ), 쓰레드는뮤텍스를얻게되고, 명시적으로뮤텍스를다시획득하는것없이속성을재시험할수있다. 만일프로그램이어떤속성이참일때만특정 condition variable에시그널을보내더라고, 대기하고있는쓰레드는반드시속성을재시험한다. pthread_cond_wait(pthread_cond_signal 이어야될거같은데?) 는 condition variable을기다리는하나의쓰레드를깨운다. 하나이상의쓰레드가대기하고있다면스케쥴링알고리즘에일치되는방법으로그중하나를선택할수있다. pthread_cond_broadcast는 condition variable을기다리고있는모든쓰레드를깨운다. 대기상태에서깨어난이런쓰데르는 pthread_cond_wait로부터리턴하기전에뮤텍스락을얻기위해서로경쟁한다. 다음은 condition variable을사용하는데필요한약간의규칙을설명한다 : 속성을시험하기전에뮤텍스를얻어라. pthread_cond_wait로부터리턴한후에속성을재시험하라. 왜냐하면 pthread_cond_wait로부터리턴된것은어떤관계없는이벤트때문이거나, 아직속성이참이아닌상태에서변수가변화되었다는나타내는 pthread_cond_signal에의한것일수있기때문이다. 속성이나타나는모든변수를바꾸기전에뮤텍스를얻어라. pthread_cond_signal 혹은 pthread_cond_broadcast를호출하기전에뮤텍스를얻어라. 짧은시간동안만뮤텍스를잡고있어라-일반적으로속성을테스트하는동안만. 뮤텍스를명시적 (pthread_mutex_unlock) 혹은묵시적 (pthread_cond_wait) 방법중하나를이용해서가능한빨리풀어라. 프로그램 10.6은 producer-driven 제한된버퍼문제에대한 condition variable을이용한해결방안을보여준다. 생산자는고정된개수의아이템을생산한후빠져나간다. 소비자는모든아이템을사용하고, 생산자가빠져나갔다는것을알때까지동작한다. 프로그램 10.6 producer-driven방식의제한버퍼문제에대한condition variable을이용한해결방안 #include <stdio.h> #include <stdlib.h> #define SUMSIZE 100 #define BUFSIZE 8 int sum = 0; pthread_cond_t slots = PTHREAD_COND_INITIALIZER;

pthread_cond_t items = PTHREAD_COND_INITIALIZER; pthread_mutex_t slot_lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t item_lock = PTHREAD_MUTEX_INITIALIZER; int nslots = BUFSIZE; int producer_done = 0; int nitems = 0; void get_item(int *itemp); void put_item(int item); void *producer(void * arg1) int i; for (i = 1; i <= SUMSIZE; i++) pthread_mutex_lock(&slot_lock); /* acquire right to a slot */ while (nslots <= 0) pthread_cond_wait (&slots, &slot_lock); nslots--; pthread_mutex_unlock(&slot_lock); put_item(i*i); pthread_mutex_lock(&item_lock); /* release right to an item */ nitems++; pthread_cond_signal(&items); pthread_mutex_unlock(&item_lock); pthread_mutex_lock(&item_lock); producer_done = 1; pthread_cond_broadcast(&items); pthread_mutex_unlock(&item_lock); void *consumer(void *arg2) int myitem; for ( ; ; ) pthread_mutex_lock(&item_lock); /* acquire right to an item */ while ((nitems <=0) &&!producer_done) pthread_cond_wait(&items, &item_lock); if ((nitems <= 0) && producer_done) pthread_mutex_unlock(&item_lock);

break; nitems--; pthread_mutex_unlock(&item_lock); get_item(&myitem); sum += myitem; pthread_mutex_lock(&slot_lock); /* release right to a slot */ nslots++; pthread_cond_signal(&slots); pthread_mutex_unlock(&slot_lock); void main(void) pthread_t prodtid; pthread_t constid; int i, total; /* check value */ total = 0; for (i = 1; i <= SUMSIZE; i++) total += i*i; printf("the checksum is %d\n", total); /* create threads */ pthread_create(&prodtid, NULL, producer, NULL); pthread_create(&constid, NULL, consumer, NULL); /* wait for the threads to finish */ pthread_join(prodtid, NULL); pthread_join(constid, NULL); printf("the threads produced the sum %d\n", sum); exit(0); 프로그램 10.6에서의생산자는속성 nslots>0이참이될때까지대기상태가된다. 이속성을테스트하기위한코드는 while(!(nslots>0) ) 혹은 while(nslots<=0) 과같이작성될수있다. 소비자는속성 (nitems > 0) producer_done(i.e., 소비자는생산자가끝났거나생산한아이템이있을경우에동작을한다 ) 이참이될때까지대기상태로있게된다. 그러므로, 이속성을테스트하기위한코드는 while( (nitems<=0) &&!producer_done ) 과같이작성될수있다. 소비자는생산자가끝났을경우혹은아이템이남아있지않을경우에만끝난다. 10.4 Signal Handling and Threads 시그널을통한쓰레드간의상호작용은몇개의복잡한점을가지고있다. 모든쓰레드는프로세세

의시그널핸들러 (signal handler: 시그널이발생했을때처리되는루틴 ) 를공유하지만각쓰레드는자신만의시그널매스크 (signal mask) 를가질수있다. 게다가, 다른형태 (type) 의시그널은다른방법으로다루어진다. 테이블 10.1은시그널의형태와핸들링방법을종합한것이다. Type Delivery Action 비동기 (Asynchronous) 대기상태에있지않은어떤쓰레드고전달된다. 동기 (Synchronous) 시그널을일으킨쓰레드로전달된다. 직접 (Directed) 지정된 (identified) 쓰레드로전달이된다.(pthread_kill) 테이블 10.1: 쓰레드에서의시그널전달 SIGFPE(floating point exception) 과같은시그널들은그시그널을일으킨쓰레드에동기적으로동작한다 (synchronous, i.e., 시그널들은항상쓰레드가실행하는데같은점에서생성된다 (?)). 동기시그널 (synchronous signal) 은때때로함정 ( 트랩 :trap) 으로불린다. 트랩은자기를생성한쓰레드에의해다뤄진다. 다른시그널들은예측된시간에생성되지도않고, 특정쓰레드와연관성을가지지않는비동기시그널이다 (asynchronous). 만일여러쓰레드가대기상태가아닌 (unblocked) 비동기시그널을갖는다면, 여러쓰레드중하나가시그널을처리하기위하여선택이된다. 시그널은 pthread_kill을이용해특정쓰레드로직접보내질수있다 (directed). 쓰레드는시험하거나, pthread_sigmask함수를이용하여시그널매스크에설정을할수있다. SYNOPSIS #include <signal.h> int pthread_kill(pthread_t thread, int sig); POSIX.1c SYNOPSIS #include <signal.h> int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset); POSIX.1c pthread_sigmask 호출은 sigprocmask와비슷하다. how 파라미터는 SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK중하나를설정할수있다. 시그널핸들러는전프로세스에걸쳐있고 (processwide), 한쓰레드만을가진프로세스 (single-threaded process) 에서와같이 sigaction을호출함으로써설치가된다. 전프로세스에걸쳐있는 (processwide) 시그널핸들러와쓰레드에의해지정된 (thread-specific) 시그널매스크사이의차이점은매우중요하다. 시그널핸들러로들어갔을때시그널에대한토론을다시생각해보다. 이벤트를일으킨시그널은자동적으로대기상태 (block) 가된다. 다중쓰레드 (multithreaded) 어플리케이션에서시그널에대해대기상태가아닌 (unblocked) 인다른쓰레드에전달되는같은타입의다른시그널을막을수없다. 이것은여러

쓰레드가같은시그널핸들러안에서실행되기때문에가능하다. 이것은핸들되고 (handled) 있는시그널에대해명시적으로 sigaction을호출하는것은아주심각한문제이다. 시그널을대기상태가되도록시그널핸들러를설치하는것은시그널핸들러가동작했을때모든쓰레드가대기상태가된시그널을갖도록하는일을일으킨다 ( 모든시그널에대해모든쓰레드가대기상태가된다.). 다중쓰레드프로세스에서시그널을다루는다른방침은시그널을핸들링하도록특정쓰레드를설정하는 (dedicate) 것이다. 메인쓰레드는쓰레드를생성하기전에모든시그널을대기상태 (block) 로만든다. 시그널매스크는자기를생성한쓰레드로부터상속을받기때문에모든쓰레드는대기상태가된시그널을갖게된다. 그러면시그널을핸들링하도록설정된쓰레드는시그널에대해 sigwait를실행한다.(page 391을볼것 ) 두개의접근방법 ( 시그널핸들러대설정된쓰레드 ) 을설명하기위해, 생산자가프로그램이 SIGUSR1을받을때까지버퍼에아이템을넣는제한버퍼문제의변형을생각해보자. 그시점에서생산자는현재아이템을끝마치고빠져나간다. 프로그램은생산자쓰레드를바로죽일 (kill) 수없다. 왜냐하면정해지지않은상태에서뮤텍스를떠나려는쓰레드가뮤텍스락을잡고있기때문이다. 프로그램 10.7은시그널구동형 (signal-driven) 제한버퍼문제 (bounded-buffer problem) 에대한시그널핸들러접근방법을보여준다. 시그널핸들러는생산자 Tm레드가빠져나가도록가르쳐주기위해 producer_shutdown 변수를설정한다. 프로그램 10.7의 producer_shutdown변수가시그널과생산자쓰레드에공유가되기때문에, 생산자쓰레드에서와같은뮤텍스로핸들러안에서도보호된다. 만일 SIGUSR1 시그널이생산자가이락을갖고있는동안발생한다면시그널핸들러는데드락상태가된다 (deadlock). 이런상황을피하기위하여생산자는뮤텍스를획득한상태에서는시그널을막도록 (block signal) 한다. 이해결방안은또한모든다른쓰레드가 SIGUSR1을막는다고 (block) 가정한다. 생산자는조건을기다릴 (condition wait) 동안시그널을막으므로 (block) 슬롯이사용가능하게되고, 다음아이템을생성할때까지 shutdown을감지할수없다. 프로그램 10.7에서함수는테스트할수있도록설치되었다. 메인프로그램은자신의프로세스 ID 를보여주고쓰레드를생성하기전 5초동안잠들어있는다. 이것은사용자가다른창으로움직이거나프로세스에게 ISGUSR1 시그널을보내는명령을실행할수있도록준비하도록한다. 생산자와소비자의쓰레드 ID가쓰레드의시작점을가르쳐주기위해보여진다. 대부분의머신에서이간단한프로그램의제곱합계산은상당히빠르게 (1861 아이템후에 ) 32비트정수를오버플로우 (overflow) 하게된다. 그래서속도를줄이기위해약간의딜레이요소를여러곳에추가하였다. 딜레이의양은 SPIN 상수로조절을하고다른머신에대해서조절할수있도록하였다. 오버플로우가일어났을경우프로그램이없어질때경고가디스플레이된다. 프로그램 10.7에서의소비자는딜레이요소를제외하면프로그램 10.6에서와거의동일하다. 메인프로그램은 SIGUSR1 시그널을쓰레드가생성되기전에블록시킨다 (block). 그러므로블록된시그널을가진시그널매스크가쓰레드에상속된다. 생산자는 slot_lock 뮤텍스를획득한동안블록된시그널을갖는게필요하다. 생산자쓰레드는이시그널을막지않기위해 (to unblock) pthread_sigmask를사용하고, 그래서시그널은오직이쓰레드에서만블록이되지않은상태가된다. 프로그램 10.7 시그널-구동형제한버퍼프로그램에의시그널-핸들러접근방법 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #define SUMSIZE 1861

#define BUFSIZE 8 #define SPIN 10000 int sum = 0; pthread_cond_t slots = PTHREAD_COND_INITIALIZER; pthread_cond_t items = PTHREAD_COND_INITIALIZER; pthread_mutex_t slot_lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t item_lock = PTHREAD_MUTEX_INITIALIZER; int nslots = BUFSIZE; int producer_done = 0; int nitems = 0; int totalproduced = 0; int producer_shutdown = 0; void get_item(int *itemp); void put_item(int item); /* spinit loops to waste time */ void spinit(void) int i; for (i = 0; i < SPIN; i++) ; /* signal handler for shutdown */ void catch_sigusr1(int signo) pthread_mutex_lock(&slot_lock); producer_shutdown = 1; pthread_mutex_unlock(&slot_lock); void *producer(void * arg1) int i; sigset_t intmask; sigemptyset(&intmask); sigaddset(&intmask, SIGUSR1); for (i = 1; ; i++) spinit(); pthread_sigmask(sig_block, &intmask, NULL); pthread_mutex_lock(&slot_lock); /* acquire right to a slot */ spinit();

while ((nslots <= 0) && (!producer_shutdown)) pthread_cond_wait (&slots, &slot_lock); if (producer_shutdown) pthread_mutex_unlock(&slot_lock); break; nslots--; pthread_mutex_unlock(&slot_lock); pthread_sigmask(sig_unblock, &intmask, NULL); spinit(); put_item(i*i); pthread_mutex_lock(&item_lock); /* release right to an item */ nitems++; totalproduced++; pthread_cond_signal(&items); pthread_mutex_unlock(&item_lock); spinit(); pthread_mutex_lock(&item_lock); producer_done = 1; pthread_cond_broadcast(&items); pthread_mutex_unlock(&item_lock); void *consumer(void *arg2) int myitem; for ( ; ; ) pthread_mutex_lock(&item_lock); /* acquire right to an item */ while ((nitems <= 0) &&!producer_done) pthread_cond_wait(&items, &item_lock); if ((nitems <= 0) && producer_done) pthread_mutex_unlock(&item_lock); break; nitems--; pthread_mutex_unlock(&item_lock); spinit(); get_item(&myitem); spinit(); sum += myitem;

pthread_mutex_lock(&slot_lock); /* release right to a slot */ nslots++; pthread_cond_signal(&slots); pthread_mutex_unlock(&slot_lock); void main(void) pthread_t prodtid; pthread_t constid; double total; double tp; struct sigaction act; sigset_t set; fprintf(stderr, "Process ID is %ld\n", (long)getpid()); sleep(5); /* setup signal handler and block */ act.sa_handler = catch_sigusr1; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(sigusr1, &act, NULL); sigemptyset(&set); sigaddset(&set,sigusr1); sigprocmask(sig_block, &set, NULL); /* create threads */ pthread_create(&prodtid, NULL, producer, NULL); pthread_create(&constid, NULL, consumer, NULL); fprintf(stderr,"producer ID = %d, Consumer ID = %d\n", (int)prodtid, (int)constid); /* wait for the threads to finish */ pthread_join(prodtid, NULL); pthread_join(constid, NULL); printf("the threads produced the sum %d\n", sum); /* show correct value */ total = 0.0; tp = (double) totalproduced; total = tp*(tp+1)*(2*tp+1)/6.0; if (tp > SUMSIZE) fprintf(stderr,"*** Overflow occurs for more than %d items\n", SUMSIZE); printf("the checksum for %4d items is %1.0f\n",

totalproduced, total); exit(0); pthread_mutex_lock과 pthread_mutex_unlock은 async-signal safe한함수이다. 이뜻은이함수들은시그널핸들러에서호출될수있다는말이다. 그러나모든쓰레드는뮤텍스락을획득하거나프로그램이데드락이되기전에시그널을반드시블록해야한다. 시그널을핸들링하는데다른접근방법은설정된쓰레드를사용하는것이다. 설정된쓰레드를제외한모든쓰레드는질문하는데있어 ( 웬질문?) 시그널을블록한다. 설정된쓰레드는지정된시그널에대해 sigwait를건다. SYNOPSIS #include <signal.h> int sigwait(sigset_t *sigmask, int *signo); POSIX.1c sigwait함수는쓰레드가 *sigmask에의해지정된모든시그널들을받을때까지블록을한다. *signo 값은 sigwait로부터리턴을일으키는시그널의숫자를말한다. sigwait 함수는성공했을때 0을그렇지않을경우 -1을리턴한다. 에러가발생했을경우 errno를설정한다. sigwait와 sigsuspend 함수의차이점을알아두어라. 이두함수는첫번째인자로시그널셋에대한포인터를갖는다 (sigset_t *). sigsuspend에서이셋은새로운시그널매스크를갖고있고, 그래서이 set 에있지않은시그널은 sigsuspend를리턴할수있게만드는것중하나가된다 ( suspend하도록설정이되지않은시그널때문에 sigsuspend함수라리턴이된다 (?)). sigwait에대해이파라미터는기다려야할시그널의셋을가지고있다. 그래서셋안에있는시그널은 sigwait를리턴할수있게만드는것중하나가된다 ( 셋에설정이된시그널때문에 sigwait가리턴할수있다.). 이두가지경우에프로그램은호출이전에관심있는시그널을블록할수있다. sigsuspend와함께사용할경우시그널이프로세스에전달이되고 sigsuspend는시그널핸들러로부터평범하게리턴이된후에만리턴이된다. sigwait를사용할경우대기 (pending) 하고있는시그널은그시그널이전달되지않고제거되고, 어떤시그널핸들러도필요하지않다. 프로그램 10.8은 SIGUSR1 시그널을기다리는분리된쓰레드를사용하는시그널구동형제한버퍼문제에대한해결방안을보여준다. 설정된시그널접근방법은여러관점에서시그널핸들러접근방법보다간단하다. 쓰레드는 async-signal safe 함수를호출하는데제한을받지않고생산자쓰레드는슬롯이비는것을기다리는동안에도빠져나가도록정보를받을수있다. 프로그램 10.8에서메인프로그램은모든쓰레드를생성하기전에 SIGUSR1 시그널을블록한다. 생성된시그널은시그널매스크를상속받기때문에모든쓰레드는블록된 SIGUSR1을갖는다. 메인프로그램은시그널을핸들하도록설정된쓰레드를생성한다. 프로그램 10.8은오직선점우위스케쥴링 (preemptive priority scheduling) 만을지원하는시스템에서테스트되었다. 시그널을기다리는쓰레드, sigusr1_thread는 sigusr1_thread가시그널을잡을수있도록보장하기위하여디폴트보다높은우선순위를가진다. 프로그램 10.8이먼저 sigusr1_thread의우선순위를높이지않은같은시스템에서실행이되었을때프로그램은여전히정확하게동작하지만때때로시그널이생성되었을때와그시그널이인지되었을때사이에몇초의시간이걸리는경우가있다. 우선순위는변수에우선순위를넣고, 그변수에있는우선순위를증가시키고속성에우선순위를재설정하고그다음에이새로운설정을가지고쓰

레드를생성할수있게해주는 pthread_attr_init를사용해쓰레드속성 (attribute) 를초기화함으로써설정될수있다. 만일 round-robin 스케쥴링정책이가능하다면모든쓰레드는같은우선순위를갖게된다. 시그널핸들링쓰레드로설정된쓰레드, sigusr1_thread는자기우선순위가제대로설정이되었나확인하기위하여우선순위를보여주고, SIGUSR1 시그널에대해 sigwait함수를호출한다. sigwait가이러한대기상태 (pending) 로부터시그널을제거해주기때문에시그널핸들러가필요치않다. 시그널은항상블록이되어있기때문에디폴트 SIGUSR1 핸들러 ( 프로세스를죽이는역할을하는 ) 로는절대들어가지않는다. 프로그램 10.8은다른윈도우로부터 kill 명령을이용해프로세스에게 SIGUSR1을보냄으로써테스트할수있도록설정이되었다. 프로그램은시작할때프로세스 ID를보여주고, 프로그램 10.7에서와같이계산을시작한다. 만일프로그램이없어진다면프로그램은빠른시스템에서몇초안에 1861 아이템후 sum을하는데사용되는정수가오버플로우난것이다. 함수 spinit는여러곳에서프로그램을느리게한다. 파라미터 SPIN은얼마나오래그냥회전 (spin) 할것인지결정한다. 이값은타겟시스템에맞춰서조절되어야한다. 이러한잠자는형태 (sleep-type) 의함수는프로그램을더포팅하기쉽도록하지만쓰레드의스케쥴링에영향을미칠수있기때문에사용하지는않는다. 프로그램 10.8 설정된쓰레드를이용한시그널-구동형제한버퍼문제해결방안. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sched.h> #define SUMSIZE 1861 #define BUFSIZE 8 #define SPIN 1000000 int sum = 0; pthread_cond_t slots = PTHREAD_COND_INITIALIZER; pthread_cond_t items = PTHREAD_COND_INITIALIZER; pthread_mutex_t slot_lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t item_lock = PTHREAD_MUTEX_INITIALIZER; int nslots = BUFSIZE; int producer_done = 0; int nitems = 0; int totalproduced = 0; int producer_shutdown = 0; void get_item(int *itemp); void put_item(int item); void spinit(void) int i;

for (i = 0; i < SPIN; i++) ; void *sigusr1_thread(void *arg) sigset_t intmask; struct sched_param param; int policy; int sig; sigemptyset(&intmask); sigaddset(&intmask, SIGUSR1); pthread_getschedparam(pthread_self(), &policy, &param); fprintf(stderr, "sigusr1_thread entered with policy %d and priority %d\n", policy, param.sched_priority); sigwait(&intmask,&sig); fprintf(stderr, "sigusr1_thread returned from sigwait\n"); pthread_mutex_lock(&slot_lock); producer_shutdown = 1; pthread_cond_broadcast(&slots); pthread_mutex_unlock(&slot_lock); void *producer(void *arg1) int i; for (i = 1; ; i++) spinit(); pthread_mutex_lock(&slot_lock); /* acquire right to a slot */ while ((nslots <= 0) && (!producer_shutdown)) pthread_cond_wait (&slots, &slot_lock); if (producer_shutdown) pthread_mutex_unlock(&slot_lock); break; nslots--; pthread_mutex_unlock(&slot_lock); spinit();

put_item(i*i); pthread_mutex_lock(&item_lock); /* release right to an item */ nitems++; pthread_cond_signal(&items); pthread_mutex_unlock(&item_lock); spinit(); totalproduced = i; pthread_mutex_lock(&item_lock); producer_done = 1; pthread_cond_broadcast(&items); pthread_mutex_unlock(&item_lock); void *consumer(void *arg2) int myitem; for ( ; ; ) pthread_mutex_lock(&item_lock); /* acquire right to an item */ while ((nitems <=0) &&!producer_done) pthread_cond_wait(&items, &item_lock); if ((nitems <= 0) && producer_done) pthread_mutex_unlock(&item_lock); break; nitems--; pthread_mutex_unlock(&item_lock); get_item(&myitem); sum += myitem; pthread_mutex_lock(&slot_lock); /* release right to a slot */ nslots++; pthread_cond_signal(&slots); pthread_mutex_unlock(&slot_lock); void main(void) pthread_t prodtid; pthread_t constid; pthread_t sighandid;

double total; double tp; sigset_t set; pthread_attr_t high_prio_attr; struct sched_param param; fprintf(stderr, "Process ID is %ld\n", (long)getpid()); /* block the signal */ sigemptyset(&set); sigaddset(&set, SIGUSR1); sigprocmask(sig_block, &set, NULL); fprintf(stderr, "Signal blocked\n"); /* create threads */ pthread_attr_init(&high_prio_attr); pthread_attr_getschedparam(&high_prio_attr, &param); param.sched_priority++; pthread_attr_setschedparam(&high_prio_attr, &param); pthread_create(&sighandid, &high_prio_attr, sigusr1_thread, NULL); pthread_create(&prodtid, NULL, producer, NULL); pthread_create(&constid, NULL, consumer, NULL); /* wait for the threads to finish */ pthread_join(prodtid, NULL); pthread_join(constid, NULL); printf("the threads produced the sum %d\n", sum); /* show correct value */ tp = (double) totalproduced; total = tp*(tp + 1)*(2*tp + 1)/6.0; if (tp > SUMSIZE) fprintf(stderr, "*** Overflow occurs for more than %d items\n", SUMSIZE); printf("the checksum for %4d items is %1.0f\n", totalproduced, total); exit(0);