본문 바로가기

OS 공부

OS 공부 - Thread, 멀티스레딩, 멀티코어

728x90
반응형

 

Thread
lightweight process
가장기본적인 CPU를 점유하는 단위가 스레드가 된다. CPU 이용의 기본 단위
PID가진 프로세스가 CPU를 점유하는게 아니라 thread(TID)가 CPU를 점유한다.
프로그램카운터, register set, 스텍은 스레드마다 달라야한다.

 

일반적으로 하나의 제어 쓰레드를 가지고 있는 중량 프로세스는 한번에 한가지의 작업만을 수행함, 만약에 하나의 프로세스에 여러개의 쓰레드가 존재한다면, 프로세스는 동시에 하나 이상의 작업들을 수행할 수 있다.

 

=> 쓰레드는 한 프로세스안에서 동시에 여러 작업들을 수행할 수 있게 해주는 프로세스 안의 작은 프로세스(경량 프로세스 또는 쓰레드)

 

하나의 프로세스가 여러개의 thread of control을 가질 수 있다.
cpu의 프로그램카운터만 바꿔주면 이프로세스 저 프로세스 사용한다.

 

code, data, file(오픈,신호) 자원은 공통으로 쓰고, register, stack, PC(프로그램 카운터)를 스레드마다 별도로 가진다.

 

예)쓰레드 3개를 생성하여 각각의 쓰레드가 평균, 최대값, 최소값 계산 기능을 맡아서 출력한다면 더욱 빠른 결과가 나온다.
브라우저는 이미지 또는 텍스트를 표시하는 하나의 쓰레드와 네트워크로부터 자료를 가져오는 또 다른 쓰레드를 가질 수 있다.

 

원격 프로시저 호출(Remote Procedure Call, RPC)에서도 매우 중요한 역할을 담당한다. RPC는 IPC(Interprocess communication)를 마치 일반적인 함수나 프로시저 호출을 하듯 할 수 있게 해줌, 이런 RPC 서버들은 대부분 다중 쓰레드 시스템으로 구현

 

멀티스레딩

 

클라이언트가 서버에 요청, 서버가 new해서 thread에게 일을 맡김
서버는 계속 Nonblocking으로 계속Resume해주면 계속 요청이 들어와도 생성할 수 있는 한도까지 Thread계속 생성

 

멀티스레딩 장점
1.Responsiveness(응답성) :
응용 프로그램이 긴 작업을 수행하더라도 프로그램의 수행이 계속되는 것을 허용함
웹 브라우저는 한 쓰레드가 이미지 파일을 적재하고 있는 동안, 다른 스레드에서 사용자와의 상호 작용이 가능함
유저인터페이스같은걸 처리할 때 블록킹(동기) 되어 있을 필요 없이 non블로킹으로 그냥 excution 계속 해줄 수 있다.

 

2.Resource Sharing(자원 공유) :
1개 이상의 쓰레드들은 본인이 속한 프로세스의 자원(code,data,files)들과 메모리를 공유가 가능
스레드는 코드와, 데이터 영역을 공유한다. 굳이 Shared Memory안만들어도(프로세스처럼) thread1과 thread2는 데이터 영역을 Shard Memory처럼 사용할 수 있다.

 

3.Economy(경제성) :
프로세스 생성을 위해서 메모리와 자원을 할당하는 것은 비용이 많이 듦
쓰레드는 프로세스 자원을 공유하기 때문에 쓰레드를 생성하고 문맥 교환(Context Switching)하는 것이 더 비용이 싸다.
프로세스 creation보다 값이 싸다. 코드영역을 복사해서 사용 것보다 스레드 여러개가 경제적이다.
컨텍스트 스위칭보다 thread스위칭이 더 쉽다.

 

4.Scalability(확장성) :
각각의 쓰레드가 다른 처리기에서 병렬로 수행될 수 있다.
일 쓰레드 프로세스는 CPU가 아무리 많더라도 한 CPU에서만 실행되지만 다중 CPU에서 다중 쓰레딩을 수행하면 병렬성이 증가됨.
코어가 여러개일 경우 각각에 스레드를 붙여서 병렬처리가 가능하다.

 

Java
Java는 thread 기반으로 개발해놨다. API도 간략하고 쉬움
=>스레드 생성과 관리가 쉽다.

 

Java에서 사용방법
1. Thread 클래스 상속 -> public void run() 메소드 override
이렇게 사용할 경우 자바는 다중상속이 안된다.-> 다른걸 상속 못받는다. 웬만하면 이렇게 쓰지 말 것
2. Runnable 인터페이스 상속 -> public void run() 메소드 override
3. Lambda expression 사용 (익명 스레드)

 

1.Thread 클래스 상속 받기

class MyThread1 extends Thread {
	public void run() {
		try {
			while (true) {
			System.out.println("Hello, Thread!");
			Thread.sleep(500);
			}
		}
		catch (InterruptedException ie) { // 종료용
			System.out.println("I'm interrupted");
		}
	}
}

 

run 호출이 아니라 start를 해주면 thread클래스가 알아서 run 호출
strat하면 아직 아직 스레드간의 context switch 안일어나서 "Hello, My Child!" 부터 나오고 그다음 컨텍스트 스위치가 일어나고 run()이 실행된다.

 

public class ThreadExample1 {
	public static final void main(String[] args) {
		MyThread1 thread = new MyThread1(); //인스턴스 생성
		thread.start(); // 
		System.out.println("Hello, My Child!");
	}
}

 

2.Runnable 인터페이스 구현하기

class MyThread2 implements Runnable {
	public void run() {
		try {
			while (true) {
				System.out.println("Hello, Runnable!");
				Thread.sleep(500);
			}
		}
		catch (InterruptedException ie) {
			System.out.println("I'm interrupted");
		}
	}	
}

 

new 해서 인스턴스만든다. -> Thread클래스의 생성자 parameter를 넘겨준다.
thread는 인스턴스의 run을 호출해준다.

 

public class ThreadExample2 {
	public static final void main(String[] args) {
		Thread thread = new Thread(new MyThread2());
		thread.start();
		System.out.println("Hello, My Runnable Child!");
	}
}

 

3.Runnable 람다 표현식 사용하기
Rnnable 인터페이스에 객체 참조 변수 하나 선언 "() ->"
오버라이딩 해줌
new Thread 안에다가 넣어줌

 

public class ThreadExample3 {
	public static final void main(String[] args) {
		Runnable task = () -> {
			try {
				while (true) {
					System.out.println("Hello, Lambda Runnable!");
					Thread.sleep(500);
				}
			}
			catch (InterruptedException ie) {
				System.out.println("I'm interrupted");
			}
		};
		Thread thread = new Thread(task);
		thread.start();
		System.out.println("Hello, My Lambda Child!");
	}
}

 

부모 쓰레드 대기: wait? join!
thread.start() 해주고 thread.join해주면 자식 끝날때까지 부모가 기다렸다가 자식 끝나면 메인스레드 진행,
"Hello, Lamda Runnable" 출력 후 "hello, my joined child" 출력

 

public class ThreadExample4 {
	public static final void main(String[] args) {
		Runnable task = () -> {
			for (int i = 0; i < 5; i++) {
				System.out.println("Hello, Lambda Runnable!");
			}
		};
		Thread thread = new Thread(task);
		thread.start();
		try {
		thread.join();
		}
		catch (InterruptedException ie) {
			System.out.println("Parent thread is interrupted");
		} 
		System.out.println("Hello, My Joined Child!");
	}
}

 

쓰레드의 종료: stop? interrupt!
동기화 문제 발생하니까 .stop 쓰지 말 것, interrupt 줘라
thread.interrupt(); 로 인터럽트 줌
"hello, lambda runnable" 다섯번 실행중에 interrupt걸려서 빠져나와서 "I'm interrupted" 출력 ->메인의 "hello, my interrupted child"출력

 

public class ThreadExample5 {
	public static final void main(String[] args) throws InterruptedException {
		Runnable task = () -> {
			try {
				while (true) {
					System.out.println("Hello, Lambda Runnable!");
					Thread.sleep(100);
				}
			}
			catch (InterruptedException ie) {
			System.out.println("I'm interrupted");
			}
		};
		Thread thread = new Thread(task);
		thread.start();
		Thread.sleep(500);
		thread.interrupt();
		System.out.println("Hello, My Interrupted Child!");
	}
}

 

멀티코어 시스템
네개의 코어, 네개의 스레드 -> 굳이 타임쉐어링 할 필요 없이 자체 코어에서 하나 잡고 얘가 여기서만 돌면 컨텍스트스위치 필요없다.
싱글 코어: 스레드를 interleaving (끼워넣기) 한다.
멀티 코어: 병렬적으로 돌 수 있다.

 

 

 

멀티코어 시스템 문제
Identifying tasks:
어떤 부분들이 separate하게 실행 될 수 있는지 찾아내는 능력 필요, 합을 구할 때 코어별로 어떻게 나눠서 계산하고 합칠지.. 정렬할 지..

 

Balance:
같은 value 로 같은 work를 하게 해야한다. 만약에 데이터가 있는데 잘못 쪼개 가지고 이만큼 쪼개가지고 Thread1 한테 시키고 Thread2한테 시키고 스레드2가 빨리끝나서 놀고있고. Thread1은 죽도록 일만하고 이런 밸런스를 맞춰줘야한다.

 

Data splitting:
데이타를 얼만틈 쪼개느냐 separate core에 실행될 수 있는 데이터 divide해야됨

 

Data dependency:
실행할때 잘 동기화한다. 의존성처리

 

Testing and debugging:
테스팅과 디버깅이 어렵다. 싱글스레드 디버깅은 브레이크포인트 같은거 줘서 확인할 수있는데 여러개의 스레드는 확인이 힘들다.

 

병렬처리
data parallelism: 데이타를 쪼개서 줌


task parallelism: task를 쪼갬

 

암달의 법칙(Amdahl's Law)
코어는 무조건 많을 수록 좋은가?


S: 어떤 시스템에서 serially(연속)하게 실행될 수 있는 비율
N: 코어 개수

 

빨간색처럼 코어증가하면 배로 빨라져야하는데 serially 실행하는 비율이 25%일때 1.6배 빨라지고 코어가 네개면 2.28배 빨라짐
50퍼만 되어도 실질적으로 코어의 개수는 소용이 없다.

반응형