C언어 공부는 완료한지 오래 되었는데

귀찮은게 컸습니다.. 

(바쁘다는 핑계로!...)

 

지금 공부해야 되는 것들이 굉장히 많은데 아마 여유가 생기려면 몇 달이 될 수도 있을 것 같습니다.

물론 지금 하는 것들은 바로바로 올리고 있지만 C언어도 분명하게 올릴겁니다!

 

C언어의 경우에는 Python을 사용해서 개인 프로젝트를 진행하였기 때문에 (프로젝트는) 공개 가능한 부분은 공개 하도록 하고, 그 외에 C언어 관련하여 공부관련 글도 (몇 달이 될지 모르겠지만요!.....) 올리도록 하겠습니다.

'프로그래밍 언어 > C' 카테고리의 다른 글

C언어 조건문 - if 문 (2)  (0) 2017.02.16
C언어 조건문 - if 문 (1)  (0) 2017.01.26
C 언어 반복문의 중첩  (0) 2017.01.15
C언어 반복문 - for문 (2)  (0) 2017.01.15
C언어 반복문 - for문 (1)  (0) 2017.01.12

제가 공부하며 정리하는 글에서 마지막으로 구분한 스레드 상태 제어 part 3입니다.

 

 

스레드 상태, 스레드 상태 제어 part 1, 2는 다음의 링크를 통해 제 글을 확인하고 와주세요!

 

 

 

 

Java - 스레드 상태(Thread State)

 

Java - 스레드 상태 제어 (Thread State Control) part 1

 

Java - 스레드 상태 제어 (Thread State Control) part 2

 


 

part 3에서는 정지와 관련된 스레드의 안전한 종료(stop 플래그, interrupt())에 대해서 알아보도록 하겠습니다.

 

 

 

스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료 됩니다.

(저번 글(Java - 스레드 상태 제어 (Thread State Control) part1)에서 notify()와 wait()의 사용 예제를 작성해보며 run()메소드가 모두 실행이 되었음에도 불구하고, 계속 살아 있었던 경우가 있었습니다(자동적으로 종료 되지 않았습니다). 그 부분을 이번 글에서 나온 내용을 바탕으로 해결하였습니다.)

 

 

스레드 상태에 대하여 작성했던 내용 중에 stop() 메소드를 사용하면 스레드를 즉시 종료시킬 수 있다고 했었는데, 그 메소드는 deprecated 되었다고 했습니다. 그 이유는 stop() 메소드로 스레드를 갑자기 종료하게 되면 스레드가 사용 중이던 자원들이 불안전한 상태로 남겨지기 때문이라고 합니다.

 

 

stop 메소드 대신 stop 플래그를 이용하는 방법과 interrupt() 메소드를 이용하는 방법이 있습니다.


우선 stop 플래그를 이용하는 방법에 대하여 알아보겠습니다.

 

다음과 같이 간단하게 이해하시면 되지 않을까 싶습니다.

 

===============

while(조건) {

    실행 문장

}

===============

 

위의 while 문에서 조건 부분이 이면 실행이 되고, 거짓이면 실행이 되지 않는 점을 이용합니다.

 

예를들어 다음과 같이 활용할 수 있습니다.

 

===============

private boolean stop;

 

public void run() {

    while ( !stop ) {

        스레드가 반복 실행하는 코드;

    }

    //스레드가 사용한 자원 정리

}

===============

 

이처럼 stop이 true가 되면 while이 종료됩니다.

while을 빠져나오게 되면 스레드가 사용한 자원을 정리하게 되고, run() 메소드가 끝나게 되면서 스레드는 안전하게 종료됩니다.

 

다음 예제를 통해 stop 플래그를 사용해 보도록 하겠습니다.

 

 

 

 

 

StopFlagExample.java - 1초 후 출력 스레드를 중지시킴

package thread_state_control_3;
 
public class StopFlagExample {
 
    public static void main(String[] args) {
        PrintThread1 printThread = new PrintThread1();
        printThread.start();
        
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        
        printThread.setStop(true);    // 스레드를 종료시키기 위해 stop 필드를 true로 
    }
}

 

 

 

 

PrintThread1.java - 무한 반복해서 출력하는 스레드

package thread_state_control_3;
 
public class PrintThread1 extends Thread {
 
    private boolean stop;
    
    public void setStop(boolean stop) {
        this.stop = stop;
    }
    
    @Override
    public void run() {
        while(!stop) {
            System.out.println("실행 중");
        }
        System.out.println("자원 정리");    // stop이 true가 될 때
        System.out.println("실행 종료");
    }
}

 

예제를 실행하게 되면 다음과 같은 결과를 나오는 것을 확인할 수 있습니다.

 

실행 중

실행 중

실행 중

...

실행 중

자원 정리

실행 종료

 


다음으로 interrupt() 메소드를 이용하는 방법에 대하여 알아보도록 하겠습니다.

 

- interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 합니다.

 

말보다 눈으로 직접 보는게 나을 수도 있지요!

 

다음의 [그림]을 보도록 하겠습니다.

 

[그림 interrupt() 발생 시 스레드의 상태]

ThreadA가 ThreadB의 interrupt() 메소드를 실행하게 되면 ThreadB가 sleep() 메소드로 일시 정지 상태가 될 때 ThreadB에서 InterruptedException이 발생하여 예외 처리(catch) 블록으로 이동합니다.

 

결과적으로 ThreadB는 while문을 빠져나와 run() 메소드를 정상 종료하게 됩니다.

* interrupt() 메소드가 sleep()(일시 정지)을 발생 시키지 않습니다! interrupt()라는 신호를 보내주는 거에요!

* 스레드 상태 제어 part 2 에서 이러한 방법을 이용하여 스레드를 종료하였습니다.

 

 

다음 예제를 통하여 사용해 보겠습니다.

 

 

 

InterruptExample - 1초 후 출력 스레드를 중지시킴

package thread_state_control_3;
 
public class InterruptExample {
 
    public static void main(String[] args) {
        Thread thread = new PrintThread2();
        thread.start();
        
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        
        thread.interrupt();    // 스레드를 종료시키기 위해 InterruptedException을 발생시킴
    }
}

 

 

 

PrintThread2.java - 무한 반복해서 출력하는 스레드

package thread_state_control_3;
 
public class PrintThread2 extends Thread {
 
    @Override
    public void run() {
        try {
            System.out.println("실행 중");
            Thread.sleep(1);    // InterruptedException 발생
        } catch (InterruptedException e) {
        }
        
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

 

★ 여기서 주목하실 점은 스레드가 실행 대기 또는 실행 상태에 있을 때 interrupt() 메소드가 실행되면 즉시 InterruptedException 예외가 발생하는게 아닙니다!

    스레드가 미래에 일시 정지 상태가 되면 InterruptedException 예외가 발생합니다. 그러므로 스레드가 일시 정지 상태가 되지 않으면 interrupt() 메소드 호출은 아무런 의미가 없습니다.

 

 

일시 정지를 하지 않고도 interrupt() 호출 여부를 알 수 있는 방법이 있습니다.

interrupt() 메소드가 호출되었다면 스레드의 interrupted()와 isInterrupted() 메소드는 true를 리턴합니다.

 

바로 위 예제의 PrintThread2.java 를 수정하여 interrupted를 사용하도록 해보았습니다.

 

 

 

 

PrintThread2.java - 수정된 코드

package thread_state_control_3;
 
public class PrintThread2 extends Thread {
 
    @Override
    public void run() {
        while(true) {
            System.out.println("실행 중");
            if(Thread.interrupted()) {
                break;
            }
        }
        
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

 

interrupted()를 stop 플래그 처럼 활용하는 방법입니다.

 

 


스레드 상태 제어는 세분화하면 더욱 다양하지만 저는 일단 여기까지를 기본 개념으로 보고 있습니다.

나머지는 상태 제어는 맞지만 개념 자체가 상태 제어도 포함하지만 어떻게 보면 다른 개념처럼 보일 수도 있기에 따로 개념을 정리하였습니다.

 

이후 데몬 스레드, 스레드 그룹, 스레드풀의 형식으로 글을 작성하도록 하겠습니다.

 

 

 

 

 

 

스레드 상태 제어 part2입니다.

 

스레드 상태와 스레드 상태 제어 part1은 다음의 링크를 통해 제 글을 보고 와주세요!

 

 

 

 

 

Java - 스레드 상태(Thread State)

 

 

Java - 스레드 상태 제어 (Thread State Control) part 1

 

 

 

 

여러분들은 같이 하는게 좋으신가요?

아니면 혼자 하는게 좋으신가요?

 

이에 대한 정답은 없고, 상황에 따라 다르다고 할 수 있을 것 같습니다.

 

그럼에도 같이 해야 하는 경우가 있습니다.

 

바로 이번에 다루게 될 스레드 간 협업(wait(), notify(), notifyAll())이 바로 그 대상입니다.

 

상황에 따라서 두 개의 스레드를 교대로 번갈아가며 실행해야 하는 경우가 있습니다.

 

정확한 교대 작업이 필요할 경우,

- 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고!

- 자신은 일시 정지 상태로 만듭니다.

 

이 방법의 핵심은 공유 객체에 있습니다.

 

이와 관련하여 다음의 [그림 1]을 통해 알아 보겠습니다.

 

[그림 1 - 스레드 간 협업(wait(), notify(), notifyAll())]

 

공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓습니다.

 

한 스레드가 작업을 완료하면 notify() 메소드를 호출해서 일시 정지 상태에 있는 다른 스레드를 실행 대기 상태로 만들고,

자신은 두 번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만듭니다.

 

- wait() 대신에 wait(long timeout)이나, wait(long timeout, int nanos)를 사용하면 nofity()를 호출하지 않아도 지정된 시간이 지나면 스레드가 자동적으로 실행 대기 상태가 됩니다.

 

notify()와 동일한 역할을 하는 notifyAll() 메소드도 있는데, notify()는 wait()에 의해 일시 정지된 스레드 중 한 개를 실행 대기 상태로 만듭니다.

 

notifyAll() 메소드는 wait()에 의해 일시 정지된 모든 스레드들을 실행 대기 상태로 만듭니다.

 

wait(), notify(), notifyAll() 메소드는 Thread 클래스가 아닌 Object 클래스에 선언된 메소드이므로 모든 공유 객체에서 호출이 가능하다고 합니다.

주의 점은 이 메소드들은 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있다고 합니다.

 

 

 

예제를 통해 사용해 보도록 하겠습니다.

 

 

 

 

 

WorkObject.java - 두 스레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

package thread_state_control_2;
 
public class WorkObject {
 
    public synchronized void methodA() {
        System.out.println("ThreadA의 methodA() 작업 실행");
        notify();    // 일시 정지 상태에 있는 ThreadB를 실행 대기 상태로 만듬
        try {
            wait();    // ThreadA를 일시 정지 상태로 만듬
        } catch (InterruptedException e) {
        }
    }
    
    public synchronized void methodB() {
        System.out.println("ThreadB의 methodB() 작업 실행");
        notify();    // 일시 정지 상태에 있는 ThreadA를 실행 대기 상태로 만듬
        try {
            wait();    // ThreadB를 일시 정지 상태로 만듬
        } catch (InterruptedException e) {
        }
    }
}
 

 

 

 

ThreadA.java - WorkObject의 methodA()를 실행하는 스레드

package thread_state_control_2;
 
public class ThreadA extends Thread {
 
    private WorkObject workObject;
    
    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodA();        // 공유 객체의 methodA()를 10번 반복 호출
        }
    }
}
 

 

 

 

ThreadB.java - WorkObject의 methodB()를 실행하는 스레드

package thread_state_control_2;
 
public class ThreadB extends Thread {
 
    private WorkObject workObject;
    
    public ThreadB(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodB();        // 공유 객체의 methodB()를 10번 반복 호출
        }
    }
}
 

 

 

 

 

WaitNotifyExample.java - 두 스레드를 생성하고 실행하는 메인 스레드

package thread_state_control_2;
 
public class WaitNotifyExample {
 
    public static void main(String[] args) {
        WorkObject sharedObject = new WorkObject();        // 공유 객체 생성
        
        // ThreadA와 ThreadB 생
        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);
        
        // ThreadA와 ThreadB를 실행
        threadA.start();
        threadB.start();
    }
}

 

 

예제를 실행해 보시면 다음과 같이

 

ThreadA의 methodA() 작업 실행

ThreadB의 methodB() 작업 실행

 

ThreadA와 ThreadB를 10번씩 실행하는 것을 확인하실 수 있습니다.

 

하지만 위의 예제를 그대로 실행하면 프로세스 종료가 되지 않습니다. (?!)

 

예전에 프로젝트 진행하면서 실행 후 종료되지 않은 상태로 계속 몇 번이고 실행 시켰던 적이 있었는데

당시에 그로 인해서 컴퓨터 자원을 쓸 수가 없어서 컴퓨터가 먹통이 될 정도로 느려져 버린 적이 있었습니다.

 

그래서 이번에는 실수하지 않고, 종료가 되지 않는 것을 확인하고, 일일이 수동으로 꺼줬는데 너무 불편하고,

우리는 개발자이기 때문에 이것도 프로그래밍으로 해결을 해야지! 라는 생각으로 다음과 같이 수정을 했습니다.

수정된 소스 코드에 들어간 내용은 스레드의 상태 제어 part 3에서 다루는 내용을 사용하였습니다.

 

 

 

 

 

ThreadA.java - 수정된 코드

package thread_state_control_2;
 
public class ThreadA extends Thread {
 
    private WorkObject workObject;
    
    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodA();        // 공유 객체의 methodA()를 10번 반복 호출
        }
        
 
        // 수정된 부분
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
    }
}

 

 

 

ThreadB.java - 수정된 코드

package thread_state_control_2;
 
public class ThreadB extends Thread {
 
    private WorkObject workObject;
    
    public ThreadB(WorkObject workObject) {
        this.workObject = workObject;    // 공유 객체를 매개값으로 받아 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            workObject.methodB();        // 공유 객체의 methodB()를 10번 반복 호출
        }
        
        // 수정된 부분
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
        }
    }
}

 

 

 

 

WaitNotifyExample.java - 수정된 코드

package thread_state_control_2;
 
public class WaitNotifyExample {
 
    public static void main(String[] args) {
        WorkObject sharedObject = new WorkObject();        // 공유 객체 생성
        
        // ThreadA와 ThreadB 생
        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);
        
        // ThreadA와 ThreadB를 실행
        threadA.start();
        threadB.start();
        
 
        // 수정된 부분
        try {
            System.out.println("Testing");
            Thread.sleep(1000);
            System.out.println("Testing");
        } catch (InterruptedException e) {}
        
        threadA.interrupt();
        threadB.interrupt();
    }
}

 

3개 파일을 수정해 줬습니다.

제가 정확히 이해를 하지 못해서 이런 식으로 했다고 생각합니다.

마지막 WaitNotifyExample.java의 println의 경우,

처음에 어떤 스레드에서 종료가 되지 않는지 해서 디버깅 해보니

ThreadA, ThreadB가 10번 실행되고, 종료가 되지 않았습니다.

 

그래서 Thread.sleep() 으로 잠깐이라도 멈춰 주려고 했는데 여기서 또 어디서 interrupt()를 걸어야 할지 몰라서

예외 처리 구문에서 확인 할 수 있듯이 try 부분에서 Thread.sleep()을 기준으로 println을 출력해 봤습니다.

 

실행 결과는

 

Testing

// 다음 2줄은 10번 반복입니다.

ThreadA의 methodA() 작업 실행

ThreadB의 methodB() 작업 실행

...

Testing

 

이와 같이 출력이 되는 것을 확인 했습니다.

part 3에서 설명 드리겠지만 interrupt()가 발생하면 각각의 작업 스레드(ThreadA, ThreadB)에서는 try 구문에 있는 Thread.sleep()을 실행하게 됩니다.

 

그렇게 해당 스레드의 사용을 정리시키고, 최종적으로 모든 스레드가 종료되는 것을 확인 할 수 있었습니다.

 

제 방법이 가장 정확한 것도 아니고, 더 좋은 방법이 있는지 찾아 봐야겠습니다. (피드백은 언제든지 환영입니다!)

 

 

 

 

 

예제를 하나 더 해보도록 하겠습니다.

 

우선 [그림 2]를 보고 설명을 드리도록 하겠습니다.

 

[그림 2 - 생산자, 소비자 스레드]

 

위 그림을 통해 사용할 예제는 다음과 같습니다.

 

- 데이터를 저장하는 스레드(생산자 스레드)가 데이터를 저장(생산)합니다.

- 데이터를 소비하는 스레드(소비자 스레드)가 데이터를 읽고 처리합니다.

 

이 2가지를 처리하는 교대 작업을 구현한 예제입니다.

 

이번 예제의 핵심은 다음과 같습니다.

 

- 생성자 스레드는 소비자 스레드가 읽기 전에 새로운 데이터를 두번 생성하면 안 됩니다.(setData() 메소드 두 번 실행 X)

- 소비자 스레드는 생성자 스레드가 새로운 데이터를 생성하기 전에 이전 데이터를 두 번 읽어서도 안 됩니다.(getData() 메소드 두 번 실행X)

 

 

 

예제로 넘어가도록 하겠습니다.

 

 

 

 

DataBox.java - 두 스레드의 작업 내용을 동기화 메소드로 작성한 공유 객체

package thread_state_control_3;
 
public class DataBox {
 
    private String data;
    
    public synchronized String getData() {
        
        //    data 필드가 null이면 소비자 스레드를 일시 정지 상태로 만듬
        if(this.data == null) {
            try {
                wait();
            } catch(InterruptedException e) {}
        }
        String returnValue = data;
        System.out.println("ConsummerThread가 읽은 데이터: " + returnValue);
        
        //    data 필드를 null로 만들고 생산자 스레드를 실행 대기 상태로 만듬
        data = null;
        notify();
        return returnValue;
    }
    
    public synchronized void setData(String data) {
        
        //    data 필드가 null이 아니면 생산자 스레드를 일시 정지 상태로 만듬
        if(this.data != null) {
            try {
                wait();
            } catch(InterruptedException e) {}
        }
        
        //    data 필드에 값을 저장하고 소비자 스레드를 실행 대기 상태로 만듬
        this.data = data;
        System.out.println("ProducerThread가 생성한 데이터: " + data);
        notify();
    }
}

 

 

 

 

 

ProducerThread.java - 데이터를 생산(저장)하는 스레드

package thread_state_control_3;
 
public class ProducerThread extends Thread {
 
    private DataBox dataBox;
    
    public ProducerThread(DataBox dataBox) {
        this.dataBox = dataBox;        //    공유 객체를 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=1; i<=3; i++) {
            String data = "Data-" + i;
            dataBox.setData(data);    //    새로운 데이터를 저장
        }
    }
}

 

 

 

 

 

ConsumerThread.java - 데이터를 소비하는(읽는) 스레드

package thread_state_control_3;
 
public class ConsumerThread extends Thread {
 
    private DataBox dataBox;
    
    public ConsumerThread(DataBox dataBox) {
        this.dataBox = dataBox;        //    공유 객체를 필드에 저장
    }
    
    @Override
    public void run() {
        for(int i=1; i<=3; i++) {
            String data = dataBox.getData();        //    새로운 데이터를 읽음
        }
    }
}

 

 

 

 

 

WaitNotifyExample.java - 두 스레드를 생성하고 실행하는 메인 스레드

package thread_state_control_3;
 
public class WaitNotifyExample {
 
    public static void main(String[] args) {
        DataBox dataBox = new DataBox();
        
        ProducerThread producerThread = new ProducerThread(dataBox);
        ConsumerThread consumerThread = new ConsumerThread(dataBox);
        
        producerThread.start();
        consumerThread.start();
    }
}

 

두 번째 예제를 실행하시면 다음과 같이 나오시는 것을 확인하실 수 있습니다.

 

ProducerThread가 생성한 데이터: Data-1

ConsummerThread가 읽은 데이터: Data-1

ProducerThread가 생성한 데이터: Data-2

ConsummerThread가 읽은 데이터: Data-2

ProducerThread가 생성한 데이터: Data-3

ConsummerThread가 읽은 데이터: Data-3

 

 

저는 아직이라고 생각하지만 다른 분들께서는 여기까지 오시면서 다 이해하셨을 거라고 생각합니다!

 

제 기준에서 가장 이해하기 좋은 방법은 제공되는 예제소스를 Ctrl + V / Command + V 하지 않고, 직접 입력해가며 실행해 보는 거라고 생각합니다.

 

그러면서 조금씩 수정이 필요한 부분이나 추가할 곳에 추가도 해주면서 해봐야 이해도 잘 되고 실력도 늘게 된다고 생각합니다.

 

이렇게 Java - 스레드 상태 제어 (Thread State Control) part 2를 마치도록 하겠습니다.

 

 

 

+ Recent posts