Concurrency(병행성) 그리고 Parallelism(병렬성)

Meow.paw·2023년 2월 8일
0

https://12bme.tistory.com/353

Concurrency(병행성) 그리고 Parallelism(병렬성)

Concurrency는 프로그램의 성질이고 parallel execution은 기계의 성질이다.

Concurrenty is a property of the program and prallel execution is a property of the machine.

먼저 concurrency부터 이야기하면, 어떤 프로그램이나 알고리즘이 순서에 상관없이 동시에 수행될 수 있다면 concurrent하다고 말합니다. 예를 들어, 1부터 100까지 숫자를 더하는 과정을 생각해보면 숫자 100개를 여러 부분 집합으로 나눈 뒤 동시에 부분합을 구합니다. 그리고 이 부분합을 다시 더하면 원래 얻고자 하는 값을 얻을 수 있습니다. 이 때 이 알고리즘은 concurrent하다라고 말합니다.

그런데 이 알고리즘이 정말 물리적으로 병렬로 돌아갈지 아닐지는 이 알고리즘이 어떤 하드웨어 위에서 돌아갈지 알아야만 확답할 수 있습니다. 방금 이야기한 알고리즘이 멀티 프로세서 머신에서 돌아가야 병렬 실행된다라고 말할 수 있습니다. Parallel execution은 따라서 프로그램의 성질보다는 하드웨어의 성질입니다.

Concurrent한 프로그램은 싱글코어 머신에서도 분명히 돌아갑니다. 뮤텍스, 데드락은 싱글코어에서도 얼마든지 그 의미를 갖습니다. 멀티스레드 프로그램이 비록 물리적인 제약으로 싱글코어에서 시분할 형태로 돌아가지만 겉으로는 concurrent하게 작동한다고 속일 수 있습니다. 반대로 아무리 멀티스레드로 작성된 프로그램이라 하더라도 멀티코어가 아니라면 병렬로 작동한다고 말하지 못합니다.

OpenMP, MPI, CUDA 같은 프로그래밍 방법론은 병행 프로그래밍 보다는 병렬 프로그래밍이 옳은 표현합니다. 언급한 세 방법론 모두 물리적으로 제공되는 다양한 병렬 하드웨어를 활용하기 때문입니다.

멀티 스레드 프로그램에서 벌어지는 각종 버그를 concurrency bug라고 부르는데, 굳이 하드웨어가 반드시 병렬성을 지원하지 않더라도 논리적으로 발생하니까 concurrency라는 말이 옳습니다.

보통 프로그램에서 병렬성을 찾는다고 하지 병행성을 찾는다고는 말하지 않습니다. 약간 모호하기는 하나 결국 물리적으로 병렬 실행이 가능한 코드를 찾는 것이니 병렬성이 더 합리적으로 보입니다. 어떤 함수에 락을 무식하게 크게 걸면 병행성(concurrency)가 나빠진다고 말합니다. 병렬성이 나빠진다고는 말하지 않습니다.

Parallel computing is a form of computation in which many calculations are carried out simultaneously, operating on the principle that large problems can often be divided into smaller ones, which are then solved concurrently ("in parallel").

Concurrent computing is a form of computing in which programs are designed as collections of interacting computational process that may be executed in parallel.

Concurrent programs can be executed sequentially on a single processor by interleaving the execution steps of each computational process, or executed in parallel by assigning each computational process to one of a set of processors that may be close or distributed across a network.

포스팅 원본 출처는 http://rapapa.net/?p=2704 입니다.

Language들을 접하다 보면 Concurrency와 Parallelism이란 단어를 접하게 되는데, 한국어로 번역하자면 동시성과 평행(병렬)성, 동시 실행과 평행(병렬) 실행인데, 이 둘은 어떤 차이일까요?

그 둘의 차이점을 살펴보려면 먼저 Thread와 Process의 차이부터 논해야 합니다. Process와 Thread의 차이는 신입이나 사회 초년생 개발들이라면 면접 때 한 두번은 들어본 질문일 것입니다.

Thread는 Process와 거의 유사합니다. 단지 Thread는 컴퓨터의 자원, 예를 들면 메모리와 IO 장치들을 Thread끼리 공유하고, Process는 독립적으로 OS로부터 할당받아 사용합니다. 일반적으로 Process는 여러 Thread를 Invoking 시키고 관리합니다. 애플리케이션이 실행될 때 한 개의 Process가 발생을 하고 그 Process가 어떠한 일을 할 때 컴퓨터의 자원을 최대로 활용하기 위해서는 병렬적으로 일을 하게 만들어야 합니다. 그러므로 Thread를 이용해서 컴퓨터의 놀고 있는 자원들을 최대한으로 사용하게 만드는 것이 Multi-Thread Programming의 영역입니다. I/O를 기다리게 하지 않고 다른 일을 하거나, 놀고 있는 여분의 CPU 코어들을 최대한 사용하게 하는 것이 핵심입니다.

각 언어에서 Threading을 편리하게 할 수 있는 API를 제공하기는 하지만 thread를 사용하다보면 join, detach등의 시점들을 설계하고, mutex_lock, mutext_unlock으로 리소스들을 락킹하고 해제하고, 필요하면 세마포어도 구현해서 최적의 퍼포먼스를 낼 수 있도록 해주는 일이 애플리케이션이 무거워지고 커질 수록 그리 간단한 일이 아님을 알게됩니다. 자연히 이런 작업을 하다보면 한두번쯤은 데드락으로 곤욕을 겪게 되기도 합니다.

Language에서 Parallelism이라고 불리워지는 대표적인 예가 바로 Thread입니다. Thread는 그야말로 동시에 평행적으로 실행이 됩니다. 그러므로 그만큼 효율적으로 일을 해냅니다.

그렇다면 Concurrency 즉, 동시성이란 무엇일까요?

Concurrency는 Parallelism의 난해함을 풀어낸 방법이라고 말하고 싶습니다. 대표적인 Coroutine입니다. 유니티에서 코루틴은 서브루틴의 일종으로 진입 시점이 여러개인 서브루틴을 말합니다.

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
IEnumerator Do() {
print("Do now");
yield return new WaitForSeconds(2);
print("Do 2 seconds later");
}
void Example() {
Do();
print("This is printed immediately");
}
}

이 코루틴은 thread와는 달리 평행하게 일을 수행하지 않습니다. 한개의 Process가 Coroutine에 설정된 조건에 따라서 일반 서브루틴들과 왔다갔다 하면서 Task를 처리하는 방식입니다. 그럼에도 불구하고 특정 라인이 수행된 다음 차근차근 수행되는 Sync 방식이 아니라 Async하게 코드를 실행시키고, 결과 값이 오거나 혹은 필요한 때에 다음 루틴을 실행 시키게 할 수 있어서 효율적으로 자원을 활용할 수 있습니다. 덤으로 코드가 Thread로 구현했을 때보다 훨씬 깔끔하게 나오고, Locking/UnLocking 메커니즘을 싹 걷어낼 수 있게 됩니다.

최신 언어라고 할 수 있는 Go에서도 이 Concurrency의 개념을 언어에 적극적으로 집어 넣었습니다. goroutine이 바로 그것인데 이걸 사용하면 multi-thread의 백단 dirty한 부분에 손 하나 대지 않고 쉽게 Async한 일들을 처리할 수 있게 해줍니다.

package main
import "fmt"

func f(from string) {
for i := 0; i < 10; i++ {
fmt.Println(from, ":", i)
}
}

func main() {
f("direct")

go f("goroutine")

go func(msg string) {
    fmt.Println(msg)
}("goding")

var input string
fmt.Scanln(&input)
fmt.Println("done")

}

이는 goroutine이 각각 Thread와 같이 독립적으로 실행이 된다는 것을 보여줍니다. 그럼에도 불구하고 goroutine은 Concurrency입니다. 그러므로 자원을 Locking/Unlocking하는 코드들이 불필요합니다. 아랫단의 일들을 golang의 runtime이 알아서 해주고 Thread를 여러개 실행 시키도록 세팅하면 각 thread에 goroutine을 할당하여 자원을 관리하는 일까지도 언어단에서 알아서 해줍니다. 개발자들은 C/C++에서 thread를 사용하는 것과 마찬가지로 적절한 작업들에 goroutine을 호출시키고 channel을 이용해서 결과값들을 받아서 훨씬 수월하게 처리할 수 있습니다.

이러한 언어의 Concurrency는 기존에 C나 C++에서 다루었었던 멀티쓰레드와 자원 관리의 복잡함으로부터 개발자들을 해방시키는 역할을 해줍니다. 동시에 Multi-Thread로 해냈던 수준의 Async하고 parallel한 작업들을 흉내내거나 손쉽게 수행할 수 있도록 해줍니다.

profile
냥냥냥

0개의 댓글