프로그래밍언어론

병렬성과 동시성 - 언어의 관점 by 12bme

plas 2022. 5. 16. 08:36

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

 

[프로그래밍] Concurrency, Parallelism 차이

Concurrency(병행성) 그리고 Parallelism(병렬성) 포스팅 원본출처는 http://egloos.zum.com/minjang/v/2517211 입니다. Concurrency는 프로그램의 성질이고 parallel execution은 기계의 성질이다. Concurrenty..

12bme.tistory.com

Concurrency는 프로그램의 성질이고 parallel execution은 기계의 성질이다.
Concurrenty is a property of the program and prallel execution is a property of the machine.

concurrency는 동시성이라고 번역하는데 병행성이라고도 합니다. 프로그램 또는 알고리즘의 성질로 순서에 상관없이 동시에 수행될 수 있는 성질을 말합니다. 예를 들어 100개의 수를 합하는 프로그램이 1번부터 100번까지 차례로 하라고 하면 순차적인 것이고 그냥 100개 숫자를 모두 합해라 라고 하면 동시성이 높은 것이지요. 

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

병렬로 수행되기 위해서는 누군가가 그것을 n개의 프로세서에 대해 데이터를 n개 부분으로 나누어서 전달하고 부분합을 모아서 다시 합하는 일을 해야 합니다. 이것을 CUDA나 MPI 같은 병렬언어의 기능을 이용해서 프로그램에서 명시적으로 할 수도 있는데, 특정 하드웨어 구성에 맞게 병렬실행가능하게 코딩을 한다면 이것을 병렬 프로그래밍이라고 합니다. 즉 병렬 프로그래밍이란 구체적인 하드웨어 타겟에 맞게 병렬 수행을 위한 방식을 코드에서 명시하는 것을 얘기하기 때문에 자연스러운 프로그래밍 방식과는 많이 다르고 환경에 의존성이 매우 높고 가독성도 낮습니다. (그래서 병렬 프로그래밍은 슈퍼컴퓨터시대부터 오랫동안 연구되고 시도되었지만 크게 성공하지 못했습니다)

동시 수행가능한 성질을 가진 Concurrent한 프로그램은 싱글코어 머신에서도 잘 동작합니다. 뮤텍스, 데드락은 싱글코어에서도 얼마든지 그 의미대로 수행될 수 있습니다. 그런가하면 멀티스레드 프로그램이 싱글코어에서 시분할 형태로 돌아가는 경우도 병렬로 수행되는 것처럼 보이지만 진짜 병렬 수행은 아닙니다. 멀티스레드 프로그램이 멀티코어 환경에서 각 스레드가 다른 코어에 할당된다면 병렬로 수행되는 것이 맞습니다.

또하나 많이 사용되는 개념이 경쟁조건(race condition) 같은 동시성 오류는 굳이 하드웨어가 병렬이 아니더라도 발생합니다. (시분할 형태의 스레드에서도 발생 가능) 

우리는 프로그램이 동시성을 가진다고 하고 프로그램에서 병렬성을 찾는다고 합니다. 즉 동시성은 프로그램 자체의 성질이고 병렬성은 그것을 수행하기 위한 구현 방법의 문제입니다. 그럼 성능을 올리는 것이 무엇보다 중요해진 현대 프로그래밍에서 문제는 어떻게 동시성이 높은 프로그램을 작성하느냐가 됩니다.

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

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으로 리소스들을 락킹하고 해제하고, 필요하면 세마포어도 구현해서 최적의 퍼포먼스를 낼 수 있도록 해주는 일이 애플리케이션이 무거워지고 커질 수록 그리 간단한 일이 아님을 알게됩니다. 자연히 이런 작업을 하다보면 한두번쯤은 데드락으로 곤욕을 겪게 되기도 합니다.

Thread는 기존 언어에서 병렬로 수행할 수 있게 코딩하는 방법이라고 볼 수 있습니다. 동시에 병렬적으로 실행이 되기위한 코드를 프로그램에서 제공합니다. 그러므로 그만큼 효율적으로 일을 해냅니다. 그러나 자원 접근과 동시에 수행되는 코드 부분의 관계를 프로그래머가 세세하게 챙기면서 오류가 없도록 보장하는 것은 정말 힘든 일이고 높은 동시성을 가진 코드를 작성하기 어렵게 만듭니다.

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

Concurrency는 병렬 프로그램을 작성하는 어려움과 번거로움을 해결하는 방법이라고 말하고 싶습니다. 대표적인  것이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한 작업들을 흉내내거나 손쉽게 수행할 수 있도록 해줍니다.

글의 출처: https://12bme.tistory.com/184 [길은 가면, 뒤에 있다.]를 읽기 쉽게 요즘 용어로 부분부분 수정했습니다.