본문 바로가기

iOS/Concurrency

[Swift] Concurrency(동시성), parallelism(병렬성) 개념 탐구하기 | NO1. GCD

 

안녕하세요.

Swift's modern concurrency를 공부하기 이전에 공부한 개념 중 Concurrency와 parallelism에 대해 정리하려고 합니다.

 

1. what is parallelism

병렬성(parallelism)은 multi core에서 여러 개의 thread를 실행합니다. multi thread가 동시에 실행됩니다. Cpu에 있는 각각의 코어는 한  개 이상의 thread를 포함합니다. 이런 코어가 동시에 실행되는 것을 병렬성이라 합니다. data parallelism, task parallelism으로 구분됩니다.

각 라인을 task로 칭한다면, 특정 time에 core1, core2, core3의 한 개 또는 여러 개의 thread가 실행되면서 task들이 동시에 수행됩니다.

 

2. what is concurrency

동시성(Concurrency)은 병렬성(Parallelism)과 비슷한 의미 같아 보이지만 사실 다릅니다. 동시성은 single or multi core에서 concurrency를 실행할 수 있습니다. cpu의 single core를 예로들 때 task가 여러 개가 있다면 time slicing을 통해 특정 시간마다 다른 task가 실행됩니다.

A task가 실행 ~ 중지 -> B task 실행 ~ 중지 ... 각각의 task가 time slicing의 특정 시간대에 맞춰 번갈아가면서 실행됩니다. 이때 task와 다른 task 간 context switch가 발생합니다.

 

context switch?

서로 다른 프로세스(실행중인 프로그램== task)가 실행되기 위해선 context switch를 통해 실행 될 서로 다른 프로세스가 각각의 프로세스 메모리 영역을 올리게 됩니다. 프로세스는 메모리가 모두 독립적으로 존재합니다.(operating system process part ) 누가 context switch를 할까요? CPU가 합니다.. task가 서로 수행하려고 한다면 context switch는 자주 발생합니다. 그렇게 된다면 진짜 0.0001초 실행됬지만 다른 프로세스가 실행하고 싶다고 해서 프로세스의 상태 정보(어디까지 실행했나,process id 등- PCB)를 저장하고 저장 중이었던 다른 프로세스를 다시 실행합니다. cpu만 일을 계속합니다. 오버헤드가 발생합니다. 그리고 각 프로세스는 별도의 공간에서 실행됩니다. == 한 프로세스에서 다른 프로세스의 메모리 영역에 접근이 불가능 합니다.

 

blocking, starvation, race condition등 Concurrency를 사용하면 여러 돌발상황이 발생합니다. 그중 deadlock 또한 Concurrency 사용 시 이슈 중 하나입니다. precessor resource는 한정되어 있기 때문에 누군가 독식하거나? 반대로 서로 양보했다가 끝도 없이 양보하게 되는 상황이 발생됩니다. 

 

그럼에도 장점은? 더 많은 수행능력을 갖출 수 있다는 점입니다.

 

https://www.kodeco.com/28540615-grand-central-dispatch-tutorial-for-swift-5-part-1-2

parallelism은 Thread1, Thread2가 동시에 수행됩니다. 하지만 Concurrency를 보면 Thread1, Thread2가 context switch를 통해 번갈아가면서 수행됩니다. 물론 cpu는 정말 빠르기 때문에 동시에 처리되는 것처럼 보일 수 있습니다.

 

single core의 경우 time-slicing을 통해 context switch를 하면서 서로 다른 task를 실행합니다. 하지만 multi core의 경우 time-slicing없이 여러개의 task를 각각의 Thread에서 parallelism하게 실행합니다.

 

3. Concurrency problem?

위에서 언급했지만 다시 한번 더 정리하겠습니다.

 

GCD나 NSOperationQueue, NSThread, async/await .. 동시성 프로그램을 위해 여러 개의 thread를 사용하게 됩니다. 여러 개의 thread(multi threading)를 사용하면 동시에 많은 task를 할 수 있어 편리하지만 문제가 발생할 수 있습니다. 공유자원!! 동시에 공유자원에 잘못 접근하게 된다면 자원의 상태가 이상하게 바뀝니다.

 

  •  Race conditions
  •  Deadlock
  •  Priority inversion

 

Race conditions

두 개 이상의 프로세스가 공유 자원을 concurrent하게 읽거나 쓸 때, 그 실행 결과가 같지 않고 달라지는 상황을 말합니다. 두 개의 쓰레드가 하나의 자원을 서로 사용하려고 경쟁하는 상황입니다. Top-down으로 실행되는 코드의 결과는 항상 같아야 하는 로직임에도 불구하고 동시성을 사용하게 되면 어느 task가 먼저 끝날지 모르기 때문에 의도치 않은 결과가 초래됩니다.

 

Deadlock

task1은 task2가 끝나야만 수행될 수 있고 반대로 task2는 task1이 끝나아만 수행될 수 있습니다. task1과 task2는 하염없이 기다립니다. 그 예로 dispatchQueue의 main에서 synchronously 수행을 할 경우입니다. 자세한 상황과 이유는 zedd님의 블로그를!! 

https://developer.apple.com/documentation/dispatch/dispatchqueue/

 

Priority inversion

GCD는 QoS(Quality of Service)가 있습니다. 특징이 있는데 높은 우선순위의 qos 타입과 낮은 qos타입의 task가 있을 때 낮은 qos타입의 Task에 system resource가 할당 되어있는 공유자원을 높은 우선순위의 qos가 빼았아 가서 낮은 우선순위 qos 타입의 task는 실행이 안되는 것입니다.

그 이유는 높은 우선순위의 task는 빨리 끝나야 사용자와 상호작용하기 수월하기 때문입니다.(qos에 관한 내용)

https://developer.apple.com/videos/play/wwdc2015/718/

 

4. When use concurrency?

C언어로 테트리스를 만든 적이 있습니다. timer가 지남에 따라 동작하도록 만들었습니다. 큰 흐름은 아래와 같습니다.

테트리스 블럭이 보드의 맨 위에서 생성되었을 때가 기준입니다.

 

1. 사용자가 누른 키보드의 input

 

- 조건 체크 함수 시작!

2. 스페이스바를 눌렀는가? 조건 체크 -> 블럭  위치 수정 블럭 실행 (return)

3. 좌 or 우 방향키가 입력했는가? 조건 체크 -> 보드의 양 옆에 닿았는가? -> 아닐 경우 한 칸 좌로 블럭 위치 수정 (return)

4. 아래키를 눌렀는가? 조건 체크 -> -> 땅이나 블럭에 쌓였는가? -> 블럭 위치 수정 블럭 실행 (return)

5. 회전키를 입력했는가? 조건 체크 -> 보드의 양 옆에 닿았는가?? or 보드의 바닥에 닿거나 블럭에 쌓이거나 중첩되는가? -> 블럭 위치 수정 블럭 실행 (return)

- 조건 체크 함수 끝

 

6. 2,3인 경우 -> 블럭 한 칸 아래 블럭위치 수정 블럭 실행(자동 한 칸 내려가기)

7. prev 블럭 위치는 화면에서 지우고 업데이트된 블럭 위치 화면에 갱신

 

1...7까지의 반복이 블럭이 한 칸 아래로 or 한 번에 끝가지 내려갈 때 수행되는 한 flow입니다.

 

블럭단위 코드 내에서 컴파일러는 위에서부터 아래로 코드를 실행합니다. timer가 지남에 따라 1부터 순차적으로 실행됩니다. 1은 반드시 실행되야 합니다. 중요한 것은 2,3,4,5인 경우 (2,3일 땐 6번 실행) 이 조건들 중 한 개가 실행됩니다. 2~5번 로직은 timer가 지남에 따라 순차적으로 실행됩니다. 

 

2,3,4,5의 조건 체크 코드는 true false가 정말 빨리 실행될 정도로 간단한 코드입니다. 만약 각각의 조건 체크 수행 시간이 적어도 1초가 걸린다고 생각하겠습니다. 블럭의 상황을 판단하는 조건 체크가 최악의 경우 2->3->4->5번째 조건 체크에 도달할 수 있습니다. 5초 후에 블럭이 한 칸 내려갈 것입니다. 즉, 최악의 경우로 5초에 한 번씩 블럭이 한 칸씩 내려갑니다. 정말 느려지고 게임을 하는 상대방의 입장에선 바로 지워버릴 것입니다.

 

2,3,4,5번 중 한 개의 조건만 실행이 됩니다. 각각의 조건 체크 -> 특정 로직 수행은 독립적인 코드입니다.

 

사용자가 블럭 상태를 변경하기 위한 이벤트는 1번을 통해 받아야 하고 7번을 통해 변경된 상태가 화면에 보여야 합니다(반드시 실행). 그러나 2,3,4,5의 경우 한 true 조건을 찾고 그에 맞는 로직을 수행하면 됩니다. (순차적으로 탐색할 경우 최대 4초가 걸리지만..)

 

2,3,4,5의 조건 탐색을 동시에 한다면 최악의 경우 적어도 1초밖에 걸리지 않고 1초에 한 번씩 내려가는 기능을 구성할 수 있습니다. 적어도 4초보다 훨씬 빠르게 느껴질 것입니다. 어떻게 동시에 조건 체크를 할 수 있는 코드를 구현할까요?

thread를 이용해서 여러 개의 일을 동시에 실행하는 것입니다. C언어는 pthread함수가 있습니다.(Swift는 NSThread가 있습니다.) thread를 여러 개 구현해서 multithreading을 구현한다면 왼쪽과 같은 concurrent 실행이 가능합니다.

 

플레이어 이동과 동시에 배경이 움직이는 상황, 상대방이 터치하는 동시에 데이터를 받아야 하는 상황, 데이터를 받는 동시에 사용자의 이벤트를 받아야 하는 상황 등등..

 

하지만 위에서 언급한 Concurrency의 issue도 존재합니다. 적절한 싱글톤, 세마포어 등의 조화가 필요합니다.