5. • thread 문제들
• thread safety (=리소스 동기화)
• 기대하지 않는 동작
• 동기화 방식과 성능 병목
• mutex / semaphore / spin lock
• lock-free / wait-free
• Java 는 동기화를 어떻게 하나요?
• monitor(=synchronized)
• Lock(ReentrantLock, ReentrantReadWriteLock)
• thread 문제 해결을 위한 다른 접근
• h/w transactional memory
• functional programmaning
6. 다수의 core 를 사용하는 방법
• multi processing
• multi threading
이 두 가지 밖에 없다.
8. thread safety
• 모든 thread safety 문제는 공유되는 리소스 간 동기화 문제
• 다음 4가지 상황을 만족하면 thread safe
• 공유 리소스 없다
• 공유 리소스 있다 & immutable
• 공유 리소스 있다 & critical section 에 대한 동기화 보장
• 공유 리소스 있다 & atomic operation
9. 동기화 메커니즘을 이해하기 위한 분류
• 동기화 메커니즘 제공자가 누구니?
• kernel level lock
• user level lock
• 사용 책임자가 누구니?(=발로 짜도 잘 돌아가니?)
• application 개발자
• system 또는 language spec(=개발자 아님)
• 취급 주의사항이 있니?
• recursion 금지
• 오랜 점유 금지
• (시나리오에 따라)성능이 어떻게 달라지니?
• 다수의 thread 가 경쟁
• 매우 짧은 critical section / 혹은 그 반대
10. 상황에 맞는 동기화 메커니즘
• 동기화 메커니즘을 일일이 살펴보는 이유
• 상황에 맞게 동기화 전략을 세우기 위해
• 닭 잡는데 소 잡는 칼을 쓰지 말자
11. mutex(Mutual Exclusion)
• mutex 라고 하면 두 가지를 지칭한다.
• 이론 : 동기화 방법론 / algorithm
• 구현
• 플랫폼마다 다르다.
• pthread : pthread_mutex_init, pthread_mutex_lock, …
• windows : CreateMutex, OpenMutex, …
• 등등등…
• mutex를 간단히 설명하면
• kernel 이 제공하는 무한 loop
• 단 1개의 thread만 무한 loop를 통과할 수 있고, 나머지는 sleep
14. mutex(Mutual Exclusion)
• mutex 의 문제
• kernel wait overhead
• mutex lock/unlock 시 thread 를 sleep/wake 시키기 위한 system call
• 아주 짧은 연산을 할때도 일단 thread 를 sleep/wake 시켜야 하기 때문에 비효율적
• 해결
• spin lock
• futex – 최근 linux kernel(2003년 이후) mutex는 모두 futex 지원
15. semaphore
• semaphore도 mutex와 마찬가지
• 이론 : 동기화 방법론 / algorithm
• 구현
• mutex와 마찬가지로 플랫폼마다 다르다.
• pthread : sem_init, sem_wait, …
• windows : CreateSemaphore, WaitForSingleObject, …
• 등등등…
• semaphore를 간단히 설명하면
• mutex로 구현한 resource counter
• 내가 지정한 개수만큼의 thread가 critical section에 진입 가능
mutex랑 다른점!!
17. lock 성능 개선
• mutex 와 semaphore의 공통점
• kernel level lock
• lock / unlock 의 비용이 크다
• kernel wait
• sleep / wake wait
• kernel resource(e.g. file / shared memory …)도 동기화 가능
• lock / unlock 비용을 줄이는 방법
• sleep / wake 를 하지 않거나
• lock 을 직접 구현해서 쓰거나
spin lock
18. spin lock
• spin lock 구현도 무한 loop
• thread 를 sleep 시키지 않는다
• 구현이 간단하고 효과적이기 때문에 user
area 에서 직접 구현하여 쓰는 경우가 많다.
• 직접 구현할 경우, 반드시 recursion 문제를
고려하여야 한다.
• 효과
• mutex / semaphore 에 비해 응답이 빠르다.
• 문제점
• 문제1. busy wait(=spin wait)
• 문제2. recursion 시 deadlock
https://github.molgen.mpg.de/git-mirror/glibc/blob/master/nptl/pthread_spin_lock.c#L44-L66
19. spin lock 의 문제
• 문제 1. 언제 busy wait 가 발생하나?
• 여러 threa가 경쟁할 때
• critical section 에 머무르는 시간이 길 때(=연산량이 많을 때)
• 문제 2. recursion 시 deadlock
• lock 을 해제하지 않고 재차 lock 을 시도하기 때문에 발생
• 보통은 spin lock 내부에 call counter 를 두어 recursion 이 가능하도록 구현한다.
• spin lock 은 다음의 경우에 유리
• thread 간 리소스를 얻기 위한 경쟁이 치열하지 않고
• 리소스 접근 시 연산량이 짧을 때
20. lock 직접 구현
• lock 을 직접 구현하였을 때 장점
• kernel wait 를 하지 않는 만큼 빠르다.
• 문제점
• 커널 리소스에 대한 동기화를 보장할 수 없다.
• 어차피 우리는 시스템 프로그래밍 안하니까 괜찮아!
• Java 의 Lock interface 구현이 여기에 해당
• ReentrantLock
• ReentrantReadWriteLock
• 어떻게 lock 을 구현하나요?
• locking algorithm 을 보고 따라하면서
• processor 가 제공하는 atomic operation 을 사용합니다.
21. thread safety
• 모든 thread safety 문제는 공유되는 리소스 간 동기화 문제
• 다음 4가지 상황을 만족하면 thread safe
• 공유 리소스 없다
• 공유 리소스 있다 & immutable
• 공유 리소스 있다 & critical section 에 대한 동기화 보장
• 공유 리소스 있다 & atomic operation
lock
atomic operation
22. atomic operation
• processor 가 H/W 적으로 제공하는 연산
• 해당 연산은 반드시 thread safe 함을 H/W 가 보장
public class AtomicNonAtomicExample {
static int foo = 0;
static AtomicInteger atomicFoo = new AtomicInteger(0);
public static void main(String[] args) {
foo += 1;
atomicFoo.incrementAndGet();
}
}
GETSTATIC atomicFoo
INVOKEVIRTUAL incrementAndGet ()I
POP
atomicFoo 읽기
incrementAngGet() 호출
call stack 정리
atomic!!
한번의 연산으로
foo에 더하고 쓰기를 완료했다.
GETSTATIC foo : I
ICONST_1
IADD
PUTSTATIC foo : I
foo 읽기
constant 1 읽기
foo 와 1 더하기
foo 에 쓰기
not atomic!!
두 연산 사이에 다른 쓰레드 연산
이 간섭할 수 있다.
23. atomic operation
• H/W 가 제공하므로 H/W 마다 instruction set이 다르다.
• x86 processor 에서 제공하는 atomic operation
• compare and swap
• test and set
• fetch and add
• 대부분의 언어에서 atomic operation 을 사용할 수 있는 수단을
제공한다.
• java : java.util.concurrent.atomic
25. atomic operation
• kernel wait 없음 –> 빠르다
• lock 없음 –> 무한 loop 없음 –> 빠르다
• atomic operation 을 사용하는 이유 -> 빠르다
• atomic operation 은 다루기 까다롭다.
• atomic operation 을 잘 사용하려면 알아둬야 할 지식이 많다.
좌우간 빠르다!!
26. test and set
• 어떤 값(=lock 여부를 확인하기 위한 Boolean)을 1로 변경/저장하는 operation
이해를 돕기 위한 S/W 구현 TAS로 구현한 mutex
• x86 cpu의 TAS 구현은 BTS(bit test and set)
• http://www.felixcloutier.com/x86/BTS.html
28. compare and swap
• 가장 만만하게 쓰임
• 저장공간에 실제 저장된 값(a) 과 예상되는 값(b) 를 비교하여
• a == b 이면 a 를 내가 변경하고 싶은 값(c) 로 변경 후 true를 리턴
• a != b 이면 실패, false 를 리턴
이해를 돕기 위한 S/W 구현
• x86 cpu의 CAS 구현은 CMPXCHG(compare and exchange)
• http://x86.renejeschke.de/html/file_module_x86_id_41.html
29. ABA problem
• 아주 우연히 재수가 없으려니 발생하는 현상
• 그런데 그것이 실제로 일어났습니다?
• 원인
• CAS의 동작원리에 따라,
기대값 == 실제값 인 경우 데이터를 변경.
허나 실제로 데이터를 변경하면 안되는 상황일 수 있다.
• 링크드리스트 node의 reference가 우연히 재사용되어
CAS instruction이 변경을 감지하지 못함
• 대체로 수십만 번 정도의 동시 접근이 일어날 때 발생 -> 많지
도 않지만 적지도 않은 빈도
• 다행스럽게도 java에서는 발생하지 않습니다.
• 다행스럽게도 java concurrent collection 구현에서는 발생하지
앖습니다.
• 그럼 몰라도 되겠네?
30. 다음 시간에는
• Java thread implementation
• Synchronized vs Lock
• Runnable vs Callable
• executorService 와 thread pool
• Future
• Why functional?