프로세스
- 실행 중인 프로그램(Program in execution)
- 1개의 CPU는 1개의 프로세스만 실행 할 수 잇다.
- IO는 CPU를 기준으로 어마어마하게 느린 작업이다.
- 그래서 IO가 발생하면 그 프로세스는 일단 Wait 상태가 되고 IO가 끝나면 다시 ready가 된다.
- 프로그램은 모두 자신만의 PC를 가지고 있다. (다른 프로세스를 실행하다 다시 돌아오면 어디서 시작해야하는지 알아야한다.)
- 프로세스는 논리적인 단위
- Linux : task_struct (double Linked List로 구성됨) 현재 시작하고있는 task를 가리키는것이 current
- 리눅스는 CPU숫자만큼 프로세스가 있다?
- 컴퓨터는 시간의 흐름에 따라 실행된다.
프로세스가 생기는 절차
- 컴퓨터가 시작하면 1번 프로세스가 생기고 이 프로세스가 새끼를 친다.
- fork()를 부르는 주체는 나.
- 자식 프로세스를 만들때 자신이 fork()를 해서 자신을 하나 더 만들고 (부모와 자식이 같다 - 클론) 그리고 exec()를 실행하면 자식프로세스가 바뀐다.
- 터미널은 배쉬쉘이란 것을 사용하는데 명령어를 치면 배쉬가 클론이 되고 변환되서 실행되서 종료된다.
- OS에는 매우 비싼 작업이다. (프로세스를 새로 실행시키는 작업은 비용이 크다.)
Process Control Block
- 프로세스의 메타데이타
- 모든 프로세스는 자기만의 메타데이타를 가진다.
- PC(Program Counter) - 프로그램의 실행 시점
- CPU - Register - L1 Cache - L2 Cache - Memory - HDD
- CPU가 어마어마 하게 빠르기 때문에 그 속도의 갭을 해결하기 위해 레지스터 - 캐쉬 가 있다.
- CPU는 ADD, SUB, MUL, DIV, SHIFT, AND, OR … 의 일을 한다.
컨텍스트 스위치
- 한 프로세스에서 다른 프로세스로 작업을 변경하는 것.
- 작업을 변경할때는 PC를 저장해놔야 한다.
- 많이 하면 좋지 않다.(오버헤드가 굉장히 크다.)
- 프로세스가 시간의 흐름에 따라 돌아가다가 인터럽트가 발생하였을 때 프로그램카운터를 저장하고 Idle 상태의 프로세스를 실행 시킨다. 그때 그 Idle상태의 프로세스의 정보를 로드하고 다 로드 되면 프로세스가 실행된다. (로드 되는시간엔 CPU는 아무것도 못한다 이것이 컨택스트 스위치 오버헤드라 한다.)
- 실행시간(execute time)을 얼마를 할건지가 중요하다. (느리게하면 반응속도가 느리다.)
쓰레드
- 프로세스가 비용이 커서 스레드가 생겼다.
- 프로세스 내에서 실행되는 흐름의 단위(정확한 정의, 시간의 흐름)
- 프로세스는 최소 하나 이상의 스레드를 갖는다.
- 라이트 웨이트 프로세스(가벼운 프로세스라고 생각해도 무방하다.)
- 프로세스에 비해 생성과 컨택스트 스위치가 빠르다. (비용이 적다.)
- 논리적인 쓰레드는 PC의 흐름이다.
- 고유한 메모리 영역이 없고 자신의 PC와 레지스터만 가지고 있기에 빠르다.
- 부모 쓰레드가 끝나면 자식쓰레드 까지 전부 종료된다.
병렬프로그래밍 (동시성 보장)
- 함수형 프로그래밍은 기본적으로 동시성이 보장된다.
- 각각이 스스로 계산한 다음에 마지막에 더하는게 최고의 효율의 병렬프로그래밍.(그래도 99%)
- 공통적으로 동작하지않는 부분이 1% 생기기 때문에 이것이 성능에 영향을 미친다.
동시성과 병렬성
- 동시성 - 여러기능이 한 번에 실행 되는 것.
- 병렬성 - 한프로그램의 동시에 여러개 실행.
- 요즘 세상에서는 동시성을 고려한다.
쓰레드프로그래밍
- 비결정적 프로그래밍 - 어떤놈이 언제 실행될지는 아무도 모른다.
- 디버깅이 어렵다. - 결과가 재현이 어렵다.
- 동기화하는 부분은 최소화 해야한다. (동기화를 할때는 1개만 동기화메서드를 점유함) ``` package study.thread;
public class ThreadTest extends Thread { public static long totalSum; public long start, end, sum;
public synchronized static long getSum() {
return totalSum;
}
public ThreadTest(long start, long end) {
this.start = start;
this.end = end;
System.out.println(start + " : " + end);
}
public synchronized static void merge(long sum) {
totalSum += sum;
}
public void sum() {
for (long i = start; i <= end; i++) {
sum += i;
}
merge(sum);
}
@Override
public void run() {
sum();
}
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
long max = 2000000000;
int n = Runtime.getRuntime().availableProcessors(); // int n= 1;
long step = max/n;
Thread[] t = new Thread[n];
for (int i=0; i < n; i++) {
t[i] = new ThreadTest(i* step + 1, (i+1) * step);
t[i].start();
}
for (Thread tt : t) {
tt.join();
}
System.out.println((System.currentTimeMillis() - start) + "ms");
System.out.println(ThreadTest.getSum());
} } ```
암달의 법칙
- 가장 오래 걸리는 프로그램을 개선해야한다.
- 무언가를 할때 가장 중요한놈을 고쳐야 한다.
데드락의 3가지 조건
- mutual exclusion - 상호배제 (한번에 한놈만 들어올 수 있다.)
- non preemption - 방해 받지않는다.(내가 시작하면 끝나기전에 끝내지않는다.) - 포기하거나 끝내게 하는것(해결법)
- circular waiting (원형 큐) - 번호를 매긴다. (홀수번은 오른손 짝수번은 왼손)
데드락 피하기
- 동기화 부분에서 외부 메서드를 호출하지 않는다.
- 순서를 미리 결정한다.
- 동기화 코드는 최대한 짧게
동기와 비동기 (Slipp에서 검색!) - 블락과 논블락, 동기와 비동기는 서로 다른축의 이야기다.
- 동기 -
- 비동기 -
- 블락 -
- 논블락 -
대부분 IO이후의 어떻게 반응을 할건가? IO가 왓을때
- 블락 - 완료될때까지 기다린다(에러나면 나는것)
- 논블락 - 오래걸릴것 같으면 하지 않는다. (정상적일때는 읽는다. 기준은 API가 정한다.)
- 동기 - 끝날때까지 기다림
- 비동기 - 바로 다음코드를 실행하는데 나중에 콜백을 부른다.(콜백은 내가 실행하는게 아니라 내가 원하는 API가 이것을 호출할때 콜백함수를 등록해주면 자기가 호출한다. - 언제 실행될지 모름)
물리메모리
스레드 추가 자료
어마어마한 시간낭비지만 해보면 OS에 대한 감을 가질 수 있다!
리눅스 오브 스크래치
젠투 리눅스
교학사 리눅스커널 완전정복
concurrent 모델 책
- 7가지 동시성 모델
- 자바 병렬 프로그래밍
-
java concurrent 구글링 검색해서 개념만 이해해도 됨.
쓰레드 예제
흡혈귀 코드
생각하는 철학자
Comments