CS - Process vs Thread Deep


Process 와 Thread

  • Process 는 OS에서 관리하는 실행되는 프로그램의 단위이다.

  • Process 안에는 최소 1개의 흐름이 있는데, 그 흐름을 Thread 라고 하며, 여러 Thread 가 존재할 수도 있다.

  • 우리가 CPU로 부터 연산을 한다고 하면, Thread 단위로 연산을 진행한다.

  • 컴퓨터의 세계는 3개의 레이어로 구성되어있다.

    • User Mode

      • 프로세스가 동작한다.

      • 시스템 콜과 인터럽트를 통해 커널 OS로 부터 받은 가상 메모리라는 제한된 공간을 받는다.

        • 메모리는 무조건 가상의 공간을 부여받는다.

        • 그렇게 하는 이유는 실제 Ram에 지금 공간이 부족할 수 있으므로, 하드디스크에서 용량을 가져와서 가상 메모리의 일부로 부여할 수도 있기 때문이다.

        • 동작 못하는 것보다는 일시적으로라도 그렇게 사용하는게 낫기 때문이다.

      • 스레드들은 Process의 가상 메모리라는 제한된 공간 속에서만 동작한다.

      • 그 가상 메모리 안에 각 스레드별로 본인만 사용할 수 있는 Thread Local Storage & Stack 이 있고, 모든 스레드가 공유하는 공간인 Heap이 있다.

      • 그러다보니 스레드가 여러개면 동시성 이슈가 발생하고, 동기화가 필요해진다.

    • Kernel Mode

      • User Mode 에서 프로세스가 동작하는데에 필요한 가상 메모리(Virtual Memory)를 할당해준다.

      • 필요한 작업을 수행한 후, 커널은 다시 사용자 모드로 전환한다.

    • H/W

      • 1차 저장소인 메모리와, 2차 저장소인 디스크가 있다.



Thread 간의 데이터 공유 방법

  • 동시성, 동기화에 대한 이슈를 해결하는 그런 공유가 아닌, 진짜 A 스레드에서 B 스레드로 데이터를 전달하는 것을 말한다.

  • 이 때는 Queue 를 활용하거나, static 변수를 활용하거나 할 수 있다.

  • 현업에는 Kafka 라는 곳에 데이터를 넣고, 컨슈머가 가져가는 방법을 많이 사용한다.

  • 다음은 Queue 를 활용한 데이터 공유 방법이다.

     public class ThreadTest {
         private static final int MAX_ITEMS = 500;
         private static final Queue<Integer> sharedQueue = new LinkedList<>();
    
         public static void main(String[] args) {
             ExecutorService executorService = Executors.newFixedThreadPool(2);
    
             executorService.submit(() -> {
                 for (int i = 1; i <= MAX_ITEMS; i++) {
                     sharedQueue.add(i);
                     System.out.println("Pushed: " + i);
                     // Simulate some work being done
                     try {
                         Thread.sleep(10);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             });
    
             executorService.submit(() -> {
                 for (int i = 0; i < MAX_ITEMS; i++) {
                     if (!sharedQueue.isEmpty()) {
                         int item = sharedQueue.poll();
                         System.out.println("Popped: " + item);
                         // Simulate some work being done
                         try {
                             Thread.sleep(10);
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             });
    
             executorService.shutdown();
         }
     }
    
  • 결과는 다음과 같다. ( 뒤죽박죽으로 나온다. )

      Popped: 1
      Pushed: 1
      Pushed: 2
      Popped: 2
      Pushed: 3
      Popped: 3
      Pushed: 4
      Popped: 4
      Pushed: 5
      Pushed: 6
      Pushed: 7
      Pushed: 8
      .
      .
      .
      Pushed: 495
      Pushed: 496
      Pushed: 497
      Pushed: 498
      Pushed: 499
      Pushed: 500
    
  • 이렇게 할 경우, 스레드에 값을 제대로 전달하지 못한다.

  • 동기화가 이루어지지 않기 때문이다.

  • 하나의 Queue 에 대해서, 읽는 것과 쓰는 것이 동시에 접근하면 문제가 발생한다.

  • 이는 Lock 으로 해결할 수 있다.

      public class ThreadTest {
          private static final int MAX_ITEMS = 500;
          private static final Queue<Integer> sharedQueue = new LinkedList<>();
          private static final Object lock = new Object();
    
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newFixedThreadPool(2);
    
              executorService.submit(() -> {
                  for (int i = 1; i <= MAX_ITEMS; i++) {
                      synchronized (lock) {
                          sharedQueue.add(i);
                          System.out.println("Pushed: " + i);
                      }
                      // Simulate some work being done
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
    
              executorService.submit(() -> {
                  for (int i = 0; i < MAX_ITEMS; i++) {
                      synchronized (lock) {
                          if (!sharedQueue.isEmpty()) {
                              int item = sharedQueue.poll();
                              System.out.println("Popped: " + item);
                          }
                      }
                      // Simulate some work being done
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
    
              executorService.shutdown();
          }
      }
    
  • 혹은 아래처럼 동기화를 보장해주는 BigQueue 와 LinkedBlockingQueue 를 활용할 수도 있다.

      public class ThreadTest {
          private static final int MAX_ITEMS = 500;
          private static final BlockingQueue<Integer> sharedQueue = new LinkedBlockingQueue<>();
    
          public static void main(String[] args) {
              Thread pushThread = new Thread(() -> {
                  for (int i = 1; i <= MAX_ITEMS; i++) {
                      try {
                          sharedQueue.put(i);
                          System.out.println("Pushed: " + i);
                          // Simulate some work being done
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
    
              Thread popThread = new Thread(() -> {
                  for (int i = 0; i < MAX_ITEMS; i++) {
                      try {
                          int item = sharedQueue.take();
                          System.out.println("Popped: " + item);
                          // Simulate some work being done
                          Thread.sleep(5);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
    
              pushThread.start();
              popThread.start();
    
              try {
                  pushThread.join();
                  popThread.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    
  • 결과는 다음과 같다.

     Pushed: 1
     Popped: 1
     Pushed: 2
     Popped: 2
     Pushed: 3
     Popped: 3
     Pushed: 4
     Popped: 4
     Popped: 5
     Pushed: 5
     Pushed: 6
     Popped: 6
     Pushed: 7
     Popped: 7
     .
     .
     .
     Pushed: 499
     Popped: 499
     Pushed: 500
     Popped: 500
    



Process 간의 데이터 공유 방법

  • File 에 저장하여 읽고 쓰게 할 수 있음.

  • Kafka 와 같은 메시지 버스를 활용 할 수 있음.

  • 웹 소켓으로도 가능.






results matching ""

    No results matching ""