Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4단계] 에버(손채영) 미션 제출합니다. #755

Merged
merged 29 commits into from
Sep 18, 2024

Conversation

helenason
Copy link
Member

@helenason helenason commented Sep 13, 2024

안녕하세요 켈리!

어느덧 톰캣 구현 마지막 단계네요.

마지막 리뷰도 잘 부탁드립니다!

3단계가 아직 머지되지 않아 4단계 커밋 부분 링크 남겨둘게요 :)

Copy link

@kelly6bf kelly6bf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 에버! 4단계도 빠르게 잘 해주셨네요! 🤭

코드 개선에 대해서는 딱히 드릴 말씀이 없어서 개념 질문 위주로 남겨보았습니다! 나중에 면접관이나 동료들이 코드를 보고 물어보겠다 싶은 내용들로 남겼으니 편하게 답변해주세요! 마지막까지 파이팅!!!

@@ -46,10 +50,16 @@ void testRunnableThread() throws InterruptedException {
Thread thread = new Thread(new RunnableThread("hello thread"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thread객체를 생성시 파라미터로 왜 RunnableThread를 넣어줘야할까요? 파라미터로 들어간 RunnableThread 객체의 인스턴스는 어떤 행위를 해줄까요?

가볍게 찾아보면 재밌을거 같아요! (저 좀 알려주세요! 😎)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runnable 인터페이스를 구현함으로써, 스레드가 실행될 때 수행할 역할을 정의하기 위함입니다!

아래는 Thread 클래스에 정의되어있는 run 메서드인데, Runnable 인터페이스를 구현한 클래스를 생성자 파라미터로 전달하는 경우 아래 메서드가 재정의됩니다.

@Override
public void run() {
    Runnable task = holder.task;
    if (task != null) {
        Object bindings = scopedValueBindings();
        runWith(bindings, task);
    }
}

@@ -41,15 +46,17 @@ private static final class SynchronizedMethods {

private int sum = 0;

public void calculate() {
public synchronized void calculate() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스레드 동기화는 정확히 어떤 개념일까요? 면접에 자주 등장하는 단골 질문이라는데 에버의 설명이 궁금하네요! 🧑‍💻

Copy link
Member Author

@helenason helenason Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2개 이상의 스레드가 사용되는 환경, 즉 멀티스레드 환경에서, 여러 스레드가 동시에 공유 자원에 접근할 때 발생할 수 있는 문제를 방지하기 위해 사용되는 기술입니다. 하나의 스레드가 특정 로직을 수행하고 있을 때 다른 스레드의 요청이 들어올 경우, 해당 로직을 동시에 수행하는 것이 아닌, 하나의 스레드가 일을 마무리할 때까지 기다리도록 하는 방식입니다. 이를 통해 공유 자원의 일관성을 유지하고 race condition을 방지합니다.

스레드 동기화가 무엇인지 추상적으로만 알고 있었는데, 이렇게 정리할 수 있는 기회가 되니 좋네요 :)

@@ -18,6 +21,8 @@
*/
class SynchronizationTest {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 테스트에서 사용된 synchronized는 정확히 어떤 역할을 해줄까요?!

Copy link
Member Author

@helenason helenason Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나의 스레드가 calculate 로직을 수행하고 있으면 다른 스레드가 해당 로직을 수행하지 못하도록 합니다. 즉, 여러 스레드가 공유하고 있는 sum 필드에 동시에 접근하지 못하게 함으로써, 동시성에 의한 예상치 못한 에러를 방지합니다!

executor.submit(logWithSleep("hello cached thread pools"));
executor.submit(logWithSleep("hello cached thread pools"));
executor.submit(logWithSleep("hello cached thread pools"));
// 실험) maximumPoolSize 이상 횟수로 실행시키면? -> OutOfMemoryError

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스레드 개수에 따라 여러 문제가 발생할 수 있기 때문에 적절한 스레드풀의 개수를 맞추는건 아주 중요한 작업이라고해요! 그럼...

  • 스레드가 너무 적을때 어떤 문제가 발생할까?
  • 스레드가 너무 많을때 어떤 문제가 발생할까?
  • 스레드 개수를 늘리거나 혹은 줄일 상황은 어떤 지표를 보고 판단할 수 있을까?
  • 우리가 개발하고 있는 서비스에서 스레드풀 개수를 책정할 지점은 어디일까?

같이 고민 해볼까요? 🤔🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스레드가 너무 적을 때 어떤 문제가 발생할까?

동시에 들어오는 요청의 수보다 스레드의 수가 한없이 적을 경우, 항상 모든 스레드가 점유되어 있기 때문에 스레드의 대기 시간이 길어질 수 있습니다. 또한 대기 중인 스레드가 큐를 모두 채울 경우 일부 요청은 거절될 수 있습니다.

스레드가 너무 많을 때 어떤 문제가 발생할까?

반대로 동시에 들어오는 요청의 수보다 스레드의 수가 한없이 많다면, 쉬고 있는 스레드가 많아져 불필요하게 메모리를 차지하고 있을 수 있습니다.

스레드 개수를 늘리거나 혹은 줄일 상황은 어떤 지표를 보고 판단할 수 있을까?

큐에 쌓이는 요청의 개수를 보고 판단할 수 있을 것 같습니다. 큐에 너무 많은 요청이 쌓이는 경우 스레드 수를 늘려야 할 것이고, 큐에 요청이 전혀 들어오지 않는 경우는 쉬고 있는 스레드가 많다 판단하여 스레드의 수를 줄여야 할 것입니다.

우리가 개발하고 있는 서비스에서 스레드풀 개수를 책정할 지점은 어디일까?

각 HTTP 요청 별로 스레드가 생성되기 때문에 HTTP 요청이 들어오는 Connector 클래스에서 책정하면 될 것 같습니다!


모두 저의 개인적인 생각이므로, 혹시 켈리가 다르게 생각하고 계신다면 공유해주시면 감사하겠습니다 😁

accept-count: 1
max-connections: 1
accept-count: 10 # 요청의 대기열(queue) 길이 -> http call count 관련
max-connections: 2 # 동시에 처리 가능한 서버의 연결 개수 -> 테스트 결과와 관련

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하루 10만건 정도의 요청이 들어오는 서비스라면 스레드 개수를 얼마나 잡아보면 좋을까요? 같이 고민해볼까요? 🤭

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 고민 거리 주셔서 감사해요! 😃

알아보니 적정 스레드 수는 CPU 코어 수 × (1 + (대기 시간 / 처리 시간))로 계산하곤 하더라구요. CPU 코어의 수와 요청 처리 시간 및 대기 시간에 따라 설정해야 할 스레드 개수가 달라질 것 같아요! 또, 하루 10만건의 요청이 어느 분포로 들어오는지도 측정해본 후 개수를 설정해볼 것 같아요. 동시에 들어오는 요청의 수가 중요하다고 생각되어서요! 켈리는 어떻게 생각하시나요? 혼자서 나름 고민하다보니 켈리의 생각도 궁금해지네요 🤔

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 에버의 의견에 동의합니다! 하루 10만건이 특정 시간대에 몰려서 오는건지, 혹은 하루동안 균일하게 요청이 들어오는건지에 따라 적정 스레드 풀 계수를 산정할거 같아요! 물론 저희가 취업전에 이정도 트래픽을 받아볼 일이 없..겠지만..! 가끔 이런 공상을 해보는것도 재밌더라구요 🤭

@@ -37,4 +37,5 @@ void test() throws InterruptedException {
// 하지만 디버거로 개별 스레드를 일시 중지하면 if절 조건이 true가 되고 크기가 2가 된다. 왜 그럴까?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

왜 그럴까요..!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디버거로 두 개의 스레드를 일시 중지한 후 동시에 실행시키면 users 리스트가 비어있는 상태에서 동시에 users.contains문이 실행되고 두 스레드 모두에서 user는 리스트에 포함되어있지 않다고 출력됩니다. 따라서 두 스레드 모두에서 user 데이터 추가를 시도하게 되고 결과적으로 리스트 사이즈는 2가 됩니다. 이를 해결하기 위해 synchronized 키워드를 붙여 동기적으로 차례차례 실행되도록 하였습니다!


private static SessionManager INSTANCE;

private final List<Session> sessions = new CopyOnWriteArrayList<>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CopyOnWriteArrayListArrayList와 다르게 내부적으로 어떤 처리를 해줄까요?! 어떤 방식으로 스레드 안전을 보장해주는지 에버의 설명이 듣고 싶어요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CopyOnWriteArrayList는 ArrayList를 thread safe하게 사용하기 위한 자료구조입니다. CopyOnWriteArrayList는 쓰기 작업을 할 때, 즉 내부를 변경하는 작업을 할 때, 항상 깨끗한 복사본을 만들어 작업합니다! 아래는 CopyOnWriteArrayList의 add 메서드 로직입니다.

public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}

복사본을 만들어 작업하기 때문에 읽기 작업 리스트와 쓰기 작업 리스트를 분리할 수 있습니다. 따라서 읽기와 쓰기가 동시에 요청되어도 예기치 못한 에러를 마주하지 않을 수 있어요!

private static final Logger log = LoggerFactory.getLogger(ConnectorTest.class);
private static final AtomicInteger count = new AtomicInteger(0);

@DisplayName("거의 동시에 300개의 요청이 들어오면?")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트도 꼼꼼하게.. 굳 👍

@helenason
Copy link
Member Author

안녕하세요 켈리!

좋은 고민거리 던져주셔서 감사해요.

코드를 작성하면서 생각해보지 못한 주제들에 대해 고민해볼 수 있는 기회가 되어 좋았어요 :)

감사합니다 🙇‍♀️

Copy link

@kelly6bf kelly6bf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에버! 4단계까지 고생 정말 많으셨습니다!! 생각보다 질문을 많이 드렸는데 하나하나 정성스럽게 찾아보고 답변해주신게 보여서 너무너무 감동했어요 🥹 답변도 너무 좋았습니다!

4단계 이만 머지할게요! 다음 단계도 파이팅!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants