고전적인 동기화 문제들
1.
유한 버퍼 문제
•
n 개의 버퍼로 구성된 풀(pool)에서 생산자는 버퍼에 데이터를 저장하고(생산) 소비자는 데이터가 저장된 버퍼를 읽는다(소비). mutex나 이진 세마포어를 통해 상호 배제를 구현한다.
2.
Readers-Writers 문제
•
하나의 데이터베이스가 다수의 병행 프로세스 간에 공유된다면, 프로세스 중 일부는 읽기만하고 또 다른 일부 프로세스는 읽고 쓰는 것을 모두 한다. reader가 데이터를 읽는 동안 다른 reader가 동시에 데이터를 읽어도 아무런 문제가 발생하지 않는다. 하지만 writer가 데이터베이스에 데이터를 쓰는 동안 다른 reader나 writer가 데이터를 조작하면 데이터의 정합성이 깨질 수 있다. 상호 배제를 통해 reader가 읽는 동안 다른 reader가 접근할 수 있지만 writer가 쓰는 동안은 다른 reader 및 writer가 접근하지 못하게 구현한다.
3.
식사하는 철학자 문제
•
생각하고 먹고 자기만 하는 철학자들 n 명이 원형 탁자에 앉아있고 각 철학자들 사이에는 젓가락이 1개씩 놓여있다. 철학자가 식사를 하기 위해서는 좌측과 우측에 있는 젓가락을 모두 집어야하고, 식사를 마치면 젓가락을 다시 양쪽에 내려놓는다.
동기화의 대체 방안들
1.
트랜잭션 메모리
•
트랜잭션 메모리는 데이터베이스 이론 분야에서 출발한 개념으로, 읽기와 쓰기 연산의 원자적인 연속된 순서이다.
•
하나의 트랜잭션의 모든 연산이 완료되면 트랜잭션은 확정(commit)되고, 모든 연산이 완료되지 않고 중간에 오류가 발생한다면 그 시점까지 진행된 모든 연산은 취소되고 트랜잭션 시작 전 상태로 되돌려야(roll-back) 한다.
•
트랜잭션 메모리는 하드웨어나 소프트웨어 구현 가능하다.
◦
소프트웨어 트랜잭션 메모리(STM, Software Transaction Memory)는 특별한 하드웨어 필요 없이, 트랜잭션 블록 안에 컴사 코드를 삽입함으로써 동작한다. 이 검사 코드는 컴파일러에 의해 삽입되어 명령문이 동시에 실행될 수 있는 부분과 저수준 락킹이 필요한 부분을 검사한다.
◦
하드웨어 트랜잭션 메모리(HTM, Hardware Transaction Memeory)는 각 프로세서 캐시에 존재하는 공유 데이터의 충돌을 해결하고 관리하기 위해 하드웨어 캐시 계층 구조와 캐시 일관성 프로토콜을 사용하여 동작한다. HTM은 코드 계측이 필요 없기 때문에 STM보다 오버헤드가 적지만, 트랜잭션 메모리를 위한 캐시 계층 구조와 캐시 일관성 프로토콜을 지원하는 하드웨어로 변경해야한다.
2.
OpenMP
•
OpenMP는 컴파일러 디렉티브와 API로 구성되는데, 컴파일러 디렉티브 #pragma omp parallel 이후의 코드들은 병렬 구역으로 인식되어 시스템의 처리 코어 개수만큼의 스레드에서 실행된다.
•
OpenMP는 #pragma omp ciritical이라는 디렉티브를 제공하는데, 이 이후 나오는 코드를 임계구역으로 지정하여 하나에 한번의 쓰레드만 실행할 수 있도록 한다. 이를 통해 스레드 간의 경쟁 조건을 발생시키지 않는 것을 보장할 수 있다.
•
OpenMP에서 컴파일러 디렉티브를 사용하는 것은 표준 mutex 락보다 쉽게 사용할 수 있지만, 개발자가 직접 경쟁 조건을 발견해야하고 공유 메모리를 직접 컴파일러 디렉티브로 보호해주어야 한다는 게 단점이다. 또한 임계구역 컴파일러 디렉티브는 mutex 락처럼 동작하기 때문에 교착 상태가 발생할 수 있다.
3.
함수형 프로그래밍 언어
•
C, C++, Java와 같은 프로그래밍 언어를 명령형 혹은 절차형 프로그래밍 언어라 부르는데, 함수형 프로그래밍 언어는 이와 달리 순수함수를 조합하여 원하는 동작을 만드는 방식이다.
•
함수형 프로그래밍 언어에는 부작용(side effect)이라는 키워드가 있는데, 아래와 같은 변화 또는 변화가 발생하는 작업을 의미한다.
◦
변수의 값을 변경
◦
자료 구조를 제자리에서 수정
◦
객체의 필드값 설정
◦
예외나 오류가 발생하여 프로그램이 중단
◦
콘솔이나 파일 I/O가 발생
•
함수형 프로그래밍 언어에서 순수 함수는 위의 부작용을 제거한 함수들을 의미하며, 메모리나 I/O 관점에서 side effect가 없고 함수의 실행이 외부에 영향을 미치지 않는 함수들을 의미한다.
•
이런 순수 함수의 장점이 변수의 상태 변경을 허용하지 않고 독립적이기 때문에 Thread에 안정성을 부여하고 동기화를 해결할 수 있다.
•
함수형 프로그래밍 언어로는 Erlang과 Scala, Haskell 등이 있고, Java의 stream()을 통해서 함수형 프로그래밍을 구현할 수 있다.
public class WordProcessTest {
private final List<String> words = Arrays.asList("TONY", "a", "hULK", "B", "america", "X", "nebula", "Korea");
@Test
void wordProcessTest() {
String result = words.stream()
.filter(w -> w.length() > 1)
.map(String::toUpperCase)
.map(w -> w.substring(0, 1))
.collect(Collectors.joining(" "));
assertThat(result).isEqualTo("T H A N K");
}
}
Java
복사