반응형

먼저 보고오면 좋을 글 - 코틀린에서 스레드 사용하기


코틀린에서 Thread 사용하기( + start()-스레드 시작, join()-스레드 대기상태로 만들기)

 

코틀린에서 Thread 사용하기( + start()-스레드 시작, join()-스레드 대기상태로 만들기)

코틀린에서 스레드 생성 및 실행하기 3 가지의 스레드 생성 방법이 모두 나와있는 전체 코드 import kotlin.concurrent.thread fun main() { // 1번째 스레드 - Thread()를 상속받아 생성 // testThread 생성 val..

soopeach.tistory.com


일반적으로 우리가 사용하는 채팅 시스템을 떠올려보면 채팅을 주고받음과 동시에 사진도 보내고, 사진도 다운로드하고... 그외 등등 여러 작업을 동시에 하고 있다. 이는 해당 시스템이 멀티 스레드로 동작하기 때문이다. 만약 채팅 시스템이 멀티 스레드가 아닌 싱글 스레드로 동작한다면... 사진을 업로드하는 동안, 사진을 다운받는 동안.. 단일 스레드 혼자이 작업들을 하느라 해당 프로그램이 멈추고 다운로드, 업로드에 집중하게 되어 채팅을 실시간으로 하지 못하게 될 것이다.
이를 조금 더 이해하기 쉽게 간단한 예시를 통하여 코틀린으로 구현해보았다.

 

아래에서 사용할 예시는 아주 간단히 "10 ~ 1까지 카운트를 하는 작업 + 3번 반복( text를 입력받아 그대로 출력하는 작업)" 을 진행할 것이다.

 

싱글 스레드일 경우

싱글 스레드 일경우 어떨까... 우리가 알듯이 싱글 스레드를 사용하면 위에서 아래로 순차적으로 코드가 동작한다.(main함수 하나만 사용)


첫 번째 소스코드

import kotlin.concurrent.thread

fun main() {

    // 10 - 1까지 카운트 다운
    repeat(10) {
        println("카운트 다운 ${10 - it}")
        Thread.sleep(1000)
    }
    println("카운트 다운이 완료되었습니다.")

    // 콘솔 창에 입력받은 값을 그대로 출력 - 3번 반복
    repeat(3) {
        println("채팅을 입력해주세요 : ")
        val text = readln()
        println("\"${text}\"을/를 전송하였습니다. 남은 채팅 횟수 ${3 - (it+1)}")
    }
    println("이용 가능한 채팅횟수를 다 사용하였습니다.")

}

이러한 방식으로 코드가 구성되어있다면 어떠한 결과가 나올까?

아마 다들 쉽게 예상할 수 있을 것이다. 
총 10회의 카운트 다운이 끝나고, 카운트 다운이 끝났다는 것을 알려주는 문구가 출력된 후,
3번의 텍스트 입/출력이 반복된 후 채팅횟수가 끝났다는 문구가 출력되고 프로그램이 종료된다.

첫 번째 소스코드의 출력결과


두 번째 소스코드

import kotlin.concurrent.thread

fun main() {

    // 콘솔 창에 입력받은 값을 그대로 출력 - 3번 반복
    repeat(3) {
        println("채팅을 입력해주세요 : ")
        val text = readln()
        println("\"${text}\"을/를 전송하였습니다. 남은 채팅 횟수 ${3 - (it + 1)}")
    }
    println("이용 가능한 채팅횟수를 다 사용하였습니다.")

    // 10 - 1까지 카운트 다운
    repeat(10) {
        println("카운트 다운 ${10 - it}")
        Thread.sleep(1000)
    }
    println("카운트 다운이 완료되었습니다.")
    
}

위와 동일한 코드인데, 입/출력, 카운트 다운을 하는 과정의 순서만 바뀌었다.
마찬가지로 3번의 입/출력이 반복된 후 카운트 다운이 되는 것을 확인할 수 있다.

두 번째 소스코드의 출력결과

예상한대로 결과가 나타난다!


멀티 스레드일 경우

하지만 우리가 원하는 기능은 이렇게 카운트 다운, 입/출력이 순차적으로 이루어지는 것이 아니다. 카운트 다운이 진행되는 동안 3번의 입/출력을 하는 것이다. 위와 같이 싱글 스레드를 사용할 경우 스레드가 순차적으로 작업들을 처리하여 한 작업을 하는 동안 다른 작업은 진행할 수 없게된다. 
이번에는 다수의 스레드를 사용하여 동시에 작업이 진행되도록 해보자.

첫 번째 소스코드

import kotlin.concurrent.thread

fun main() {

    // 콘솔 창에 입력받은 값을 그대로 출력 - 3번 반복하는 스레드 생성.
    val chatTh = thread(false){
        repeat(3) {
            println("채팅을 입력해주세요 : ")
            val text = readln()
            println("\"${text}\"을/를 전송하였습니다. 남은 채팅 횟수 ${3 - (it + 1)}")
        }
    }

    // 10 - 1까지 카운트 다운하는 스레드 생성.
    val countTh = thread(false){
        repeat(10) {
            println("카운트 다운 ${10 - it}")
            Thread.sleep(1000)
        }
    }

    // 입/출력을 담당하는 스레드를 실행
    chatTh.start()
    println("이용 가능한 채팅횟수를 다 사용하였습니다.")

    // 카운트 다운을 담당하는 스레드를 실행.
    countTh.start()
    println("카운트 다운이 완료되었습니다.")

}

싱글 스레드를 사용한 코드와 달리, 이번 소스코드에는 thread를 사용하여 두 개의 스레드를 생성한 것을 볼 수 있다. thread()의 인자로 false가 주어졌는데 이는 스레드를 생성만 하겠다는 소리이다. 스레드를 생성만하고 실행을 하지 않으면 스레드 실행은 당연히 되지 않는다.
그래서 아래에 .start()를 사용하여 스레드를 실행시켜준 것. 하지만 thread()의 인자로 true를 주게되면 생성과 동시에 스레드를 실행한다!

위의 코드에 대해서는 출력 결과가 어떻게 나올까?

첫 번째 소스코드의 출력결과

음?... 멀티스레드를 사용하면 동시에 잘 된다고..?
맞다. 멀티스레드를 사용하여 동시에 잘 진행되었다. 하지만 위의 코드에는 문제가 있다. 채팅을 입력하지도 않았는데 채팅횟수를 소진했다는 문구가 출력되었다. 또한 카운트 다운이 시작되기도 전에 카운트 다운이 완료되었다는 종료문구가 출력되었다...
왜 이러한 현상이 생기게 된 것일까?

 

이 소스코드가 전체적으로 동작되는 과정을 보자.

chatTh라는 스레드를 생성 -> countTh라는 스레드를 생성 -> chatTh 스레드를 실행 -> 채팅횟수가 다 사용되었다는 문구를 출력 -> countTh 스레드를 실행 -> 카운트 다운이 완료되었다는 문구를 출력.

순으로 동작하게 된다. 마지막 카운트 다운이 완료되었다는 문구를 출력하게 되면 main함수는 끝난다. 하지만 프로그램은 종료되지 않고 카운트 다운, 입/출력이 완료될 때까지 프로그램이 돌아가게 된다.
여기서 우리는 메인 함수가 종료되어도 나머지 스레드의 작업이 종료되기전까지 프로그램이 실행된다는 것을 확인할 수 있다.

 

위의 코드에서 스레드가 실행되는 것을 간단히 확인해보면,
메인 스레드(main 메서드를 실행하는 스레드)에서 별도의 스레드를 생성하고 .start()함수를 사용하여 그 스레드를 실행하면 메인 스레드에서 start()가 동작한다. 이 start()는 별도의 스레드를 만들어서 해당 스레드의 작업을 생성한 그 별도의 스레드에서 진행하도록 한다.

참고로 이 스레드들의 동작 순서는 OS스케쥴러가 진행하기 때문에 무엇이 먼저 진행될지는 알 수 없다.
이를 눈으로 보고 싶다면 ,두 개의 스레드를 생성 후 하나는 0, 하나는 1을 반복하여 출력하도록 만들고 코드를 여러 번 실행시켜보면 된다.
실행시킬때마다 다른 결과가 출력된다.(여러 번 반복해야 눈에 잘 보인다.. 적게 반복하면 하나의 스레드가 이미 연산을 먼저 끝낼 가능성이 있음.)

결론은 우리가 원하는 대로 별도의 스레드에서 입/출력을 담당하는 부분과, 카운트 다운을 담당하는 부분이 잘 동작하고 있으며 메인 스레드 또한 스레드를 생성, 시작만 시킨 후 그대로 아래의 코드를 순차적으로 실행한다는 것이다.

이를 그럼 개선해보자.

 

두 번째 소스코드 

import kotlin.concurrent.thread

fun main() {

    // 콘솔 창에 입력받은 값을 그대로 출력 - 3번 반복하는 스레드 생성.
    val chatTh = thread(false) {
        repeat(3) {
            println("채팅을 입력해주세요 : ")
            val text = readln()
            println("\"${text}\"을/를 전송하였습니다. 남은 채팅 횟수 ${3 - (it + 1)}")
        }
    }

    // 10 - 1까지 카운트 다운하는 스레드 생성.
    val countTh = thread(false) {
        repeat(10) {
            println("카운트 다운 ${10 - it}")
            Thread.sleep(1000)
        }
    }

    // 입/출력을 담당하는 스레드를 실행
    chatTh.start()

    // 카운트 다운을 담당하는 스레드를 실행.
    countTh.start()

    val afterChatTh = thread(true) {
        chatTh.join()
        println("이용 가능한 채팅횟수를 다 사용하였습니다.")
    }

    val afterCountTh = thread(true) {
        countTh.join()
        println("카운트 다운이 완료되었습니다.")
    }

}

이번에는 과연 기대하는 대로 출력이 될까??

두 번째 소스코드의 결과

오... 원하는대로 카운트 다운, 반복되는 입/출력이 동시에 실행되면서..!
채팅 횟수를 모두 소진한 후 채팅관련 경고문구가, 카운트 다운이 완료된 후 카운트 다운 관련 경고문구가 출력되는 것을 확인할 수 있다.

 

이 소스 코드는 아래와 같은 순서로 동작한다.

chatTh 스레드 생성 -> countTh 스레드 생성 -> chatTh.start()를 통하여 별도의 스레드에서 chatTh를 실행 -> countTh.start()를 통하여 별도의 스레드에서 counthTh를 실행 -> afterChatTh 스레드를 생성과 동시에 별도의 스레드에서 실행 -> afterChatTh 스레드를 생성과 동시에 별도의 스레스에서 실행 -> 메인 스레드 종료.

첫 번째 소스코드와 두 번째 소스코드의 차이는 경고문을 출력하는 부분을 별도의 스레드에 넣은 것 뿐이다. 심지어 생성과 동시에 동작하도록 만들었다.(thread의 인자로 true를 주었기 때문) 하지만 이번에는 채팅, 카운트 다운 관련 경고문구가 즉시 출력되는 것이 아니라 해당 기능들이 완료된 후 출력되는 것을 볼 수 있다!! 이는 .join()메서드를 사용했기 때문이다. afterChatTh의 스레드 내부를 보면, chatTh.join()을 사용한 것을 볼 수 있다.

chatTh.join()은 chatTh 스레드가 끝난 후 아래의 코드들이 실행됨을 의미한다. 따라서 afterChatTh 스레드는 별도의 스레드에서 동작 중이지만 chatTh 스레드의 동작이 완료되어야 아래의 코드들이 실행이 된다! 기다리고 있는 것이다!


간단한 구현을 통하여 싱글 스레드, 멀티 스레드의 차이를 알아보았다..!! 쉽게 생각하면 싱글 스레드는 혼자 일하기 때문에 일을 순서대로 하는 것이고 멀티 스레드는 여러 명이 일하기 때문에 동시에 처리하는 것이라고 생각하면 된다. 
사실 이게 말이 쉽지 실제로 프로그램이 동작하는 것을 구현하면 예상과 다른 결과가 많이 나왔었는데 이번에 직접 기능들을 구현해보니 조금 더 이해가 수월했던 것 같다아아~~~

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기