multi thread

35
Multi Thread NHN NEXT 남현욱

Upload: nam-hyeonuk

Post on 03-Aug-2015

116 views

Category:

Software


3 download

TRANSCRIPT

Page 1: Multi thread

Multi Thread

NHN NEXT남현욱

Page 2: Multi thread

01

Lock

Page 3: Multi thread

01 Lock

•Deadlock프로세스나 스레드가 결코 일어날 수 없는 특정 이벤트를 기다리는 것.

두 개 이상의 프로세스가 각각 어떤 자원을 소유하고 있으면서 다른 프로세스가 소유한 자원을

추가로 요청하여 기다리고 있는 상황

P1 P2

* 원 = 프로세스(P = process), 사각형 = 자원 (R = Resource)

R1

R2

보유

보유요청

요청 이런 식으로 자원의

요청/보유 관계가

맞물려서 멈춰버리

는 상황을 말함

Page 4: Multi thread

01 Lock

•DeadlockDeadlock은 다음 4가지 조건이 모두 만족되었을 때 발생할 가능성이 생긴다.

상호배제(Mutualexclusion)

최소한 한 개의 자원이 비공유 모드로 점유. 한 순간에 오직 하나의 프로세스만 특정 자원을 사용

할 수 있으며, 다른 프로세스는 대기해야 하는 경우.

보유후대기(holdandwait)

한 개 이상의 자원을 점유한 상태에서 다른 프로세스가 현재 점유하고 있는 자원을 쓰려고 요청

을 함으로써 대기상태에 빠지는 것

비선점(Nopreemption)

남이 쓰고 있는 자원을 마음대로 선점할 수 없다. 자원의 반환(release)은 반드시 자원을 쓰는

프로세스가 자발적으로 반납할 때만 가능.

순환대기(Circularwait)

n 개의 프로세스들 사이에서 자원에 대한 점유 - 대기 상황이 순환적으로 발생하는 것.

Page 5: Multi thread

01 Lock

•DeadlockDeadlock이 발생한 상황에서 Deadlock이 발생했음을 탐지하는 방법은?

동일한 자원이 하나만 존재하는 경우 자원 점유 - 요청 그래프를 만들어서 그래프에서의 싸이클

존재 여부를 통해 Deadlock이 발생했음을 감지할 수 있다.

동일한 자원이 하나 이상 존재하는 경우 Banker’s algorithm과 유사하게 현재 자원 할당

상태를 기반으로 Deadlock이 발생했는지 아닌지만 검사한다(이미 자원이 할당된 상태에서

자원을 할당, 해제하는 과정을 통해 모든 프로세스의 수행 시퀀스가 나올 수 있는지 아닌지를

판단함. 이미 할당된 상태를 기준으로 한다는 점 말고는 Banker’s algorithm과 별로 다를 게

없다).

Page 6: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

Page 7: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();

Page 8: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();

보유

R2.lock();

Page 9: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();R2.lock();

보유

R2.lock();

요청

우선순위가 R1 > R2 이므로 적법한 요청이다.

Page 10: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();R2.lock();

보유

R2.lock();R1.lock();

요청

요청

우선순위가 R1 < R2 이므로 이 순서로 lock을 거는 건 허용되지 않는다.

Page 11: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();R2.lock();

반납

R2.lock();R2.unlock();

요청

Page 12: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();R2.lock();

R2.lock();R2.unlock();R1.lock();

보유

요청

이제 요청 가능.

Page 13: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

보유

R1.lock();R2.lock();R2.unlock();

R2.lock();R2.unlock();R1.lock();

반납

요청

Page 14: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

반납

R1.lock();R2.lock();R2.unlock();R1.unlock();

R2.lock();R2.unlock();R1.lock();

요청

Page 15: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

R1.lock();R2.lock();R2.unlock();R1.unlock();

R2.lock();R2.unlock();R1.lock();

보유

Page 16: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

R1.lock();R2.lock();R2.unlock();R1.unlock();

R2.lock();R2.unlock();R1.lock();R1.unlock();

반납

Page 17: Multi thread

01 Lock

•Lockhierarchy각 스레드가 lock을 얻을 때 lock 간의 우선순위를 둬서 우선순위 순서대로 lock을 걸고 풀도록

강제하는 것. 이렇게 하면 각 자원을 얻는 순서가 보장이 되므로 서로 서로 자원을 물고 있는

형태가 발생하지 않아 deadlock을 회피할 수 있다.

P1 P2

R1

R2

우선순위 : high

우선순위 : low

우선순위 low에 lock을 건 채로 우

선 순위 high에 lock을 걸 수 없다

R1.lock();R2.lock();R2.unlock();R1.unlock();

R2.lock();R2.unlock();R1.lock();R1.unlock();

대충 이런 방식으로 lock에 우선순위를 둬서 deadlock을 회피할 수 있다

Page 18: Multi thread

01 Lock

•READ-WRITELOCKRead / Write 할 때 매 번 전체에 lock을 걸어버리면 효율이 굉장히 떨어진다. read는 얼마든지

중첩해서 접근이 가능하게 하고, write할 때만 한 번에 하나만 접근 가능하도록 만들어주는 lock

이 read-write lock. 읽기는 값을 변경하지 않으므로 한 번에 얼마든지 많은 스레드가 접근해도

상관 없다는 것에 기반한 lock이다.

Page 19: Multi thread

01 Lock

•READ-WRITELOCK(교수님 구현 참조)class SWLock{public: void EnterWriteLock(): void LeaveWriteLock();

void EnterReadLock(); void LeaveReadLock();

private: enum LockFlag { LF_WRITE_MASK = 0x7FF00000, LF_WRITE_FLAG = 0x00100000, LF_READ_MASK = 0x000FFFFF //하위 20비트 read lock 용 플래그 }; volatile long mLockFlag; //현재 lock 상태 파악 용 플래그};

Page 20: Multi thread

01 Lock

•READ-WRITELOCKvoid SWLock::EnterReadLock(){ while(true) { //write lock이 걸린 경우. 락이 풀릴 때 까지 기다린다. while (mLockFlag & LF_WRITE_MASK) YieldProcessor();

//read lock flag를 올리고, 혹시 write lock이 먼저 걸렸는지 체크. //write lock이 먼저 걸린 게 아니라면 read lock을 획득한 것이므로 return 한다. if((InterlockedIncrement(&mLockFlag) & LF_WRITE_MASK) == 0) { return; }

//read lock을 획득 실패. 올렸던 플래그 원상복구 시키고 다시 기다림. InterlockedDecrement(&mLockFlag); }}

Page 21: Multi thread

01 Lock

•READ-WRITELOCKvoid SWLock::EnterWriteLock(){ while(true) { //write lock이 걸린 경우. 락이 풀릴 때 까지 기다린다. while (mLockFlag & LF_WRITE_MASK) YieldProcessor(); //동시에 여러 write 들어가는 것 방지 if((InterlockedAdd(&mLockFlag, LF_WRITE_FLAG) & LF_WRITE_MASK) == LF_WRITE_FLAG) { //write flag 올려 놓고 read가 빠질 때까지 기다린다. write lock 기아 방지. while (mLockFlag & LF_READ_MASK) YieldProcessor();

return; } InterlockedAdd(&mLockFlag, -LF_WRITE_FLAG); }}

Page 22: Multi thread

01 Lock

•READ-WRITELOCKvoid SWLock::LeaveWriteLock(){ //leave는 그냥 올린 플래그 값을 낮춰주기만 하면 된다. InterlockedAdd(&mLockFlag, -LF_WRITE_FLAG);}

void SWLock::LeaveReadLock(){ //leave는 그냥 올린 플래그 값을 낮춰주기만 하면 된다. InterlockedDecrement(&mLockFlag);}

Page 23: Multi thread

02

Thread Local Storage

Page 24: Multi thread

02 Thread Local Storage

•ThreadLocalStorage전역 변수는 스레드 단위로 공유하는게 아니라 전체 프로세스가 공유하게 된다. 그런데 이런 전

역 변수 역할을 하는 변수를 스레드 단위로만 쓰게 만들고 싶을 때가 있다. 그럴 때 이용하는 개념

이 Thread Local Storage(TLS)이다. 즉, TLS를 이용하면 같은 이름의 변수를 쓰고 있으면서

도 내부적으로는 스레드 별로 할당된 서로 다른 메모리를 사용할 수 있다.

간단한사용법(정적사용법)

declspec을 이용해서 간단하게 TLS 변수를 만들어줄 수 있다.

//이 변수는 스레드 별로 독립적인 값이다.

__declspec(thread) int LThreadType;

Page 25: Multi thread

02 Thread Local Storage

•ThreadLocalStorage동적사용법

정적 사용법은 쓰기는 훨씬 편하지만 성능이 좀 떨어진다. 모든 정적 TLS 변수를 저장할 수 있을

만큼 큰 메모리를 할당받아야하며 정적 TLS 변수를 참조하는 것도 좀 느리다. 이럴 땐 쓰기는 좀

복잡하지만 동적으로 TLS를 사용하는 방법도 있다.

//TLS 메모리 공간 인덱스.

DWORD dwTlsIndex = TlsAlloc();

int* pThreadType = new int;

//값 저장

TlsSetValue(dwTlsIndex, pThreadType);

//값 불러오기

int* pType = (int*)TlsGetValue(dwTlsIndex);

//할당한 메모리 공간 해제

TlsFree(dwTlsIndex);

Page 26: Multi thread

02 Thread Local Storage

•ThreadLocalStorage구조

thread 별로 LPVOID 크기의 array를 만들어서 다 NULL로 초기화한 다음 각 스레드의 주어진

슬롯(dwTlsIndex)에 포인터 값을 저장하도록 만든다.

Thread 1

NULL

NULL

NULL

NULL

...

NULL

TLS01

2

3

4

n

data1

Thread 2

NULL

NULL

NULL

NULL

...

NULL

TLS01

2

3

4

n

data2

TLS 공간의 Index 값은 같아도 가

리키는 데이터는 서로 다르게 된다

Page 27: Multi thread

02 Thread Local Storage

•ThreadLocalStorage사용처

TLS를 유용하게 활용할 수 있는 경우는 정말 많다. 간단한 예를 몇 가지 들어보자면,

1. 스레드별 error code 관리(GetLastError와 같은 - 하나의 변수를 모든 스레드가

공유했다가는 error code를 정상적으로 획득할 수 없음.

2. 스레드 별로 해당 스레드에서 사용하는 타이머(Timer) 만들기

3. 해당 스레드에 부여된 ID값 저장

4. 스레드 별로 사용하는 힙(tcmalloc 같은 케이스)

등등 해당 스레드 내에서 전역적으로 쓰일 수 있는 값이라면 뭐든지 TLS를 이용해서

구현하는게 편하다.

Page 28: Multi thread

03

Lock - Free Algorithm

Page 29: Multi thread

03 Lock - Free Algorithm

•동기화문제멀티 스레드 프로그래밍에서 동기화 문제는 절대 피할 수 없는 이슈다. 일반적으로 동기화

문제를 해결하기 위해 여러 스레드에서 접근할 가능성이 있는 메모리에 접근할 때에는 lock을

걸어서 다른 스레드에서 동시에 해당 메모리 영역을 건드리지 못하도록 만드는 방법을 쓰는데,

이런 블락킹(Blocking) 방식의 동작은 성능에 큰 문제가 있다.

•성능저하 : 당연하다. 다른 스레드의 작업이 끝날 때까지 아무것도 못하고 기다리는 상황이

발생하므로 속도가 훨씬 떨어질 수 밖에 없다.

•PriorityInversion : Lock을 공유하는 덜 중요한 작업들이 중요한 작업의 실행을 막는

현상을 말한다. 공유자원을 쓰기 위해 대기해야하는 것때문에 우선순위가 떨어지는 작업이

중요한 작업보다 먼저 수행되어버림.

•Convoying: Lock을 얻은 스레드가 어쩌다 CPU 스케쥴링에서 제외된 경우, lock을

기다리는 모든 스레드가 공회전하게 된다. core 수보다 많은 thread가 동작하고 있는 경우

종종 발생할 수 있다.

Page 30: Multi thread

03 Lock - Free Algorithm

•Non-Blocking블락킹 방식의 이런 성능 상의 불이익을 해결하려면 Non-Blocking 방식으로 짜는 수밖에

없다. 그리고 Non-Blocking Algorithm에도 등급이 있다.

Wait-free

주어진 모든 시점에서 모든 스레드가 전진할 수 있는 경우. 이상적이긴한데 현실은... 이런

방식으로 구현할 수 있는 경우는 거의 한정적이라고 봐야 할 듯.

Lock-free

주어진 모든 시점에서 최소 하나 이상의 스레드가 전진할 수 있는 경우. 무대기면 무잠금이며,

기아(Starvation)을 유발할 수 있다는 문제가 있다. lock은 안 씀

obstruction-free

한 개를 제외한 모든 쓰레드가 멈췄을 때, 멈추지 않은 쓰레드의 메소드가 유한한 단계에

종료하는 경우.

Page 31: Multi thread

03 Lock - Free Algorithm

•Lock-freeLock-free에 대해 좀 더 자세히 살펴보자.

• 멀티쓰레드에서 동시에 호출해도 정확한 결과를 만들어준다

• Non-Blocking이다. 다른 쓰레드가 어떤 상태에 있건 상관없이 호출이 완료된다.

• 호출이 다른 쓰레드와 충돌하였을 경우 적어도 하나의 승자가 있어서 승자는 delay없이

작업을 완료할 수 있다.(wait-free는 호출이 다른 쓰레드와 충돌해도 모두 delay없이

완료된다)

• lock을 안 쓴다고 lock-free는 아니다. 하지만 lock을 쓰면 절대 lock-free가 아니다.

Page 32: Multi thread

03 Lock - Free Algorithm

•CAS(ComareAndSet)CAS는 Lock-free 알고리즘의 핵심이다. CAS를 이용하면 모든 싱글 쓰레드 알고리즘을 lock-

free 알고리즘으로 바꿀 수 있기 때문이다.

CAS(&A,old,new)

A의 값이 old면 new로 바꾸고 true를 리턴한다 -> A메모리를 다른 쓰레드가 먼저

업데이트해서 false가 나왔다. 포기해라(lock-free의 핵심! 경쟁 상태에서 승자-먼저

업데이트한 쓰레드-가 하나 나온다.)

단, 한 번에 하나의 변수 밖에 바꾸지 못한다는 한계점이 있다.

Page 33: Multi thread

03 Lock - Free Algorithm

•Lock-free구현방식

자료 구조의 변경을 시도한다but, 다른 쓰레드가 먼저 변경했으면 시도 취소

성공했는가? 완료YES

No

현재 자료구조를 파악한다

CAS 이용

Page 34: Multi thread

03 Lock - Free Algorithm

•Lock-Free예제공유 자원 메모리에 간단한 덧셈을 반복하는 경우를 생각해보자.

Blocking방식

lock.lock();

sum = sum +2;

lock.unlock();

lock-free방식

while(true)

{

int old_sum = sum;

if ( true == CAS(&sum, old_sum, old_sum +2) ) break;

}

Page 35: Multi thread

03 Lock - Free Algorithm

•정리앞에서도 말했듯이 Blocking을 이용한 알고리즘은 성능이 많이 떨어진다. 여러 쓰레드가

공유하는 자료구조를 적절히 잘 만들어진 Lock-free 기반의 자료구조를 이용하면 성능이 훨씬

좋아질 것이다. 대표적으로 게임 서버 등에서의 Job queue 같은 걸 예로 들 수 있을 듯.

하지만 Lock-free 알고리즘은 상당히 복잡해서 직접 작성하기가 까다롭다. 정확히 동작하는 지

확실히 증명된 알고리즘을 쓰거나 아니면 이미 잘 만들어져있는 lock-free 알고리즘을 가져다

쓰거나 하는게 좋다.

Lock-Free Algorithm에 대한 추가 참고 자료 :

http://www.slideshare.net/jinuskr/concurrency-in-action-chapter-7