[Android] Coroutine에 관해서

fanthasium·2022년 12월 6일
0

개발을 하다보면 언젠가 한번은 내 스레드에서 무슨 일이 일어나고 있는지 궁금할 때가 생긴다..(사실 아니긴 하지만..)
나의 경우 안드로이드 ANR에러를 마주쳐 스레드 공부의 필요성을 느꼈다

*출처 : 게시물에 담기엔 너무 방대해 개념들은 링크로 걸어 놨습니다.


코루틴을 알기전 왜? 코루틴을 쓰는가에 대해 알 필요가 있다!

안드로이드에선 UI를 단일 스레드방식으로 프로세스를 구축한다.

단일 스레드란? :하나의 응용 프로그램이 하나의 실행흐름을 만든다 쉽게 말해, 한번에 한가지 일 만 한다는 뜻이다

어? 그럼 프로세스 하나에 여러 스레드가 있음 일처리를 더 잘 해줄수 있지 않나? 하는 의구심이 (생길수도 안 생길수도) 있겠지만 이러한 멀티 스레드를 사용하지 않는 이유는 Android 프로세스 및 스레드 개요 에서 말 하듯 UI(main)스레드가 인터페이스 상호작용에서 필요한 이벤트를 처리해주기에 경합,교착상태가 일어나선 안되기 때문이다.
- -> 멀티 스레드는 데이터와 힙 영역을 공유하기 때문에 다른 스레드에서 사용 중인 변수나 자료구조에 접근하여 엉뚱한 값을 읽어오거나 수정할 수 있어 교차,경합상태가 날수 있음

또한, 이러한 이유 때문에 당연히 UI쓰레드는 비동기적 처리가 어려워진다!!!
이것이 우리가 Coroutine을
사용하는 이유다.

(**그래도 이해가 안 간다면 멀티,싱글 스레드에 대해 공부해보자)
[싱글 vs 멀티 스레드]


Coroutine이란?

코루틴의 개념

우선 Coroutine이란 thread에서 발생하는 이벤트를 동시성으로 , 비동기적으로 처리해주며 이를 obJect단위로 작업을 해줍니다.
비동기적 처리를 위해선 context switching 방법으로 프로세스(A)를 중지하고 다른 프로세스(B)의 PCB정보를 바탕으로 프로세스(B)를 실행하는 구조를 짤 수 있다.

  • 그러나 context switching 여러개의 스레드를 사용하기에 싱글스레드에선 부적합한 방법이다. 때문에 Coroutine에선 Programmer Switching을 하여 코딩을 통한 switching 시점 을 JVM heap영역에서 따로 정할 수 있다. ( 아래 자료처럼 ) 그렇기에 Concurrency(동시성)또한 보장 할 수 있는 것!

    이미지 출처

Andorid에서 동시성은 어떻게 보장되나?


이미지 출처
싱글 스레드라면 작업 후 block하고 다음작업을 진행하는 형태지만 코루틴을 사용하면 시각화된 자료처럼
Thread에 경량 스레드가 있는 것과 같아 하나의 Thread에서 여러 작업을 해주기에 blocking 상태를 늦춰 사용 가능하다.

  • 안드로이드에서는?
    • UI스레드는 인터페이스를 IO스레드는 데이터 처리 를 각각 해준다.
      그리고 UI에서 보여줄 내용을 IO에서 따로 처리하여 화면이 멈춰 있는 (block 돼 있는) 상황을 지연시킨다.
      이러한 과정은 CoroutineScope.Dispatcher로 스레드를 나눠주기에도 있지만, 결국은 UI에 보여질 여러 정보들이 Coroutine으로부터 분리 돼 작업하기에 가능한 것이다.
      (이는 컴퓨터가 연산속도가 빨라 사실은 따로 하고 있지만 동시에 한것 처럼 보이는 것)

kotlin Coroutine

Android의 Kotlin 코루틴

사용방법:

app수준 모듈에 의존성 추가
 //coroutine
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

Activity에

CoroutineScope(Dispatchers.Main).launch or async
CoroutineScope(Dispatchers.IO).launch or async
CoroutineScope(Dispatchers.Default).launch or async

로 스레드를 분리해 사용할 수 있다.
여기서 Dispatchers의 역할은 코루틴의 스레드풀을 받아서 배분해주는 것이다

  • Dispatchers.Main : UI 구성하는 스레드를 메인으로 사용 하는 부분에서 사용된다.
    **(Ul 컨트롤 하는 함수를 다른 곳에서 사용하면 " Only the original thread " 에러가 나니 궁금하면 테스트해보쟈!)**

  • Dispatchers.IO : 네트워크, 디스크 사용 할때 사용합니다. 파일 읽고, 쓰고, 소켓을 읽고, 쓰고 작업을 멈추는것에 최적화되어 있다.

  • Dispatchers.Default : CPU 사용량이 많은 작업에 사용합니다. 주 스레드에서 작업하기에는 너무 긴 작업 들에게 사용됨.

    • 동시작업이 가능한 갯수는 cpu의 코어수
  • Dispatchers.Unconfined 는 다른 Dispatcher 와 는 달리 특정 스레드 또는 특정 스레드 풀을 지정하지 않으며, 일반적으로는 사용하지 않고 특정 목적을 위해서만 사용된다.

코루틴을 제어하기 위한 키워드

  • launch, async: Dispatcher에 Coroutine을 붙이는 작업

    • launch: 새로운 코루틴을 시작 o, Job의 반환타입을 가짐, 결과 반환x
    • async: 새로운 코루틴을 시작 x, Defered 반환타입을 가짐, 결과 반환o
  • runBlocking : 스레드를 차단하여 runBlocking() 함수로 시작된 코드블록의 작업이 스레드를 혼자 점유함.
    ->이렇게 독점으로 스레드 운영하면 코루틴의 장점이 사라짐
    따라서 coroutineScope() , withContext() 함수를 사용해 일시중단을 하는게 효과적임

  • Job, Deferred ..


**코루틴은 이 페이지에서 끝낼 내용이 아니기에 관련 내용은 따로 기재하겠습니다**

Test


//버튼 on off
   CoroutineScope(Dispatchers.Main).launch {
            btn.setOnClickListener {
                times ++
                if(times % 2== 1){
                    Log.e("??","${times % 3}")
                    tRue = false
                    Log.e("Boolean: Main", "${tRue}")
                }else{
                    tRue = true
                    Log.e("Boolean: Main", "${tRue}")
                }
                Log.e("count : Main", "${count}")
                txtView.text = "${count}"
            }

        }

        countBtn.setOnClickListener {
        CoroutineScope(Dispatchers.Default).launch {
                Log.e("Boolean: Default", "${tRue}")

                while (!tRue) {
                    count++
                    delay(1000)
                    Log.e("multiple 10", "${count * 10}")
                }
               
            }
        }

UI의 이벤트를 처리하는 구문은 Main(UI)thread에 그러나 여기선 Default에 넣어도 되는데

이런식으로 만들어 로그도 찍어보고 해보자!! 이때 여기서 while이 무한루프에 빠지게 된다면
안드로이드 "ANR" 접하게 될것이다
해당 에러는 UI thread에서 데이터는 처리되고 있지만 그 과정이 지연돼 화면이 멈춘 것 처럼 보이는 상황이 5초이상 지연되면 응답대기에러를 내는 것이다. 이에 대한 ERROR는 안드로이드 공식문서에 나와 있다

thread 확인방법

 btn1.setOnClickListener {
        CoroutineScope(Dispatchers.Main).launch {
            Log.e("id", "${Thread.currentThread().id}")
            Log.e("name", Thread.currentThread().name)
        }
    }

       btn2.setOnClickListener {
            CoroutineScope(Dispatchers.Default).launch {
                Log.e("id", "${Thread.currentThread().id}")
                Log.e("name", Thread.currentThread().name)
            }
        }
          btn3.setOnClickListener {
            CoroutineScope(Dispatchers.IO).launch {
                Log.e("id", "${Thread.currentThread().id}")
                Log.e("name", Thread.currentThread().name)
            }
        }

각각 어떤 스레드에서 사용되는지 확인해 봐도 좋으다..!

profile
디그다 디그다 (끙챠끙챠)

0개의 댓글