티스토리 뷰

android

스레드간 데이터 통신

길 킴 2021. 2. 16. 11:54
728x90

스레드간 통신을 알려면 먼저 스레드가 어떻게 구성되고 돌아가는지 알아야 한다. 

Thread 구성 요소

  • Thread: 프로세스 내의 세부 작업 단위
  • Handler :아래의 2가지 역할을 한다. (다재 다능한 놈)
    • Message, Runnable Task 를 Message Queue로 전달
    • Message, Runnable Task 를 실행
  • Looper : Message Queue에서 Handler로 전달
  • Message Queue : Message, Runnable Task를 저장하는 Queue

생각보다 복잡하다. 

안드로이드 개발에서 가장 많이 하는 실수중에 하나가 Thread를 만들고 그 안에서 UI 변경 작업을 실행하는 경우이다. 
바로 에러가 터진다!

Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
 			myTextView.setText("터지냐");
            }
      }
});

 아래는 정상 동작한다. 

Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
 
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                myTextView.setText("안터진다!");
                            }
                        });
                 }
            }
});

 

 

위의 스레드의 작동 원리를 이해하면 금방 이해가 갑니다. 

UI 작업은 UI 스레드에서만 작업해야 합니다. 왜냐하면 여러 스레드가 동시에 같은 UI 객체를 건드리게 되면 Thead Safe 하지 않기 때문이죠. 그래서 외부 Thread에서 main 스레드의 핸들러에게 UI 작업 코드를 전달해줘야 합니다. 그러면 handler가 Message Queue에 작업을 넣고, 순차적으로 Looper가 하나씩 handler에게 실행요청할 테니 Thread Safe 합니다. 

runOnUiThread() 함수는 Main Thread의 Message Queue에 바로 작업을 전달하는 역할을 하는 함수입니다. 

 


그러면 스레드간 통신은 어떻게 하면 될까? 

 1. 핸들러에 변수를 전달하는 방법 (위의 스레드 개념을 활용하는 방법)

public class A extends AppCompatActivity { 
	Handler handler;
    
    @Override
    protected void onCreate(Bundle savedInstance) {
    	...
        
        new Handler()
        {
        	@Override
            public void handleMessage(Message msg) {
            
            	val num = mas.arg1;
                // 처리 코드 작성
                
            }
         };
         
         new Thread(new B()).start();
    }
}

class B implements Runnable {
	
    @Override
    public void run() {
    
    	Message message = Message.obatin();
        
        message.arg1 = 100;
        handler.sendMessage(message);
    }
}
    
    
    

해당 코드는 개념적인 코드이다. 실제 업무에선 저렇게 사용하지 않는다. 

왜냐하면, 변수를 받고 처리하는 코드(handleMessage)에 많은 양의 연산 코드가 들어가면 멀티 스레딩 작업의 효과가 떨어지기 때문이다. 
한마디로 버벅댄다. 

handler가 처리하는 코드에 연산코드가 들어가면 안된다. UI 변경 코드만 들어가는 것이 이상적이다. 

 

2. 지역(전역) 변수 참조하는 코드 

실제 업무에서는 당연히 2번의 방법으로 많이 사용한다. 

스레드를 생성할 액티비티에 변수를 놓고, 그걸 스레드들이 건드리는 방법이다. 

여기서 발생하는 문제점은 자원의 원자성이 보장되는가 이다. 

  • 자원을 처리중일 경우에 다른 스레드가 동시 접근하는 경우 
  • 자원을 가져오는 시점에, 다른 스레드의 작업이 처리가 끝나지 않은 경우 

 

해결 방법은 간단하다. 

  1. 동시에 접근하여 Write 처리를 하는 코드에 Synchronized 처리를 한다. 
  2. 동시에 변수를 Read 하는 경우 @Volatile Annotation을 활용한다. 

Synchronize는 해당 코드 블럭의 원자성을 유지시켜 준다. 즉 다른 스레드가 사용중일 때, 다른 스레드는 끝날 때 까지 기다린다. 

@Volatile annotation은 작업을 처리중인 변수가 끝날 때까지 기다리고 바뀐 값을 가져올 수 있다. 이것 또한 원자성을 유지시켜 준다. 


아래 Singleton 코드는 Google에서 추천하는 코드로, 자원의 원자성 유지하는 방법을 가이드해주는 좋은 코드이다. 

class Singleton private constructor(val name: String) {
    companion object {
    	@Volatile private var INSTANCE: Singleton? = null
        
        fun getInstance(name: String): Singleton = 
        	INSTANCE ?: synchronized(this) {
            	INSTANCE ?: Singleton(name).also { INSTANCE = it }
            }
     }
}     

 


<참고자료>
jyami.tistory.com/112

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함