운영체제

Thread

재밌는게임~ 2024. 4. 9. 13:46

Thread

프로세스도 CPU의 코어를 점유하여 명령어들을 실행하고 쓰레드도 CPU의 코어를 점유하여 명령어를 실행한다.

사실 프로세스를 실행하는 것은 메인 쓰레드가 하나 실행되는 것이다. (단일 쓰레드 프로세스)

하지만 프로세스와 쓰레드 둘 다 작동은 비슷해보이지만 메모리 구조가 조금 다르다.

heap은 프로세스 내의 모든 스레드가 공유하고 스택, 레지스터 상태는 각자 갖는다.

프로세스끼리 Context Switch가 일어나는 것과 똑같이 쓰레드끼리도 Context Switch가 일어나면서 레지스터의 내용도 바꿔줘야한다.

 

*그럼 어떤 프로그램을 여러개 실행하는 것과 한 프로그램에서 쓰레드를 여러개사용하는 것은 메모리상 무엇이 다를까?

  • 위 그림의 code, data, files 등이 쓰레드간에 공유된다.
  • 프로세스 안에서 공유되는 메모리 자원이 있다.

**위의 다른 점 때문에 꼭 주의해야하는 것이 있다.

 

public static void Main(string[] args)
{
    Thread a = new Thread(func);
    Thread b = new Thread(func);
    a.Start();
    b.Start();

    Console.WriteLine("Wait");
    a.Join();
    b.Join();
    Console.WriteLine("Done");
}

public static void func()
{
    int number = 0;
    for (int i = 0; i < 5; i++)
    {
        number++;
        Console.WriteLine(number);
    }
}

 

이러한 경우 각자의 자원에 접근했기 때문에 결과는 우리가 원하는 대로 결과가 도출 되었을 것이다. 그러나 다른 상황을 봐보자!

 

static int number = 0;
public static void Main(string[] args)
{
    Thread a = new Thread(func);
    Thread b = new Thread(func); 
    a.Start();
    b.Start();

    a.Join();
    b.Join();

    Console.WriteLine(number);
}

public static void func()
{
    for (int i = 0; i < 30; i++)
    {
				Thread.Sleep(100);
        number++;
    }
}

 

이러한 경우는 두개의 쓰레드로 공유 자원을 접근한 경우이다. 결과를 확인해보면 원하는대로 나오지 않았을 가능성이 높다. 이러한 형태를 Atomic 하지 않다라고 말한다.

 

그래서 동시에 접근하는 것을 막기위해 사용되는 것이 있다.

Lock

두개의 쓰레드로 공유 자원 접근하기 (쓰레드 동기화)

쓰레드 동기화라는 것은 쓰레드간의 실행순서를 의도에 맞게 맞추는 것이다.

둘이 동시에 read/write을 못하게 한다는 것이다.

 

static object _lock = new object();
static int number = 0;
public static void Main(string[] args)
{
    Thread a = new Thread(func);
    Thread b = new Thread(func);
    a.Start();
    b.Start();

    a.Join();
    b.Join();

    Console.WriteLine(number);
}

public static void func()
{
    for (int i = 0; i < 30; i++)
    {
        Thread.Sleep(100);
        lock (_lock)
        {
            number++;
        }
    }
}

 

이렇게 작성하게 되면 결과가 정상적으로 나온 것을 확인할 수 있다. 

이를 Mutual Exclusion (상호 배제)라고 한다. 

  • 특정 자원을 동시 사용을 피하게 했다는 뜻.

프로세스 간에 뭔가 자원을 공유하려면 그럼 무조건 쓰레드를 사용해야하는걸까?

그렇지 않다.

공유 메모리 (shared memory), 소켓통신, 파이프, 파일 등등이 있다.

 

Lock의 종류

엄청 많지만 대표적인 것만 설명하겠다.

 

- Lock : 가장 단순한 락

- Monitor : Lock은 Monitor를 조금 더 편리하게 작성할 수 있게 만든 것이다. (base는 Monitor 이다.)

- Mutex : 동기화 대상이 1개일 때 사용한다.

- Semaphore : 쓰레드의 개수를 설정할 수 있다. 그래서 보통 1개 이상일 때 사용한다.

 

또한 Spin Lock 이라는 것도 있다.

대기 시간이 짧을 것으로 예상되고, 경합이 적으면 다른 종류의 잠금보다 SpinLock을 수행하는 것이 효과적입니다.

그러나 SpinLock은System.Threading.Monitor 메서드 또는 Interlocked 메서드로 인해 프로그램 성능이 크게 떨어진다는 점이 프로파일링을 통해 확인될 때만 사용하는 것이 좋습니다.

 

생산자와 소비자 문제

int things = 10;

void Produce()
{
    for(int i = 0; i < 100000; i++)
    {
        things++;
    }
}

void Consume()
{
    for (int i = 0; i < 100000; i++)
    {
        things--;
    }
}

var produce = Task.Run(Produce);
var consume = Task.Run(Consume);

produce.Wait();
consume.Wait();

Console.WriteLine(things);

Wait() 메서드는 비동기 작업이 완전히 종료될때까지 기다리는 메서드

Run() 메서드를 비동기 작업 실행

 

공유자원과 임계구역(Critical Section)

동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역을 Critical Section (임계구역)이라고 한다.

해당 구역에서 여러 프로세스, 여러 쓰레드가 동시에 실행하여 문제를 일으키면 레이스 컨디션이 발생했다고 한다.

 

DeadLock (데드락)

using CSharpTest;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

object _tellMe = new object();
object _hireMe = new object();
void TellMeWhatDeadlockIs()
{
    Console.WriteLine("Explain us deadlock and we'll hire you.");
    for(int i  = 0; i < 10000000; i++)
    {
        lock (_tellMe)
        {
            Thread.Yield();
            lock (_hireMe)
            {

            }
        }
    }
}

void HireMeThenIWillExplain()
{
    Console.WriteLine("Hire me and I'll explain it to you.");

    for(int i  = 0; i < 10000000; i++)
    {
        lock (_hireMe)
        {
            Thread.Yield();
            lock (_tellMe)
            {

            }
        }
    }
}

var interviewerTask = Task.Run(TellMeWhatDeadlockIs);
var intervieweeTask = Task.Run(HireMeThenIWillExplain);

interviewerTask.Wait();
intervieweeTask.Wait();

Console.WriteLine("End");

 

DeadLock 이란 상호 배재적으로 사용하고 있는 자원을 요청하면서 서로가 가진 자원이 대기하는 현상을 말한다. 이로 인해 프로그램이 계속 정상적 진행되지 않고 멈추는 현상을 의미합니다.

 

*yeild()메서드

1) 현재 실행 대기중인 동등한 우선순위 이상의 다른 스레드에게

실행기회를 제공한다.(양보)

728x90