티스토리 뷰

Skill/Programming - Network

5. 스레드(Thread)

gyulee0220 2019. 4. 27. 15:53

  우리가 만든 프로그램을 작동시키면 프로세스가 생성되어 CPU로부터 메모리 영역을 할당 받고 명령을 수행한다는 것까지는 이제 알겠다. 하지만, 우리는 네트워크 서비스를 프로그래밍 해야한다. 만약 우리가 만든 네트워크 프로그램에 수천명이 동시에 접속한다고 생각하자. 이 수천명에게 각각 프로세스를 만들어야 한다고 가정해보자. 모든 사람에게 CPU가 메모리 영역을 할당해주어야 한다. 장비의 성능이 좋더라고 운영체제는 무거운 프로세스의 무게를 버티지 못하게 된다. 그래서 새로 나온 개념이 바로 스레드이다.


1. 스레드(Thread)란? 

  모든 카페에는 주문을 받는 카운터 직원이 존재한다. 조용한 동네 카페라면 한명의 카운터 직원이 모든 주문을 충분히 처리 가능하다. 하지만, 우리가 이번에 만드려는 프로그램은 대형 프랜차이즈 카페다. 뱅뱅사거리 앞의 프랜차이즈 카페에 카운터 직원이 한명이라고 생각해보자. 정말 끔찍한 일이 벌어질 것이다. 우리가 만드려는 스레드도 이와 비슷한 개념이다. 

  각각의 스레드는 스택만 할당 받게 된다. 하지만, 프로세스와 달리 데이터 영역을 할당 받지 못한다. 스레드는 프로세스 안에 속하고 하나의 프로세스에 여러개의 스레드가 존재 할 수 있다. 스레드가 데이터 영역을 사용하기 위해서는 프로세스의 데이터 영역을 사용한다. 그렇기 때문에 동일한 프로세스 안에 존재하는 스레드는 메모리 영역을 서로 공유한다. 



  스레드는 프로세스와 결정적인 몇가지 차이를 보인다.

- 프로세스(Process)

  • 컴퓨터 메모리에서 연속적으로 실행되는 프로그램 인스턴스
  • 각각의 프로세스에 독자적인 메모리 영역 존재
  • 프로세스간 전환 속도 느림
  • 비정상적인 명령 수행시 해당 프로세스만 종료

- 스레드 (Thread)

  • 프로세스 내에서 실행되고 있는 흐름의 단위
  • 동일한 프로세스 내에 존재하며, 프로세스의 메모리 영역을 공유
  • 스레드간 전환 속도 빠름
  • 비정상적인 명령 수행시 동일한 프로세스안의 스레드 모두 종료

2. 스레드의 특징

  스레드를 사용하게 되면 메모리의 과부화를 획기적으로 줄일 수 있다. 프로세스 단위로 작동할 때 CPU의 부담을 물론이며, 서버의 장비 성능도 비교적 낮은 성능의 장비를 사용하게 되더라고 많은 사용자를 수용 할 수 있다. 단일 스레드 프로세스에서 생겼던 많은 문제를 분명 해결 할 수 있다. 또한 재사용 스레드 풀(thread pool)을 이용하게 된다면 각 연결에 대해 스레드를 생성하지 않고도 수천개의 연결을 처리할 수 있다.

하지만, 애석하게도 스레드는 만능이 아니다. 스레드는 아래와 같은 몇가지 문제점을 가지고 있다.

  ▶ 개발자의 코드가 복잡해진다.

  아래에서 스레드 코딩에 대해 알아볼 것이지만, 스레드를 사용하게 되면 개발자의 주의가 각별해진다. 단일스레드와 달리 멀티스레드에서는 안전성과 지속성 부분에 대한 고민이 필요하다. 단일스레딩에서는 프로세그 각각의 메모리를 사용해 저장된 값 사용에 대해 큰 고민이 필요 없지만, 멀티스레딩에서는 공용 메모리를 사용하므로 프로그램 로직에 따라 프로그램 결과값이 달라지는 일도 발생한다. 하나의 스레드가 다른 모든 스레드를 망가뜨릴 수 있다. 

  ▶ 데드락(DeadLock) 문제가 발생한다.

  멀티스레딩에 메모리 사용이 신중하다고 말한바 있는데, 이를 해결하기 위해 하나의 스레드가 메모리에 접근하게 된다면 다른 스레드는 종료되는 배타적 요소가 추가된다. 이때, 두 스레드가 너무 신중하게 작동하게 된다면 어떤 스레드도 메모리에 접근하지 못해 프로세스가 멈추는 데드락 상태에 빠질 수 있다. 어느 한 스레드도 리소스를 반환하지 않는다면, 프로세스는 영원히 멈추게 된다.

  ▶ 어느 스레드가 먼저 실행될지 모른다.

  멀티스레딩에서는 어떤 스레드가 먼저 실행될지 모른다. 앞으로 배우게 될 스케줄링에 대한 별도 로직이 없다면 CPU는 프로세스 단위로 프로그램을 올리기 때문에 내부에서 어떤 스레드를 먼저 처리할 지는 관심 밖이다. 앞서 언급한 부분과도 겹치는 부분인데, 어떤 스레드가 먼저 실행될지 예측하지 못한다면 전혀 엉뚱한 프로그램 결과 값이 도출 될 수 있다. 그래서 우린 우선순위 부여를 통해 스레드 스케줄링을 제어한다.

3. 스레드 실행하기

  Java에서 역시 프로세스와 마찬가지로 스레드 클래스를 제공한다. 앞서 언급한대로 Java 프로그램 실행 자체가 하나의 프로세스 생성이다. 그래서 프로세스 시간에는 외부 프로세스를 호출하는 방법에 대해 배웠다. 스레드는 다른 매커니즘으로 작동한다. 우리가 생성한 프로세스 내부에서 작동하게 된다. 따라서 스레드를 실행할 때는 미리 Java Code로 정의한 임의의 스레드를 클래스로 정의하고 main 함수에서 연결하여 스레드를 시작한다. 말로 설명하면 매우 어려우니 아래의 코드를 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
public class Solution {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
        Thread t1 = new Thread();
        Thread t2 = new Thread();
        
        t1.start();
        t2.start();
        
    }
 
}
 
cs


  위에서는 스레드 t1과 t2를 정의해 실행 시켰다. 스레드를 위에서 선언을 한뒤, start() 메소드로 스레드를 실행 시킨다. 우리가 선언 하는 Thread 클래스를 JVM은 스레드로 인식하여 실행시키게 된다.


  하지만 앞서 소개한 스레드는 아무런 행동이 정의 되지 않은 빈 스레드이다. 스레드 행동을 정의하기 위해서는 Thread 클래스를 상속 받아야 한다. 그리고 Thread Class에 존재하는 run() 메소드를 오버라이딩 하여 행동을 정의한다. 그리고 run()메소드에 정의된 프로그램을 실행시키는 메소드가 바로 start()이다.


  이를 바탕으로 매우 간단한 스레드 프로그래밍을 만들 수 있다.


TestThread.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TestThread extends Thread{
    
    int number =0;
    
    public TestThread(int a){
        this.number = a;
    }
    
    @Override
    public void run(){
        System.out.println("Thread Number : "+number);
        try {
            Thread.sleep(5000); // 스레드 잠시 멈춤
        } catch (Exception e){
            
        }
        
    }
    
}
 
cs

MainClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
public class Solution {
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
        System.out.println("Process start");
        
        for(int i=0; i<10; i++){
            TestThread t = new TestThread(i);
            t.start();
        }
        
        System.out.println("Process end");
        
    }
 
}
 
cs

결과 값



  앞에서 설명한대로 우리가 원하는 대로 스레드가 실행이 되지 않는다. 프로세스 안에 있는 스레드 중 어떤 걸 먼저 실행할 지는 전혀 모른다. 0번 스레드부터 9번까지 순서대로 실행 시켰지만, 실제 순서는 전혀 다른 방향으로 실행된 것을 볼 수 있다.

4. 스레드 값 반환하기

  앞서 설명한대로 스레드를 그냥 실행 시키면 우리는 어떤 스레드가 먼저 실행될지 전혀 모른다. 물론, 스레드라는 개념 상 우리가 스레드 순서까지 제어해야할 필요는 없다. 하지만, 공용 데이터를 공유한다면 말이 달라진다. 예를들어 해당 스레드를 통해 우리 프로그램의 전역 변수를 제어 해야 하는 경우가 발생 할 수 있다. 


  이번에는 프로그램에서 전역 변수를 사용하고 변경하는 스레드가 포함된 프로그램을 만들어보자.


  A와 B 두사람이 있고, 각각 잔고에는 500원씩 보유하고 있다. 그리고 100원 짜리 제품을 총 5번 구매를 해야하는데 두 사람중 돈이 많은 사람이 구매한다고 하자. 만약, 동일한 잔고를 보유하고 있다면 A가 지불한다. 그렇다면 우리가 원하는 결과값은 A: 200, B: 300이 될 것이다.


  스레드 컨트롤에는 여러가지 방법이 있다. 우선 이번에는 간단한 방법을 써보자. 가장 쉽고 직관적인 방법은 스레드가 끝날 때 까지 기다르리는 것이다.


- 스레드 종료 시점까지 대기

MainClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
public class Solution {
    
    static public int a = 500;
    static public int b = 500;
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
        System.out.println("Process start");
        
        for(int i=0; i<5; i++){
            TestThread t = new TestThread(a,b);
            t.start();
            
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            a=t.getA();
            b=t.getB();
        }
        
        
        System.out.println("A : "+a+"  B: "+b);
        System.out.println("Process end");
        
    }
 
}
 
cs



TestThread.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
class TestThread extends Thread{
    
    int a =0;
    int b =0;
    
    public TestThread(int a, int b){
        this.a = a;
        this.b = b;
    }
    
    @Override
    public void run(){
        System.out.println("Thread Start");
        
        try {
            if(a>=b){
                a = a-100;
            }
            else{
                b = b-100;
            }
        } catch (Exception e){
            
        }
        
    }
    
    public int getA(){
        return a;
    }
    
    public int getB(){
        return b;
    }
    
}
cs


결과값




  이런식으로 원하는 결과값을 얻을 수는 있겠지만, 매우 불안정한 방법이다. 스레드가 비정상적으로 작동할 수 도 있고 우리가 매번 스레드의 실행 시간을 맞출 수는 없는 노릇이다. 그저 저 시간이면 가능할 것이라고 짐작하고 개발할 뿐이다. 이는 매우 좋지 않은 코딩 방법이다.


  이 문제를 어떻게 해결할 수 있을까? 스레드를 스케줄링하는 다양한 방법이 존재하다. 이번 시간에는 스레드의 개념과 java 에서의 스레드 실행 방법에 대해 알아보았다. 다음 시간에는 앞에 발생 한 것 처럼 스레드가 전역 데이터에 접근해야 하거나, 순서 제어 및 동기화를 적용 하는 방법에 대해 배워보도록하자.


'Skill > Programming - Network' 카테고리의 다른 글

7. 스레드 동기화  (0) 2019.05.19
6. 스레드 폴링, 콜백  (0) 2019.05.07
4. 프로세스 관리 (Process Control)  (1) 2019.04.07
3. 프로세스(Process)  (0) 2019.03.27
2. 스트림 (Stream)  (0) 2019.03.03
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함