프로그램 내부의 흐름, 맥
class Test {
public static void main(Sting[] args) {
int n = 0;
int m = 6;
System.out.println(n+m);
while (n < m)
n++;
System.out.println("Bye");
}
}
하나의 프로그램은 하나의 맥이 있고 이러한 맥을 쓰레드라고 한다.
동시에 돌 수 있는 이유는 맥이 빠른시간으로 스위칭 되기 때문이다.
그래서 실제로 같이 하고 있는 것처럼 보이지만 사실 CPU는 하나이기 때문에 우리는 하나만 사용하고 있다. 우리는 일반적으로 concurrent!
화면 출력하는 쓰데르 + 데이터 읽어오는 쓰레드
화면 출력하는 쓰레드 + 키보드 입력 받는 쓰레드 + 철자/문법 오류 확인 쓰레드
실제 context switching 되는 단위는 process단위가 아니라 thread단위이다!
한 프로세스에는 기본 1개의 쓰레드: 단일 쓰레드(single thread) 프로그램
한 프로세스에 여러개의 쓰레드: 다중 쓰레드 (multi-thread) 프로그램
쓰레드 구조
하나의 프로그램은 코드, 데이터, 스택으로 구성되는데 쓰레드가 코드, 데이터는 공유를 하지만 이때 스택은 따로 가지고 있다.
맥 만들기
java.lang.Thread
쓰레드가 가지고 있는 주요 메소드
쓰레드가 시작되면 run()메소드가 실행된다(run()메소드를 치환한다)
run()안에서 안에서 새로운 맥이 실행되기 때문에 run()을 치환해야 하고, 치환하려면 쓰레드 클래스에 하위 클래스를 만들어서 치환한다.
class MyThread extends Thread{
public void run() { // 치환(override)
// 코드
while (True) {
System.out.println("B");
try {
Thread.sleep(100);
} catch (InterruptedException e) ()
}
}
}
class Test {
public static void main(String[] args) {
MyThread th = new MyThread()
th.start();
while (True) {
System.out.println("A");
try {
Thread.sleep(100);
} catch (InterruptedException e) ()
}
}
}
해당 프로그램이 시작되면 main함수가 먼저 시작할것이다. main함수의 MyThread가 실행되면서 while문의 무한루프가 돌면서 B가 계속해서 찍혀나갈 것이고 그와 동시에 main함수의 while문이 무한루프를 돌면서 A를 계속해서 찍어낼 것이다. 컴파일해보면 A와 B가 섞여서 찍힐 것이다.
더 정확한 표현은 Thread Synchronization이다.
서로간 영향을 주고받는 데이터들간에 데이터의 일관성이 유지될 수 있도록 해주는 것이 동기화이다.
보통 컴퓨터 메모리 안의 프로세스들은 독립적이지 않고 협조하는 관계이다. 즉, 다른 프로세스에게 영향을 주거나 영향을 받는다. 대부분 공통된 자원(메인메모리)을 서로 접근하려고 하다보니 그런것이다. 하나의 메인메모리에 프로세스들이 많으니 어떤 방식으로든 영향을 주고받으니 그럴수록 프로세스 동기화의 개념이 중요해지고 있다.
Processes
Process Synchronization
ex: BankAccount Problem(은행계좌문제)
class BankAccount {
int balance;
void deposit(int n) {
balance = balance + n;
}
void withdraw(int n ){
balance = balance + n;
}
int getBalance() {
return balance;
}
}
class Parent extends Thread {
BankAccount b;
Parent(BankAccount b) {
this.b = b;
}
public void run() {
for (int i=0; i<1000;, i++)
b.deposit(1000);
System.out.println("+");
}
}
class Child extends Thread {
BankAccount b;
Child(BankAccount b) {
this.b = b;
}
public void run() {
for (int i=0; i<1000;, i++)
b.withdraw(1000);
System.out.println("-");
}
}
class Test {
public static void main(String[] args)
throws InterruptedException {
BankAccount b = new BankAccount();
Parent p = new Parent(b);
Child c = new Child(b);
p.start();
c.start();
p.join();
c.join();
System.out.println("balance = " + b.getBalance());
}
}
잔액은 0원이 나올 것이다.
시간지연에 따라 잘못된 결과를 초래한다.
class BankAccount {
int balance;
void deposit(int n) {
balance = balance + n;
System.out.println("+");
balance = temp;
}
void withdraw(int n ){
balance = balance + n;
System.out.println("-");
balance = temp;
}
int getBalance() {
return balance;
}
}
class Parent extends Thread {
BankAccount b;
Parent(BankAccount b) {
this.b = b;
}
public void run() {
for (int i=0; i<1000;, i++)
b.deposit(1000);
}
}
class Child extends Thread {
BankAccount b;
Child(BankAccount b) {
this.b = b;
}
public void run() {
for (int i=0; i<1000;, i++)
b.withdraw(1000);
}
}
class Test {
public static void main(String[] args)
throws InterruptedException {
BankAccount b = new BankAccount();
Parent p = new Parent(b);
Child c = new Child(b);
p.start();
c.start();
p.join();
c.join();
System.out.println("balance = " + b.getBalance());
}
}
시간지연에도 불구하고 결과는 바르게 나와야하는데, 이를 해결해야하는 것을 동기화라고 한다.
-> context switching이 중간에 일어나게 되면 이상한 결과를 초래할 수 있다.
-> 공통변수(common variable, balance)에 대한 동시 업데이트(concurrent update)로 인해 발생!
(즉, 하이레벨 랭귀지는 한줄이지만 로우레벨은 여러줄인데 업데이트 도중에 스위칭이 발생)
-> 해결: 공통변수에 대해 한번에 한 쓰레드만 업데이트 하도록한다! 임계구역 문제 = atomic하게!
Critical section(임계구간, 치명적인 구간): 이 구간에서 아주 치명적인 오류가 발생할 수 있다.
A system consisting of multiple threads.
Each thread has a segment of code, called critical section, in which the thread may be changing common variables, updating a table, writing a file, and so on
여러개의 쓰레드로 구성된 시스템에서 각각의 쓰레드는 어떤 코드의 영역을 가지고 있는데, 그 코드의 영역을 임계구역이라고 한다. 이 임계구역이 되기 위해서는 여러 쓰레드들이 공통의 변수, 테이블, 파일들을 바꾸는 부분 코드를 임계구역이라고 한다.
Solution (3개가 모두다 만족되어야 한다.)
Mutual exclusive(상호배타): 오류가 일어나지 않으려면 공통 변수에 대한 업데이트는 오직 한 쓰레드만 들어가 있을 때 진행되어야 한다. 즉 Parent가 critical section에 들어가면 그 순간에는 Child가 critical section에 들어가면 안된다.
Progress(진행): 진입 결정은 유한 시간 내에 일어나야 한다. 즉 critical section에 누가 들어갈 것인지를 결정하는 것은 유한 시간 내에 일어나야 한다.
Bounded waiting(유한대기): 어느 쓰레드라도 유한 시간 내에 일어나야 한다. 기다리는 시간의 한계가 들어가 있다는 것으로 내가 기다리고 있다면 유한 시간 내에 critical section 안으로 들어갈 수 있다.
프로세스 관리에서 중요한 것은 결국 프로세스/쓰레드의 동기화이다. 틀린답이 나오지 않도록 os에서 임계구역 문제를 해결해줘야 한다. 그리고 프로세스/쓰레드의 순서를 임의로 제어할 수 있어야 한다.