stemmmm / cs-study

컴퓨터 공학을 모의 면접으로 학습하는 스터디
2 stars 0 forks source link

4주차 #4

Open stemmmm opened 2 months ago

stemmmm commented 2 months ago
stemmmm commented 2 months ago

중요한 사실) 프로세스 자체는 운영체제의 스케줄러에 의해 직접 실행되지 않으며, 스레드가 실행됨

jihojivenchy commented 2 months ago

Thread

main()

class CustomThread: Thread {
    override func main() {
        for i in 1...5 {
            print("스레드 작업: \(i)")
        }
    }
}

let thread = CustomThread()
thread.start()

/*
스레드 작업: 1
스레드 작업: 2
스레드 작업: 3
스레드 작업: 4
스레드 작업: 5
*/

Sleep == 대기

class CustomThread: Thread {
    private let maxCount = 5

    override func main() {
        for i in 1...maxCount {
            print("스레드 작업: \(i)")

            if i == 3 {
                print("스레드 5초 대기")
                Thread.sleep(forTimeInterval: 5)
            }
        }

        print("작업 완료")
    }
}

let thread = CustomThread()
thread.start()

/*
스레드 작업: 1
스레드 작업: 2
스레드 작업: 3
스레드 5초 대기
스레드 작업: 4
스레드 작업: 5
작업 완료
*/

Cancel()

class CustomThread: Thread {
    private let maxCount = 10

    override func main() {
        for i in 1...maxCount {
            if self.isCancelled {
                print("스레드가 취소 요청을 받아서 종료합니다.")
                break
            }
            print("스레드 작업: \(i)")
            Thread.sleep(forTimeInterval: 1)
        }

        print("작업 완료")
    }
}

let thread = CustomThread()
thread.start()

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print("메인스레드에서 취소 요청")
    thread.cancel()
}

/*
스레드 작업: 1
스레드 작업: 2
메인스레드에서 취소 요청
스레드가 취소 요청을 받아서 종료합니다.
작업 완료
*/

exit()

class CustomThread: Thread {
    private let maxCount = 10

    override func main() {
        for i in 1...maxCount {
            print("스레드 작업: \(i)")

            if i == 5 {
                print("작업 강제 종료")
                Thread.exit()
            }
        }

        print("작업 완료")
    }

    deinit {
        print(#function)
    }
}

let thread = CustomThread()
thread.start()

/*
스레드 작업: 1
스레드 작업: 2
스레드 작업: 3
스레드 작업: 4
스레드 작업: 5
작업 강제 종료
*/

세마포어를 이용하여 쓰레드 차단하기

class SemaphoreDemo {
    private let semaphore = DispatchSemaphore(value: 1)

    func performTask() {
        print("\(Thread.current)가 접근 시도")
        semaphore.wait()  // 대기

        print("\(Thread.current)가 작업 시작")

        Thread.sleep(forTimeInterval: 2) // 작업 수행

        print("\(Thread.current)가 작업 완료")
        semaphore.signal()  // 신호를 보냄으로써 다른 스레드의 접근을 허용
    }
}

let demo = SemaphoreDemo()

let thread1 = Thread {
    demo.performTask()
}

let thread2 = Thread {
    demo.performTask()
}

thread1.start()
thread2.start()

/*
<NSThread: 0x600001725180>{number = 8, name = (null)}가 접근 시도
<NSThread: 0x6000017254c0>{number = 9, name = (null)}가 접근 시도
<NSThread: 0x6000017254c0>{number = 9, name = (null)}가 작업 시작
<NSThread: 0x6000017254c0>{number = 9, name = (null)}가 작업 완료
<NSThread: 0x600001725180>{number = 8, name = (null)}가 작업 시작
<NSThread: 0x600001725180>{number = 8, name = (null)}가 작업 완료
*/

8번 쓰레드가 9번 쓰레드보다 먼저 접근했는데, 왜 9번이 먼저 작업하는 것일까?

stemmmm commented 2 months ago

스레드 생성과 실행

스레드 생성

Thread 상속

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

Runnable 구현

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
        thread.start();
    }
}


데몬 스레드, 유저 스레드

stemmmm commented 2 months ago

스레드 제어와 생명 주기

스레드 생명 주기

image


join

join()

public class Join {
    public static void main(String[] args) {
        SumTask task1 = new SumTask(1, 50);
        SumTask task2 = new SumTask(51, 100);

        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);

        thread1.start();
        thread2.start();

        thread1.join();  // thread1 종료될 때까지 main 스레드 무한 대기
        thread2.join();  // thread2 종료될 때까지 main 스레드 무한 대기

        int totalSum = task1.result + task2.result;
        System.out.println("total sum: " + totalSum);
    }

    static class SumTask implements Runnable {
        int startValue;
        int endValue;
        int result = 0;

        public SumTask(int startValue, int endValue) {
            this.startValue = startValue;
            this.endValue = endValue;
        }

        @Override
        public void run() {
            int sum = 0;

            for (int i = startValue; i <= endValue; i++) {
                sum += i;
            }

            result = sum;
        }
    }
}

join(ms)


인터럽트

public class Interrupt {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread thread = new Thread(task);
        thread.start();

        Thread.sleep(100);
        thread.interrupt();
        System.out.println("스레드 인터럽트 상태1: " + Thread.currentThread().getState());  // true
    }

    static class MyTask implements Runnable {
        @Override
        public void run() {
            while (!Thread.interrupted()) {  // 스레드 인터럽트 상태 정상(false)으로 변경
                System.out.println("작업 중...");
            }

            System.out.println("스레드 인터럽트 상태2: " + Thread.currentThread().getState());  // false

            try {
                Thread.sleep(1_000);
                System.out.println("자원 정리 완료!");
            } catch (InterruptedException e) {
                System.out.println("인터럽트 발생하여 자원 정리 실패");
                System.out.println("스레드 인터럽트 상태3: " + Thread.currentThread().getState());
            }
        }
    }
}


yield

public class Yeild {
    static final int THREAD_COUNT = 1_000;  // 양보할 수 있도록 많은 스레드 생성

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread thread = new Thread(new MyTask());
            thread.start();
        }
    }

    static class MyTask implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                Thread.yield();
            }
        }
    }
}
woogisfree commented 2 months ago

다중 스레드 프로그래밍 - Producer and Consumer Problem

Java 에서는 ConcurrentLinkedQueue, CountDownLatch 등을 사용하여 스레드 간의 데이터를 공유하고, 일관성을 유지하도록 관리합니다. 여기서는 ConcurrentLinkedQueue 을 사용했습니다.

추가적으로 CountDownLatch 는 아래와 같이 사용하면 됩니다.

CountDownLatch producerLatch = new CountDownLatch(PRODUCER_THREAD_COUNT);
CountDownLatch consumerLatch = new CountDownLatch(CONSUMER_THREAD_COUNT);
CountDownLatch finishedLatch = new CountDownLatch(PRODUCER_THREAD_COUNT + CONSUMER_THREAD_COUNT);

producerLatch.await();
consumerLatch.await();
finishedLatch.await();

아래는 예시코드입니다.

@AllArgsConstructor
@Getter
public class Data {
    private int num1;
    private int num2;
    private char operator;
}
@Slf4j
public class Consumer extends Thread {

    private Queue<Data> queue;

    public Consumer(Queue<Data> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        int cnt = 0;
        while (cnt < 5) {
            Data data = queue.poll();
            if (data == null) {
                try {
                    Thread.sleep(3000);
                    cnt++;
                } catch (InterruptedException e) {
                    log.info("Data 에 값이 없습니다.");
                    e.printStackTrace();
                }
            } else {
                int result = 0;
                if (data.getOperator() == '+')
                    result = data.getNum1() + data.getNum2();
                else if (data.getOperator() == '-')
                    result = data.getNum1() - data.getNum2();
                log.info(data.getNum1() + " " + data.getOperator() + " " + data.getNum2() +
                        " = " + result);
                cnt++;
            }
        }
    }
}
@Slf4j
public class Producer extends Thread {

    private Queue<Data> queue;

    public Producer(Queue<Data> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        Random random = new Random();
        int cnt = 0;
        while (cnt < 5) {
            int num1 = random.nextInt(10000);
            int num2 = random.nextInt(10000);
            char operator = random.nextBoolean() ? '+' : '-';
            log.info("num1 : " + num1 + ", num2 : " + num2 + ", operator : " + operator);
            Data data = new Data(num1, num2, operator);
            queue.offer(data);
            cnt++;
        }
    }
}
@Slf4j
public class Main {
    private static final int PRODUCER_THREAD_COUNT = 4;
    private static final int CONSUMER_THREAD_COUNT = 2;

    private static final Queue<Data> queue = new ConcurrentLinkedQueue<>();

    public static void main(String[] args) throws InterruptedException {
        List<Producer> producerList = new ArrayList<>();
        List<Consumer> consumerList = new ArrayList<>();

        for (int i = 0; i < PRODUCER_THREAD_COUNT; i++) {
            Producer producer = new Producer(queue);
            producerList.add(producer);
            producer.start();
            producer.join();
        }

        for (int i = 0; i < CONSUMER_THREAD_COUNT; i++) {
            Consumer consumer = new Consumer(queue);
            consumerList.add(consumer);
            consumer.start();
            consumer.join();
        }

        log.info("작업 종료");
    }
}

결과

22:46:46.259 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6042, num2 : 947, operator : -
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6424, num2 : 9308, operator : -
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6795, num2 : 1615, operator : +
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 59, num2 : 3020, operator : +
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 8810, num2 : 8627, operator : -
22:46:46.264 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7644, num2 : 9560, operator : +
22:46:46.264 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 8588, num2 : 94, operator : -
22:46:46.264 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 5790, num2 : 8704, operator : +
22:46:46.265 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 1207, num2 : 6436, operator : +
22:46:46.265 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7600, num2 : 6645, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6039, num2 : 2968, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 1763, num2 : 9907, operator : -
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 1004, num2 : 6288, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7891, num2 : 1750, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 4275, num2 : 3976, operator : +
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7064, num2 : 9714, operator : +
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 8334, num2 : 59, operator : -
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 271, num2 : 2286, operator : -
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 47, num2 : 3478, operator : -
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7559, num2 : 1964, operator : +
22:46:46.283 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 6042 - 947 = 5095
22:46:46.283 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 6424 - 9308 = -2884
22:46:46.284 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 6795 + 1615 = 8410
22:46:46.284 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 59 + 3020 = 3079
22:46:46.284 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 8810 - 8627 = 183
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 7644 + 9560 = 17204
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 8588 - 94 = 8494
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 5790 + 8704 = 14494
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 1207 + 6436 = 7643
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 7600 + 6645 = 14245
22:46:46.284 [main] INFO com.example.demo.plusandminus.concurrent.Main - 작업 종료