본격적으로 스레드에 대해 배워보자
프로세스
- 실행중인 프로그램
- 자신의 주소 공간에서 순차적으로 실행되는 스트림
이렇게 정의 내릴 수가 있다.
스레드
- 프로그램 내의 단일 순차적 제어 흐름
- OS 측면에선 각 스레드들은 밑에 내용들로 구성되어 있다.
- PC (program counter)
- Register Set
- Stack
- 스레드들이 서로 공유하는 것이 있는데
- 코드 영역
- 데이터 영역
- OS resources ex) open files
싱글 스레드 프로세스
자 보면 스레드 하나가 있고, 위에서 설명한 대로 register, stack, pc(는 안보인다 지금은), code, data, files가 있는 것을 확인 가능하다.
멀티 스레드 프로세스
확실히 차이가 나는 것을 확인이 가능하다.
각 스레드마다 regiset, stack은 고유하게 가지나, code, data, files는 서로 공유하고 있다.
(만약 동기화의 얘기가 나온다면 당연하게도 저 위 3개의 영역이 제일 영향을 받는 것이다.)
여기서 stack은 간단하게 말하자면 local 변수들을 담아두는 곻간이라 생각하면 쉽다.
그래서 각 스레드가 가진 지역변수는 다른 스레드가 볼 수 없다.
멀티 프로세스 vs 멀티 스레드
- Process
- Process는 process 0을 제외하고서 모든 프로세스는 fork()라는 명령어를 통해 만들어 진다.
- 그래서 child process는 parent process를 그대로 copy해가지고 온다
- 그렇기에 자식과 부모가 동일한 코드를 실행한다
- 당연히 자식 프로세스는 부모의 변수들을 copy한다
- 그렇기에 시작이 매우 비싸다고 할 수 있다.
- 그러나 변수에 대한 동시 접근에 대한 걱정은 없어지게 된다 (다 복사해오니)
- Thread
- 자식이 부모의 변수를 공유해서 사용한다
- 그렇기에 아주 가볍게 시작한다
- 동시 접근에 대한 issue가 있다
그림으로 보면 이해가 더 쉽다
위의 프로세스를 봐라
딱 봐도 무거워 보이지 않는가. 이걸 fork할시 계속 복사 한다 생각하면 thread가 얼마나 가벼운지 알게 될 수 있다.
Thread with Java
자바로 스레드를 배워보자(입문)
스레드에 관한 내용을 배우면서 자바는 어떻게 짜는지에 대해 공부하는 것이다.
그렇기에 자바로 스레드를 활용 하는 것은 따로 공부하는 것이 좋을 듯 하다.
방법 1. Thread를 상속받자
class MyThread extends Thread{
public void run(){
//작업 할 것
}
}
....
MyThread t = new MyThread();
t.start();
방법 2. Runnable 인터페이스를 구현하자
class MyRunnable implements Runnable{
public void run() {
System.out.println(“MyRunnable.run()”);
}
//other methods and data for this class
}
MyRunnable myrun= new MyRunnable();
Thread t = new Thread(myrun);
t.start()
스레드 Life-Cycle
스레드는 만들어지고 start를 안한다면 실행을 안한다.
그래서 start를 하면 Alive 상태가 되고, run이 끝나거나 stop이 나오면 종료가 된다.
또한 Alive 상태가 된다면 안에서 또 substate가 존재한다.
Running, Runnable, non-runnable 상태가 있다.
실행 중일때 yield 명령어를 사용한다면 runnable상태가 되고,
wait, sleep, block이 되면 non-runnable이 된다.
물론 이 상태에서도 notify, 시간 만료, I/O가 된다면 Running으로 바로가는게 아닌 Runnable로 가게 된다.
스레드 우선순위
스레드에도 우선순위를 둘 수 있다.
1~10사이의 숫자로 우선순위를 설정 가능하다.
선점적인 스케쥴링이 가능하게 해준다
JVM은 우선순위가 높은 스레드를 선호한다고 하기 때문이다. (보장되지는 않긴 하다.)
yield
- 스케쥴러가 다른 실행 가능한 스레드를 고르게 허락해준다
- 물론 어떤게 선택 될지는 모른다
Thread sleep, suspend, resume
- static void sleep
- 즉 block시킨다는 말이다.
- stop(), suspend() ,resume() 은 deprecated가 되었다
Thread Waiting & Status Check
- void join()
- A라는 스레드가 B가 끝날때까지 기달린다는 의미이다.
Thread Synchronization
스레드는 동시에 여러 일을 작업하게 된다.
그렇기에 문제가 발생 할 수 있다.
Safety와 Liveness에 대해 문제가 생길 수 있다.
- Safety
- 아무런 나쁜 일이 안생기는 것이다.
- no race condition
- Liveness
- deadlock같은게 없는 상태
race condition 예시
class Account{
private int balance;
public void deposit(int val){
balance = balance + val;
}
public void withdraw(int val){
balance = balance - val;
}
}
위의 상황을 봐라.
만약 스레드들이 deposit과 withdraw를 막 실행 한다 생각해보자, 돈이 입금되고 출금이 되야하는 상황에서 출금되고 입금되는 순서가 역전되는 상황이 안 벌어 질 것이라고 장담 가능한가?
불가능하다.
synchronized keyword
자바에서 동기화를 하는 방법 중 하나로 synchronized keyword를 붙일 수 있다.
ex)
class Account{
private int balance;
public synchronized void deposit(int val){
balance = balance + val;
}
public synchronized void withdraw(int val){
balance = balance - val;
}
}
Synchronized Lock Object
Lock을 Object 단위로도 걸 수 있다.
ex)
class TransThread extends Thread
{
private FinTrans ft;
TransThread (FinTrans ft, String name)
{
super (name); // Save thread's name
this.ft = ft; // Save reference to financial transaction object
}
public void run ()
{
for (int i = 0; i < 100; i++){
if (getName ().equals ("Deposit Thread"))
{
synchronized (ft){
ft.transName = "Deposit";
try{
Thread.sleep ((int) (Math.random () * 1000));
}
catch (InterruptedException e) {}
ft.amount = 2000.0;
System.out.println (ft.transName + " " + ft.amount);
}
}
위 예시는 ft를 보호하고 있는 것을 확인 할 수 있다.
Condition Variables
- lock(synchronized)
- 스레드가 데이터 접근하는 것을 control
- condition variable (wait, notify/notifyall)
- 스레드들이 특정 상황이 올 때 까지 기달리게 하는 것
- 즉 스레드들이 자동으로 lock에서 해제되고 sleeping state에 들어가게 해준다
- 만약 이게 없다면 프로그래머들이 직접 일일히 다 해제하고, 조건을 확인하고 해야할텐데… 참 끔찍한 일이었을 것 같다
- condition variable은 항상 mutex lock과 같이 사용된다
wait & notify
- wait
- interrupt가 없다면, 현재 스레드는 blocked된다
- 그럼 그 스레드는 wait set에 배치가 된다.
- Object의 Synchronization lock 해제된다
- notify
- 스레드 하나가, T한테 wait set에서 제거하라고 말한다.
- 그럼 T는 이제 락에서 해제되고, waiting status에서 재개한다.
ex)
class ParkingGarage {
private int places;
public ParkingGarage(int places) {
if (places < 0)
places = 0;
this.places = places;
}
public synchronized void enter() { // enter parking garage
while (places == 0) {
try {
wait();
} catch (InterruptedException e) {}
}
places--;
}
public synchronized void leave() { // leave parking garage
places++;
notify();
}
}
class Car extends Thread {
private ParkingGarage parkingGarage;
public Car(String name, ParkingGarage p) {
super(name);
this.parkingGarage = p;
start();
}
public void run() {
while (true) {
try {
sleep((int)(Math.random() * 10000)); // drive before parking
} catch (InterruptedException e) {}
parkingGarage.enter();
System.out.println(getName()+": entered");
try {
sleep((int)(Math.random() * 20000)); // stay within the parking garage
} catch (InterruptedException e) {}
parkingGarage.leave();
System.out.println(getName()+": left");
}
}
}
간단한 차고지 예시이다.
만약 주차장에 차가 들어오게 된다고 가정해보자.
차가 들어오고, 그럼 그 스레드를 wait시킨다.
자 그러다가 차가 빠져나가게 되면 notify를 함으로서 다른 스레드들에게 말한다. 야 나 나가니깐 아무나 들어와.
그럼 이제 락에서 어떤 한 스레드가 해제되어 들어오게 되는 것이다.
'CS > 잡 지식' 카테고리의 다른 글
Redis vs Memcached (0) | 2023.06.25 |
---|---|
(Nonblocking vs Blocking) vs (비동기 vs 동기) (0) | 2023.06.24 |
인코딩이란? (1) | 2023.05.30 |
스레드(1) - 멀티 프로세스(병렬처리) (1) | 2022.12.26 |
동시성 vs 병렬성 (2) | 2022.12.26 |
댓글