[C#] 동시성(Concurrency)과 병렬성(Parallelism)

Arthur·2023년 7월 31일
0
post-thumbnail
post-custom-banner

공부하게 된 계기


C# 유니티에서 코루틴을 사용하면서 동시성과 병렬성관련 내용까지 찾아보게 되었습니다.

코루틴은 동시성 프로그래밍을 지원합니다.
제가 코루틴에 대한 이해도가 거의 없을 때는 병렬로 처리를 해주는 줄 알았습니다.

스레드를 하나 더 생성하거나 불러와서 해당 태스크(Task)를 처리하도록 하는 줄 알았던 것입니다.

병렬성 프로그래밍에 고려할 점과 위험성에 대해서 잘 모르고 단순하게 생각했던 결과입니다.

그래서 이번에 우선 동시성과 병렬성에 대한 개념을 정리하면서 이해하기 위해 작성하게 되었습니다.



이 글의 핵심 키워드 및 내용


  • 동시성(Concurrency)

    • 멀티태스킹: 여러 작업을 하나의 주체가 동시에 진행.
    • 실행 주체: 궁극적인 실행 주체가 하나이며, 싱글 코어 및 싱글 스레드에서 발생.
    • 문제점: 잦은 문맥 교환으로 인해 오버헤드가 발생.
  • 병렬성(Parallelism)

    • 태스크(Task): 여러 주체가 동시에 각자의 작업을 수행.
    • 실행 주체: 싱글 및 멀티 코어, 멀티 스레드 등 다양한 환경에서 발생.
  • 지연(Latency)

    • 작업이 시작되고 완료될 때까지의 시간.
    • 낮은 지연 시스템이 빠르게 응답하는 경향이 있음.
  • 락(Lock)

    • 공유 자원에 대한 동시 접근을 제어하기 위해 사용되는 메커니즘.
  • 공유 자원에 동시 접근 문제점

    • 경쟁 상태(Race condition): 여러 스레드가 동시에 공유 자원을 수정하려고 할 때 발생.
    • 일관성 문제(Consistency Problem): 스레드 간의 작업이 동시에 수행될 때 데이터 일관성이 깨질 수 있음.
    • 결과 값의 불일치: 여러 스레드가 동시에 자원을 수정할 때 예상치 못한 결과가 발생할 수 있음.


동시성(Concurrency)이란?


  • 동시성은 동시에 두 가지 이상의 일을 하는 것
  • 동시성은 멀티태스킹
  • 동시에 실행되는 것 같이 보인다

위 글은 파이썬 격월 세미나 영상(링크)에서 나온 내용입니다.

정말 간결하게 동시성을 설명하는 글이라고 생각해서 가져와봤습니다.

동시성은 실행하는 궁극적인 주체(코어 혹은 스레드)가 하나인데 두 가지 이상의 일을 수행하는 것입니다.
하지만 컴퓨터가 처리하는 속도가 사람이 인지하기 힘들 정도로 빠르기 때문에 동시에 실행되는 것처럼 보입니다.



병렬성(Parallelism)이란?


  • 병렬성은 동시에 두 가지 이상의 일을 하는 것
  • 실제로 동시에 여러 작업이 처리되는 것

병렬성은 동시성과 반대로 실제로 해당 태스크(Task)를 수행하는 주체가 다수인 것입니다.

대부분 OS 관련 책에는 싱글 코어와 멀티 코어의 차이로 설명을 합니다.
병렬성은 여기서 멀티 코어에서 각 태스크 하나 당 하나의 코어가 작업을 진행하는 것을 예시로 듭니다.



그림으로 보는 동시성과 병렬성


위 자료를 보면 동시성(Concurrent)는 병렬성과 차이를 한눈에 볼 수 있습니다.
여기서 조금 더 눈여겨 봐야 할 것은 동시성에서는 Context Switch가 발생한다는 것입니다.

문맥 교환(Context switch)는 하나의 프로세스가 CPU를 사용 중인 상태에서 다른 프로세스가 CPU를 사용하도록 하기 위해, 이전의 프로세스의 상태(문맥)을 보관하고 새로운 프로세스의 상태를 적재하는 작업을 말한다.
한 프로세스의 문맥은 그 프로세스의 프로세스 제어 블록(PCB)에 기록되어 있다.
<위키피디아 문맥 교환>

이런 문맥 교환이 빈번하게 발생하면 많은 오버헤드가 생길 수 있습니다.

만약에 하나의 코어에 하나의 태스크(T1)만 실행 시키면 Context Switch는 발생하지 않을 것입니다.

하지만 사진에는 2개 이상의 태스크를 실행 시킬 때 차이를 보여주고 있습니다.



병렬성이 동시성보다 좋은 처리 방법일까?


싱글 스레드 보다 멀티 스레드가 작업을 실행하는 주체가 많기 때문에 이점이 훨씬 많다고 생각했습니다.

하지만 멀티 스레드를 공부하면서 고려해야 할 부분이 상당히 많다는 것을 알게되었습니다.

  • 멀티 스레드 환경에서 공유되는 자원에 대한 접근으로 인한 문제
    • 공유된 자원에 동시 접근에 대한 문제
      • 경쟁 상태(Race condition)
      • 일관성 문제(Consistency Problem)
      • 스레드끼리 각자 공유 자원을 수정 시 원하는 결과 값이 안나올 수 있다.
  • 멀티 스레드 관리에 관한 문제
    • 하나의 스레드에 작업이 과도하게 몰리는 문제
    • 멀티 스레드를 사용하지만 놀고 있는 스레드가 많을 경우의 문제

성능적 이점과 같은 부분 때문에 병렬 프로그래밍을 무턱대고 도입하다가,
위와 같은 치명적인 문제가 발생할 수 있습니다.

공유 자원의 접근을 해결하기 위해 Lock을 거는 방법이 있지만,
무분별한 Lock을 걸게 되면 지연(Latency) 생기는 문제가 발생할 수 있습니다.

Lock을 사용해서 공유 자원에 대한 접근을 하나의 스레드만 접근 하도록 보장 하지만,
그 만큼 다른 스레드가 대기 시간이 길어지면 생기는 문제입니다.



병렬 처리 프로그래밍 예제코드와 성능


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;

class Exercise
{
    const string PASSWORD = "password";
    const int MAX = 10000000;
    static SHA256 SHA256 = SHA256.Create();

    static void Main()
    {
        List<string> passwordList = new List<string>(MAX);
        for (int i = 0; i < MAX; i++)
            passwordList.Add($"{PASSWORD}{i}");

        SequentialEncryt(passwordList);
        ParallelEncryt(passwordList);
    }


    static void SequentialEncryt(List<string> passwordList)
    {
        DateTime start = DateTime.Now;
        for (int i = 0; i < passwordList.Count; i++)
        {
            byte[] array = Encoding.Default.GetBytes(passwordList[i]);
            byte[] hashValue = SHA256.ComputeHash(array);
        };
        DateTime end = DateTime.Now;
        TimeSpan ts = (end - start);
        Console.WriteLine($"순차적 : {ts.TotalMilliseconds}");
    }

    static void ParallelEncryt(List<string> passwordList)
    {
        DateTime start = DateTime.Now;
        Parallel.For(0, passwordList.Count, i =>
        {
            byte[] array = Encoding.Default.GetBytes(passwordList[i]);
            byte[] hashValue = SHA256.ComputeHash(array);
        });
        DateTime end = DateTime.Now;
        TimeSpan ts = (end - start);
        Console.WriteLine($"병렬적 : {ts.TotalMilliseconds}");
    }
}

위 코드는 문자열로 되어있는 패스워드 데이터를 SHA256으로 암호화 하는 코드입니다.
순차 처리되는 암호화와 병렬로 처리되는 암호화 로직의 성능을 비교해봤습니다.


대량의 데이터를 가용한 CPU를 최대한 활용해 여러 쓰레드가 나눠서 처리하는 병렬 처리가 순차적 처리보다 빠를 가능성이 높다는 것을 알게 되었습니다.

C#에서 Parallel 클래스가 있어서 쉽게 구현해보고 테스트 할 수 있었습니다.



참고 자료


  • 동시성(Concurrency) vs 병렬성(Parallelism) => 링크
  • [ 제 4회 파이썬 격월 세미나 ] 동시성과 병렬성 - 이찬형 => 링크
  • 동시성과 병렬성 => 링크
  • 쾌락코딩 - 코루틴을 이해하기 위한 발악 2편 => 링크
  • Smart Tiger's blog 동시성(Concurrency) vs 병렬성(Parallelism) => 링크
  • csharpstudy.com - 병렬 프로그래밍 (Parallel Programming) => 링크
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 7월 31일

좋은 글이네요. 공유해주셔서 감사합니다.

답글 달기