반응형

코틀린에서 스레드 생성 및 실행하기

3 가지의 스레드 생성 방법이 모두 나와있는 전체 코드

import kotlin.concurrent.thread

fun main() {

    // 1번째 스레드 - Thread()를 상속받아 생성
    // testThread 생성
    val testTh = testThread()

    // 2번째 스레드 - Runnable을 구현하여 생성
    // Runnable 인터페이스를 구현한 testThreadWithRunnable 객체 생성
    val runnable = testThreadWithRunnable()
    // Thread의 인자로 runnable을 주어 스레드 생성.
    val testThWithRunnable = Thread(runnable)

    // 3번째 스레드 - thread() 함수를 이용하여 생성
    val simpleTh = thread(false) {
        println("this is simple thread ${Thread.currentThread().name}")
    }

    // name = 동작하고 있는 스레드의 이름을 출력 - 메인
    println("${Thread.currentThread().name}")

    // 첫 번째 스레드 실행.
    testTh.start()
    // 두 번째 스레드 실행.
    testThWithRunnable.start()
    // 세 번째 스레드 실행
    simpleTh.start()

}

// Thread를 상속받음
class testThread : Thread() {
    override fun run() {
        // name = 동작하고 있는 스레드의 이름을 출력
        println("this is testTh - ${name}")
    }
}

// Runnable 인터페이스를 구현
class testThreadWithRunnable : Runnable {
    override fun run() {
        // Thread를 상속받은 것이 아니라 Runnable을 구현한 것이기 때문에
        // 동작중인 스레드의 이름을 알기 위해선 Thread.currentThread().name 를 사용
        println("this is ThreadWithRunnableInterface - ${Thread.currentThread().name}")
    }
}

출력 결과

main
this is testTh - Thread-0
this is ThreadWithRunnableInterface - Thread-1
this is simple thread Thread-2

첫 번째 방법 - Thread 클래스를 상속받기

코틀린에서 Thread를 생성하고 사용하기 위한 방법들 중 첫 번째 방법은 Thread 클래스를 상속받아 run() 메서드를 오버라이딩 하는 것입니다! run() 메서드를 오버라이딩하고 스레드에서 실행하기를 원하는 코드를 run()메서드에 작성하면 됩니다. 
그 후 메인 함수에서 Thread클래스를 상속받은 클래스의 객체를 생성하고 start()를 사용하여 해당 객체를 스레드에서 동작하도록 만들 수 있습니다.

첫 번째 방법 소스코드

fun main() {

    // 1번째 스레드 - Thread()를 상속받아 생성
    // testThread 생성
    val testTh = testThread()

    // 스레드 실행.
    testTh.start()

}

// Thread를 상속받음
class testThread : Thread() {
    override fun run() {
        // name = 동작하고 있는 스레드의 이름을 출력
        repeat(5) {
            println("Count ${5 - it} - ${name}")
        }
    }
}

이 소스 코드는 메인스레드가 아닌 별도의 스레드(Thread-0)에서 5 ~ 1 까지 카운트 다운을 진행합니다.

출력 결과

Count 5 - Thread-0
Count 4 - Thread-0
Count 3 - Thread-0
Count 2 - Thread-0
Count 1 - Thread-0

두 번째 방법 - Runnable 인터페이스 구현

코틀린은 다중 상속을 허용하지 않기 때문에 위에서 설명한 방법인 Thread를 상속받는 클래스를 만들고 해당 클래스를 사용하면 다른 클래스를 상속받을 수 없게됩니다. 
Thread()를 상속받는 것 대신 Runnable 인터페이스를 구현한 객체를 Thread를 생성할 때 인자로 전달하여 사용할 수도 있습니다.

두 번째 방법 소스코드

fun main() {

    // 2번째 스레드 - Runnable을 구현하여 생성
    // Runnable 인터페이스를 구현한 testThreadWithRunnable 객체 생성
    val runnable = testThreadWithRunnable()
    // Thread의 인자로 runnable을 주어 스레드 생성.
    val testThWithRunnable = Thread(runnable)

    // 스레드 실행.
    testThWithRunnable.start()

}

// Runnable 인터페이스를 구현
class testThreadWithRunnable : Runnable {
    override fun run() {
        // Thread를 상속받은 것이 아니라 Runnable을 구현한 것이기 때문에
        // 동작중인 스레드의 이름을 알기 위해선 Thread.currentThread().name 를 사용
        repeat(5) {
            println("Count ${5 - it} - ${Thread.currentThread().name}")
        }
    }
}

출력 결과

Count 5 - Thread-0
Count 4 - Thread-0
Count 3 - Thread-0
Count 2 - Thread-0
Count 1 - Thread-0

첫 번째 방법과 동일한 출력 결과를 가집니다!


세 번째 방법 - thread() 메서드 사용

제가 보기에는 제일 간단한 방법인 것 같습니다..!! thread() 메서드를 이용하여 간단하게 스레드를 생성할 수 있습니다.
이 thread() 메서드는 인자로 Boolean값을 받는데, 이는 스레드 생성과 동시에 실행을 할 것이냐를 판단합니다. default는 true로 인자를 주지 않았을 경우에는 스레드를 생성과 동시에 실행하며 false를 인자로 주었을 경우에는 스레드를 생성만 하고 실행은 별도로 start() 메서드를 이용하여 실행시킬 수 있습니다.

예시는 start = false로 설정하여 별도로 스레드를 실행하도록 지정하였습니다.

세 번째 방법 소스코드

fun main() {

    // 3번째 스레드 - thread() 함수를 이용하여 생성
    val simpleTh = thread(false) {
        repeat(5){
            println("Count ${5 - it} - ${Thread.currentThread().name}")
        }
    }

    // 세번째 스레드 실행
    simpleTh.start()

}

출력 결과

Count 5 - Thread-0
Count 4 - Thread-0
Count 3 - Thread-0
Count 2 - Thread-0
Count 1 - Thread-0

마찬가지로 동일한 결과를 출력합니다.


스레드의 처리 순서

자 그럼 3번째 방법으로 총 3개의 스레드를 생성해보겠습니다.

소스코드

import kotlin.concurrent.thread

fun main() {

    // 첫 번째 스레드
    val firThread = thread(false) {
        repeat(5) {
            println("this is firThread ${Thread.currentThread().name}")
        }
    }
    // 두 번째 스레드
    val secThread = thread(false) {
        repeat(5) {
            println("this is secThread ${Thread.currentThread().name}")
        }
    }
    // 세 번째 스레드
    val thirdThread = thread(false) {
        repeat(5) {
            println("this is thirdThread ${Thread.currentThread().name}")
        }
    }

    // 첫 번째 스레드 실행
    firThread.start()
    // 두 번째 스레드 실행
    secThread.start()
    // 세 번째 스레드 실행
    thirdThread.start()

}

자 이렇게 총 3개의 스레드를 생성하고 순차적으로 실행하도록 코드를 구성하였습니다. 과연 출력 결과가 어떻게 나올까요?
첫 번째 스레드, 두 번째 스레드, 세 번째 스레드 이렇게 순차적으로 실행했으니 순차적으로 5씩 출력이 될까요??

출력 결과

엥? 굉장히 난잡한 순서로 출력이 된 모습을 확인할 수 있습니다.

왜 이런 결과가 나오게 되었을까요??

스레드의 동작 순서는 OS 스케쥴러가 진행하기 때문에 무엇이 먼저 얼마나 오래동안 진행될지는 알 수가 없습니다.
따라서 위의 코드와 동일한 코드를 작성하고 반복해서 코드를 실행해보면 계속해서 다른 결과가 나오는 것을 확인할 수 있습니다.

 

참고로 main 스레드에서 순차적으로 코드를 실행하다가 start()로 스레드를 실행하게 되면 별도의 스레드가 생성되고 생성된 스레드에서 해당 스레드가 별로도 돌아가게 됩니다.(run()이 동작)

 

그렇다면 우리가 원하는 순차적인 스레드 실행은 어떻게 할 수 있을까요?


스레드를 순차적으로 실행하기

join()함수를 사용하면 해당 thread의 작업이 모두 완료되기 전까지 blocking되어 대기 상태가 됩니다.
이 join() 함수를 사용하여 순차적으로 스레드를 실행할 수 있습니다.

예시 소스코드

import kotlin.concurrent.thread

fun main() {

    // 첫 번째 스레드
    val firThread = thread(false) {
        repeat(5) {
            println("this is firThread ${Thread.currentThread().name}")
        }
    }
    // 두 번째 스레드
    val secThread = thread(false) {
        firThread.join()
        repeat(5) {
            println("this is secThread ${Thread.currentThread().name}")
        }
    }

    // 세 번째 스레드
    val thirdThread = thread(false) {
        firThread.start()
        secThread.start()
        secThread.join()
        repeat(5) {
            println("this is thirdThread ${Thread.currentThread().name}")
        }
    }

    thirdThread.start()

}

출력 결과

위의 소스 코드는 아래와 같이 동작합니다.

firThread 생성 -> secThread 생성 -> thirdThread 생성 -> thirdThread가 start()로 호출되어 별도의 스레드에 할당 
-> firThread 가 start()로 호출되어 별도의 스레드에 할당 -> secThread가 start()로 호출되어 별도의 스레드에 할당
firThread, secThread, thirdThread가 이제 각각의 스레드에 할당되어 동작 
firThread는 정상 동작
secThread는 firThread.join()이 있으므로 firThread의 동작이 완료될 때까지 대기상태
thirdThread는 secThread의 동작이 완료될 때까지 대기상태
firThread의 동작이 완료되면 secThread의 동작이 시작, secThread의 동작이 완료되면 thirdThread가 동작

이처럼 join()을 사용하여 해당 스레드가 완료되기 전까지 대기 상태로 만들 수 있습니다!
ex) firThread.join() 를 호출하면 firThread의 동작이 완료될 때까지 해당 스레드는 대기 상태가 됩니다.


join()을 사용하며 timeout을 주기

위와 같이 join()을 사용하여 해당 스레드의 동작이 완료되기 전까지 대기 상태로 만들 수 있다는 것을 확인했습니다! 
하지만 해당 스레드에 오류가 생겨 무한히 반복되게 된다면 대기 상태도 무한히 유지될 것입니다. 
이를 해결하기 위하여 join()을 호출할 때 인자의 값으로 ms단위(1000 = 1초)의 시간을 전달하여 Timeout을 지정할 수 있습니다!
참고로, 인자를 사용하지 않고 join()으로만 사용한다면 default값은 0입니다!

예시 소스코드

import kotlin.concurrent.thread

fun main() {

    // 첫 번째 스레드
    val firThread = thread(false) {
        Thread.sleep(2000)
        repeat(5) {
            println("this is firThread ${Thread.currentThread().name}")
        }
    }
    // 두 번째 스레드
    val secThread = thread(false) {
        firThread.join(500)
        repeat(5) {
            println("this is secThread ${Thread.currentThread().name}")
            Thread.sleep(1000)
        }
    }

    // 세 번째 스레드
    val thirdThread = thread(false) {
        firThread.start()
        secThread.start()
        secThread.join(500)
        repeat(5) {
            println("this is thirdThread ${Thread.currentThread().name}")
        }
    }

    thirdThread.start()

}

출력 결과

Thread.sleep()은 해당 스레드를 sleep()의 인자로 주어진 수(ms단위)만큼 스레드를 대기 시킵니다.
ex) thirdThread에서 Thread.sleep(1000)를 호출하면 thirdThread는 1초간 대기 상태 후 작업 시작.

 

firThread는 2초를 쉬고 반복문을 5번 출력,
secThread는 firThread.join(500)을 사용했으므로, firThread가 종료될 때까지 대기 but, 0.5초가 지나도 firThread가 종료되지 않는다면 작업 시작,
thirdThread는 firThread와 secThread를 실행시킴. secThread가 종료될 때까지 대기 but, 0.5초가 지나도 secThread가 종료되지 않는다면 작업시작.
따라서 위의 사진과 같이 thirdThread가 가장 먼저(secThread가 가장 먼저 출력될 수도 있음 -- firThread, secThread가 동시에 실행되며 secThread는 firThread에 대한 0.5초의 Timeout을 thirdThread는 secThread에 대한 0.5초의 Timeout을 가졌기 때문.)
출력되고 secThread, firThread가 지연된 시간을 맞춰 출력되는 모습을 볼 수 있습니다!!

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