본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java] 병렬 프로그래밍 개요.

by 돼지왕 왕돼지 2012. 6. 19.
반응형
 

01. 개요.


- 스레드는 멀티프로세서 시스템의 능력을 최대한 끌어낼 수 있는 가장 쉬운 방법이며, 프로세서 개수가 늘어날수록 여러 작업을 동시에 실행하는 일이 더욱 중요하다.



1.1 작업을 동시에 실행하는 일에 대한 (아주) 간략한 역사.


- 프로세스는 각자가 서로 격리된 채로 독립적으로 실행되는 프로그램으로서 운영체제는 프로세스마다 메모리, 파일 핸들, 보안 권한 등의 자원을 할당한다. 프로세스끼리는 서로 통신을 할 수도 있는데, 소켓, 시그널 핸들러, 공유 메모리, 세마포어, 파일 등의 비교적 큰 단위의 다양한 통신 수단이 제공된다.

<세마포어>
 

- 동시에 실행할 수 있는 운영체제를 개발하게 된 몇 가지 요인
1. 자원 활용 : 입출력과 같이 외부 동작이 끝나기를 기다려야 하는 경우가 많은데, 기다리는 동안은 유용한 일을 처리하지 못한다.
2. 공정성 : 여러 사용자와 프로그램이 컴퓨터 내 자원에 대해 동일한 권한을 가질 수 있다. 저 작은 단위로 컴퓨터를 공유하는 방법이 바람직하다.
3. 편의성 : 각기 일을 하나씩 처리하고 필요할 때 프로그램 간에 조율하는 프로그램을 여러 개 작성하는 편이 더 쉽고 바람직하다.

- 스레드는 메모리, 파일 핸들과 같이 프로세스에 할당된 자원을 공유한다. 하지만 각 스레드는 각기 별도의 프로그램 카운터, 스택, 지역 변수를 갖는다. 또한, 프로그램을 스레드로 분리하면 멀티프로세서 시스템에서 자연스럽게 하드웨어 병렬성을 이용할 수 있다.

- 스레드를 lightweight process 라고 부르기도 하며, 현대 운영체제의 대부분은 프로세스가 아니라 스레드를 기본 단위로 CPU 자원의 스케줄을 정한다. 한 프로세스 내 모든 스레드는 같은 변수에 접근하고 같은 힙(heap)에 객체를 할당한다.
 
 


1.2 스레드의 이점.


1.2.1. 멀티 프로세서 활용


- 여러 개의 스레드를 사용하면 프로세서가 하나라 해도 처리 속도를 높일 수 있다.


1.2.2. 단순한 모델링


- 복잡하면서도 비동기적인 작업 흐름을 각기 별도 스레드에서 수행되는 더 단순하고 동기적인 작업 흐름 몇 개로 나눌 수 있다. 이런 작업 흐름에서는 특정한 동기화 시점에서만 상호 작용이 발생한다.


1.2.3. 단순한 비동기 이벤트 처리.


-  이전의 운영체제는 하나의 프로세스가 생성할 수 있는 스레드 개수가 상대적으로 제약이 심했다. 그래서 유닉스 시스템의 select 나 poll 시스템 콜처럼 효율적인 다중화 I/O 수단이 개발되었고, 표준 자바 API 에도 대기 상태에 들어가지 않는 I/O를 지원할 수 있도록 java.nio 같은 패키지가 추가됐다. 하지만 시간이 지나며 운영체제에서 더 많은 스레드를 지원할 수 있게 됨에 따라, 클라이언트마다 스레드를 하나씩 생성하는 일이 현실적인 경우가 많아지고 있다.


1.2.4 더 빨리 반응하는 사용자 인터페이스





1.3. 스레드 사용의 위험성.


- 자바 자체에 스레드 관련 기능이 내장되어 있다는 점은 양날의 칼이다.지금은 개발자라면 대부분 스레드 안정성 (thread safety) 에 대해 기본적으로 잘 알고 있어야 한다. 그렇지 않으면 독이 된다.

1.3.1. 안정성 위해 요소


- 프로그래머들이 잘못 생각하기 쉬운 것이 ++ 연산이다. value++ 연산은 하나의 연산인 것 같지만, assembly level 로 내려가면 값을 읽고, 읽은 값에 1을 더하고, 그 결과값을 다시 기록하는 3개의 별도의 연산으로 구성되어 있다. multi-thread 프로그래밍에서는 항상 "최악의 경우"를 기본으로 생각하여야 한다.

- Thread safe 한 함수의 경우 표준 annotation @ThreadSafe 를 표기해주는 것이 좋다. 사용자는 멀티 스레드 환경에서 안전하다는 것을 보장받을 수 있고, 해당 함수 개발자는 해당 함수가 계속 thread-safe 해야 한다는 것에 주의할 수 있다.

- 스레드는 서로 같은 메모리 주소 공간을 공유하고 동시에 실행되기 때문에 다른 스레드가 사용 중일지도 모르는 변수를 읽거나 수정할 수도 있다. 이는 상당히 편리한데, 다른 스레드 간 통신 방식보다 데이터 공유가 훨씬 쉽기 때문이다. 하지만 이 점은 위험 요소이기도 하다. 

- race condition (경쟁 조건) 을 막기 위해서는 동기화가 필요하다. 동기화를 하지 않으면 컴파일러, 하드웨어, 실행 환경 각각에서 명령어의 실행 시점이나 실행 순서를 상당히 자유롭게 조정할 수 있다.


1.3.2. 활동성 위험.


- multi-thread 프로그래밍에서는 항상 스레드 안전성 문제를 신경  써야 한다. 안전성은 양보할 수 없다. 멀티스레드 프로그램에서는 단일 스레드 프로그램에서 나타나지 않는 추가적인 "활동성(liveness)" 장애가 발생할 수 있다. 안전성이 "잘못된 일이 생기지 않는다" 는 것을 뜻하는 반면, 활동성은 "원하는 일이 결국 일어난다" 는 보완적인 목표에 관한 것이다. 어떤 작업이 전혀 진전되지 못하는 상태에 빠질 때 활동성 장애가 발생했다고 할 수 있다. ( dead lock, starvation, live lock 이 그 예. )  


1.3.3. 성능 위험.


- 스레드가 많은 프로그램에서는 컨텍스트 스위칭( 다른 스레드가 실행될 수 있게 스케쥴러가 현재 실행 중인 스레드를 잠시 멈출 때 )이 빈번하고, 그 때문에 상당한 부담이 생긴다. 즉, 실행중인 컨텍스트를 저장하고 다시 읽어들여야 하며, 메모리를 읽고 쓰는 데 있어 지역성(locality)이 손실되고, 스레드를 실행하기도 버거운 CPU 시간을 스케쥴링하는 데 소모해야 한다. 스레드가 데이터를공유하는 경우에는 동기화 수단도 사용해야 하는데, 이런 동기화는 컴파일러 최적화를 방해하고, 메모리 캐시를 지우거나 무효화하기도 한다. 공유 메모리 버스에 동기화 관련 트레픽을 유발하기도 한다. 이런 요인은 성능 측면에서 추가적인 손실을 유발한다.




1.4. 스레드는 어디에나.


- 모든 자바 프로그램은 기본적으로 스레드를 사용한다. JVM을 시작시키면 main 메소드를 실행할 main thread 뿐 아니라 가비지 컬렌션이나 객체 종료( object finalization )과 같은 JVM 내부 작업을 담당할 스레드도 생성한다. AWT 와 스윙 사용자 인터페이스 프레임웍은 사용자 인터페이스의 이벤트를 관리할 스레드를 생성하며, Timer 는 대기 중인 작업을 실행할 스레드를 생성한다.  

- 사용하는 컴포넌트만 스레드에 안전해야 하는 것은 아니다. 해당 컴포넌트가 실행되는 과정에서 접근하는 코드 경로에 포함된 컴포넌트는 모두 마찬가지다. 스레드 안정성에는 전염성이 있다.

타이머 : TimerTask 에 지정된 작업이 프로그램이 아닌 Timer 가 관리하는 스레드에서 실행되기 때문에 순차적인 프로그램을 복잡하게 만들 수도 있다. TimerTask 가 접근하는 갹체 자체를 스레드에 안전하게 만드는 것이 가장 쉬운 방법이다. 즉, 공유된 데이터 객체 내부에 스레드 안전성을 캡슐화하는 것이다.

서블릿과 JSP : 서블릿은 스레드에 안전해야 한다.

원격 메소드 호출(Remote Method Invocation ): RMI 는 다른 JVM 에서 실행 중인 객체의 메소드를 호출할 수 있게 해 준다. RMI로 원격 메소드를 호출하면, 메소드 인자는 바이트 스트림으로 변환(marshaled)되고 네트워크를 통해 원격 JVM으로 전달된다. 원격 JVM에서는 원래대로 변환되어(unmarshaled) 원격 메소드에 인자로 전달된다. RMI코드가 원격 객체를 호출할 때 동일한 원격 객체에 같은 메소드가 여러 RMI 스레드에서 동시에 호출 될 수 있다. 원격 객체는 스레드 안전성에 대한 두 가지 위험에 대비해야 한다. 먼저 다른 객체와 공유될 수 있는 상태에 접근할 때 적절히 조율해야 하지만, 원격 객체 자체의 상태에 접근할 때도 마찬가지다. 

스윙과 AWT : GUI 어플리케이션은 본질적으로 비동기적으로 동작한다. 이벤트 스레드에서만 GUI 컴포넌트에 접근할 수 있게 제한하는 방법으로 스레드 안전성을 얻는다. 



반응형

댓글