<프로세스(Process)와 스레드(Thread)>

이 두 개념을 혼동하기 쉬우나, 이 두 개념의 차이점을 이해하는 것은 동시성(concurrency) 및 병렬성(parallelism) 문제를 해결하면서 효율적인 Software를 구현하는 데 있어서 매우 중요한 개념이라고 할 수 있다. 여기서는 C#을 중심으로 설명하겠다.

 

귀여워서 쓰는 gifㅎㅎ

<프로세스(Process)>

프로세스는 운영체제로부터 자원을 할당받아 실행되는 작업 단위를 말한다. 프로세스는 각각 독립된 메모리 영역(Code,Data,Stack,Heap)을 가지며, 이는 운영체제로 부터 할당 받는다.

  • Code : C#을 사용한다고 가정하면, C# Program code가 저장되는 영역이라 할 수 있다. 이 부분은 읽기 전용이고, 변경이 불가 하다.

[코드 영역이 읽기 전용인 이유]

만약, 프로그램 코드가 프로그램 실행 중에 변경된다면, 프로그램은 의도치 않은 방식으로 동작하거나 오류를 발생시킬 수 있기에, 코드의 무결성을 보장하기 위해 코드 영역은 보호되어야 하기에 읽기 전용으로 설정된다. 

또한 악성 코드가 있다면, 프로그램의 동작을 변경하려고 시도할 수 있기에 읽기 전용으로 해놓는 이유도 있다.

  • Data: 전역 변수와 정적(Static) 변수 등이 저장되는 영역. 이 부분은 읽기와 쓰기가 가능하다.

[Data는 읽기가 쓰기가 왜 가능할까?]

여기는 전역 "변수", 정적 "변수"가 저장되는 영역이라고 했다. 변수라는 것은 , 즉 변할 변 이기에 변할 수가 있는 것이다.

그럼, 이 변수들은 프로그램 실행 도중에 읽거나 변경할 수 있는것이다. 예를 들어서 프로그램이 사용자의 입력을 받아서 전역 변수의 값을 변경하거나, 계산 결과를 전역 변수에 저장하는 등의 작업을 수행하게 된다. 이런 이유로 데이터 영역은 읽기와 쓰기가 모두 가능한것이다.

  • Stack : 함수 호출 정보(지역 변수, 매개 변수, 반환 주소 등)가 저장되는 영역, 읽기와 쓰기가 가능하고, 함수의 호출과 함께 할당되고 함수의 반환과 함께 해제된다. 
  • Heap : 동적 할당을 위한 영역, 필요에 따라 크기를 조절할 수 있는 영역으로 이 부분은 읽기와 쓰기가 가능하다.

new 키워드를 사용해서 동적으로 객체를 생성하면 이 영역에 할당된다. 

 

여기서 주의해야 할 것은 코드를 변경 가능 하다는 것이 아니라. '객체'를 변경 가능하다는 것이다.

예를 들어

using System;

public class Service
{
    public string Name { get; set; }
}

public class Program
{
    public static void Main()
    {
        // 객체 생성
        Service service = new Service();

        // 객체의 상태 변경
        service.Name = "Initial Name";
        Console.WriteLine(service.Name); // "Initial Name"

        // 객체의 상태 변경
        service.Name = "Changed Name";
        Console.WriteLine(service.Name); // "Changed Name"
    }
}

이 예제에서 'Service' 객체의 Name 프로퍼티는 프로그램이 실행 중일 때 변경된다. 이런 변경은 프로그램을 다시 컴파일하거나 실행하지 않고도 일어난다.

using System;

public class Program
{
    // Data 영역: 전역 변수가 이곳에 저장된다.
    static int globalVariable = 10;

    // Code 영역: 함수 및 메서드가 이곳에 저장된다.
    public static void Main()
    {
        // Stack 영역: 지역 변수와 함수 호출 정보가 이곳에 저장된다.
        int localVariable = 20;

        Console.WriteLine("Global Variable: " + globalVariable);
        Console.WriteLine("Local Variable: " + localVariable);

        // Heap 영역: 동적으로 할당된 데이터가 이곳에 저장된다.
        int[] dynamicArray = new int[10];
        dynamicArray[0] = 30;
        Console.WriteLine("Dynamic Array Value: " + dynamicArray[0]);
    }
}

여기서 'globalVariable'은 전역 변수로서, Data 영역에 저장된다.

Main 함수는 Code 영역에 저장된다.

localVariable은 Main 함수 내의 지역 변수로서, Stack 영역에 저장된다.

dynamicArray는 new 키워드를 사용하여 동적으로 할당되므로 Heap 영역에 저장된다.

 

프로세스는 집으로도 비유할 수 있다.

Code: 집의 설계도와 같다. 설계도는 집이 어떻게 생겼는지, 어떤 방들이 있는지 등의 정보를 담고 있다. 이 설계도는 읽기만 가능하고 변경할 수 없다. 마찬가지로, 코드 영역에는 프로그램 코드가 저장되어 있고, 이는 변경할 수 없다.
Data: 집안에 있는 물건들과 같다. 예를 들어, 가구나 컴퓨터 등이 이에 해당한다. 이들 물건들은 위치를 이동하거나 상태를 변경하는 것이 가능하다. 마찬가지로, 데이터 영역에는 변수와 같은 데이터가 저장되며, 이는 읽기와 쓰기가 가능하다.
Stack: 이는 집안의 할 일 리스트나 메모와 같다. 할 일을 추가하거나 완료하면 메모를 변경하게 된다. 마찬가지로, 스택 영역에는 함수 호출 정보가 저장되며, 함수 호출이 발생하거나 완료될 때마다 정보가 변경된다.
Heap: 이는 집의 창고와 같다. 필요에 따라 물건을 가져다 놓거나 물건을 가져갈 수 있다. 마찬가지로, 힙 영역은 동적 할당을 위한 공간으로, 필요에 따라 메모리를 할당하고 해제할 수 있다.


<스레드(Thread)>

Thread는 Process 내에서 실행되는 흐름의 단위를 의미한다. 하나의 Process 내에서 여러 Thread를 생성할 수 있고,

Thread들은 Process의 Memory 영역(Code,Data,Heap)을 공유하면서 실행된다. 그러나 각 Thread는 자신만의 Stack영역을 가지고 있다. 각 Thread가 별도의 함수 호출을 관리하기 때문이다.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(PrintNumbers);
        thread.Start();

        PrintNumbers();
    }

    static void PrintNumbers()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(i);
        }
    }
}

이 코드를 예로 설명하자면 여기서는 총 두 개의 스레드가 실행된다. 하나는 Main() 함수가 실행되는 메인 스레드, 또 하나는 Thread thread = new Thread(PrintNumbers);에 의해 생성되는 새로운 스레드다. 

Main() 함수에서 thread.Start();를 호출하면서 새로운 스레드가 시작되며, 이 스레드는 PrintNumbers() 함수를 실행한다. 동시에 Main() 함수 내에서  PrintNumbers() 함수를 호출하여 메인 스레드에서 동일한 함수가 실행된다.

 

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // 첫 번째 추가 스레드 생성
        Thread thread1 = new Thread(PrintNumbers);
        thread1.Start();

        // 두 번째 추가 스레드 생성
        Thread thread2 = new Thread(PrintLetters);
        thread2.Start();

        // 메인 스레드에서 PrintNumbers와 PrintLetters 실행
        PrintNumbers();
        PrintLetters();
    }

    static void PrintNumbers()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(i);
        }
    }

    static void PrintLetters()
    {
        for (char c = 'a'; c <= 'j'; c++)
        {
            Console.WriteLine(c);
        }
    }
}

이렇게 하면 된다.   총 3개의 스레드가 실행 된다. 

< 프로세스와 스레드의 관계와 차이점 >

프로세스는 운영체제로부터 자원을 받아 할당하고, 스레드는 프로세스로부터 자원을 할당받아 실행되는 것이라고 할 수 있다.

즉, 프로세스는 독립적인 실행 단위이며, 스레드는 프로세스 내에서 독립적인 실행 흐름을 나타내는 단위이다. 따라서 여러 스레드를 생성하는 것은 하나의 프로세스 내에서 병렬적인 작업을 수행하는 것을 가능하게 함으로써 여러 작업을 동시에 처리할 수 있으므로 프로그램의 실행 속도를 향상시킬 수 있다. 예를 들어, 한 스레드에서 데이터를 로드하는 동안 다른 스레드에서는 계산을 수행하는 등, 여러 작업을 동시에 진행하면 전체 실행 시간을 줄일 수 있다. 또한 사용자 인터페이스를 가진 Application에서는 별도의 스레드에서 오래 걸리는 작업을 수행하면, 주 스레드(일반적으로는 UI스레드 라고 한다.)는 사용자 입력에 대응하거나 인터페이스를 업데이트하는 등의 작업을 계속 할 수 있다.

하지만 주의해야 할 점은, 프로세스 내의 스레드들이 메모리를 공유하기 때문에 동시성 제어가 필요하다. 예를 들어, 두 스레드가 동일한 메모리 위치에 동시에 접근하려고 시도한다면, 예측할 수 없는 결과를 초래할 수 있다.

 

C# Threads

https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread?view=net-5.0 

 

Thread Class (System.Threading)

Creates and controls a thread, sets its priority, and gets its status.

learn.microsoft.com


"Processes and Threads" - Linux Documentation Project

https://tldp.org/LDP/LG/issue23/flower/threads.html

 

Processes and Process Context

There are two types of threads: user-space and kernel-space.  User space threads consist of internal cooperative multitasking switches between sub tasks defined with a process.   A thread may send a signal, perform it?s own switch or be invoked by a tim

tldp.org

 

+ Recent posts