본문 바로가기

ComputerScience/Multi-core Computing

멀티코어컴퓨팅 - 3. Programming JAVA threads

728x90

http://tutorials.jenkov.com/java-concurrency/index.html

 

Java Concurrency and Multithreading Tutorial

Java Concurrency refers to multithreading, concurrency and parallelism as handled in the Java language and platform. This Java Concurrency tutorial explains the basic benefits, costs, problems and solutions a decent Java developer ought to know about.

tutorials.jenkov.com

process

실행 중인 프로그램

UNIX에서 fork()를 통해 프로세스를 만들 수 있다.

PCB가 만들어지는 단위

메모리를 할당받는 단위이다.

 

Thread

쓰레드들은 code, data, files(process resources)를 서로 공유하고 각자 독립적인 program counter, register, stack을 가지고 있다. 

*program counter : cpu 내부에 있는 레지스터 중의 하나로서, 다음에 실행될 명령어의 주소를 가지고 있어 실행할 기계어 코드의 위치를 지정한다. 때문에 명령어 포인터라고도 한다.

 

JAVA threading model

자바에는 thread라는 built in 패키지가 존재한다.

모든 프로그램들은 main이라는 쓰레드를 적어도 하나 가지고 있다.

JVM(Java Virtual Machine)이 main을 실행하는 initial thread를 만든다. main안에서 다른 thread를 만들 수 있다.

미리 만들어 놓은 MyThread를 main에서 만들면 된다. 

mt.start()가 실행되면 MyThread에서 정의한 run()동작을 쓰레드 mt가 수행한다.

정확한 순서는 모르지만 run()과 main의 for loop는 동시에 실행된다.

Thread-1, Thread-2가 기본적으로 이름으로 할당된다. 직접 HappyThread라고 이름을 붙이거나 getName()으로 그 이름을 가져올 수 있다.

1,2,3쓰레드는 동시에 각자 작업을 수행하는데 오른쪽을 보면 실행 순서가 일관적이지 않다. 각 thread는 서로 다른 실행 속도를 갖기 때문이다. 작업량 차이, 스케쥴링 문제, 코어의 성능 등 원인은 다양하다.

 

JAVA에서 쓰레드를 생성하는 방법2

Thread를 상속하는 새로운 클래스를 만드는게 아니라 runnable을 implement하는 MyRun을 만들어서 Thread에 생성자로 전달하는 방법이다.

t.start()가 실행되면 내가 정의한 MyRun의 run()이 실행된다.

 

Thread life cycle

stop()은 deprecated되어 더 이상 사용이 불가능하다.

 

Alive 상태를 확대한 모습이다.

cpu를 점유한 상태가 running이다. (쓰레드가 실행중인 상태)

runnable은 쓰레드들이 cpu 할당(dispatch)을 기다리는 상태이다.

long, busy waiting이 발생하면 non-runnable 상태로가서 notifiy()로 누가 깨워주길 기다린다.

 

기타 다양한 함수들

setPriority(int newPriority) : 쓰레드의 우선순위를 부여할 수 있다.

 

yield() : cpu 점유를 양보한다. 위의 예시에서는 정확히 1번 돌고 cpu를 양보했다.

 

Thread.currentThread() : 현재 running thread의 정보를 반환한다.

위의 예시에서 main thread가 t1.run을 실행하고 있기 때문에 화면에 문구가 출력되지 않는다. 이렇게 _me가 현재 running thread일 때 "AutoRun.run()"을 출력하도록 한다.

 

sleep(long millis) 만큼 쓰레드를 잠재울 수 있다.

 

join() 만약 쓰레드 A에서 threadB.join()을 실행하면 쓰레드A는 쓰레드B를 기다리게 된다.

MyThread2(Thread_a)생성자로 Thread_b를 만들 때 Thread_a를 인자로 넘겨주면 MyThread2(Thread_b)에서 wait4me라는 이름으로 Thread_a를 저장해둔다.

Thread_b의 run()이 실행되어 wait4me.join()을 만나는 순간 Thread_b는 wait4me가 가리키는 Thread_a의 종료를 기다리게 된다.

즉, thread_a, thread_b는 서로 동시에 실행되었지만 thread_b가 thread_a를 기다렸기 때문에 결과가 정렬되어 나왔다.

 

synchronization

여러 쓰레드가 동시에 작업하다보면 같은 메모리 공간을 동시에 접근하는 경우가 있을 수 있다.

쓰레드 간의 동기화가 정확하게 이루어지지 않았다면 위 상황에서 race condition이 발생할 수 있다. (smart phone, atm이 shared data를 얻기 위해 경쟁한다.)

critical section : 코드의 특정 부분을 말하는데 이 영역에서는 반드시 한 쓰레드만 존재 할 수 있다. 예시에서는 deposit 코드 부분이 해당할 것이다.

mutual exclusion of critical section : 즉 상호배제란 오직 한 쓰레드만 critical section에 진입할 수 있는 상태를 보장하는 것을 말한다.

우측 그림에서 shared data에 접근하는 method1, method2가 있다. shared memory에 접근하는 method1,2가 ciritical section이 되고 한 thread가 method1,2를 사용중이라면 다른 thread가 critical section에 진입하지 못하도록 막는 것이 상호배제이다. 그 과정에서 lock을 걸고 unlock을 기다리고 한다.

당연히 critical section이 작을수록 concurrency에 좋다. ciritical section이 클수록 쓰레드가 서로 기다려야하는 시간이 길지만 synchronization safety는 높아질 수 있다.

 

synchronized

synchronized 키워드를 붙인 메서드는 한 쓰레드만 진입할 수 있다. 즉 한 쓰레드만 lock을 hold한다.

synchronized update()는 한번에 한 쓰레드만 접근할 수 있다.

아니면 이렇게 synchronized object를 사용할 수도 있다. ciritical section을 저 괄호 안에 넣기만 하면 mutual exclusion이 보장된다.

synchronized 키워드를 붙이면 그 안에서 wait(): 자원할당 대기 상태로 만들기 위해 잠재운다. , notify() : 잠자고 있는 대기 상태 쓰레드를 깨운다 그 한명의 쓰레드에게 lock을 부여한다, notifall() : 잠자고 있는 모든 쓰레드를 깨운다, 함수를 사용할 수 있다.

위의 예시에서는 ft라는 객체에는 한번에 한 쓰레드만 접근할 수 있다.

 

lock

synchronized 말고 lock을 사용할 수도 있다. 기능은 동일하다. await(), signal(), signalAll()을 사용한다. 

 

Garage Parking Problem

진입로가 한 개인 주차장에 순서대로 여러 차가 진입해서 주차하고 때가 되면 떠나서 다른 차를 위해 자리를 비켜주는 것을 구현한 코드이다.

enter는 한번에 한 쓰레드만 접근함이 보장된 크리티컬 섹션이다.

자동차가 진입하려는데 주차장에  공간이 없으면 wait을 만나게 하여 sleep시켜 enter()를 다른 쓰레드에게 양보하고 이 쓰레드를 block상태로 만든다.

차량이 주차장을 떠나면 notify()로 잠자고 있던 쓰레드 하나를 깨운다. leave()도 한번에 한 쓰레드만 사용할 수 있는 메서드이다.

728x90
반응형