스레드에 대해 (1)

jinvicky (남궁진)·2024년 8월 13일

Cs

목록 보기
6/6

쓰레드: 프로세스 내에서 실행되는 작은 실행 단위,
모든 프로세스가 1개 이상의 스레드를 가진다.
프로세스 간 통신보다 스레드 간의 통신이 더 효율적이다.

2개 이상의 스레드를 동시 실행하면 멀티 스레딩이 된다.
실행부터 동작하는 스레드가 이들 중 메인 스레드가 된다.

비동기와 코루틴
Q. 코루틴은 비동기인가? A. 아니.
코루틴은 비동기를 도와주는 기술이지, 단순 비동기라고 치부할 수 없다. 그 이상임.

프론트는 promise, async-await으로 비동기를 처리한다.
백엔드는 비동기 처리를 어떻게 할까?
1. 원시적인 방법

  • 신규 스레드를 생성한다.
  1. 고수준 api 사용
  • executorService를 사용한다.
    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.submit(() -> performTask());
    (executorService를 사용하면 스레드 풀을 관리할 수 있으며, 좀 더 효율적으로 스레드를 활용할 수 있다.)

그럼 또 여기서 동시성이냐 병렬성이냐가 궁금해진다.
동시성은 마치 동시에 실행하는 것처럼 스케줄링을 해서 보이게 하는 것이고,
병렬성은 실제 물리적으로 동시 실행을 구현하는 것이라고 한다.

그럼 동시성은 흉내만 내는 것인가? 라고 생각했는데 그건 아니고,
작업을 분할해서 context switching을 통해 여러 프로그램이 논리적으로
동시실행으로 보이게끔 한다고 한다.
"싱글 코어 또는 멀티 코어에서 멀티 스레딩을 하기 위해 적용"

동시성의 장점은 멀티코어 환경에서 찾을 수 있다.
1. 코어 갯수보다 작업 갯수가 더 많다면 동시성 방식이 필요하다.
2. 모든 코어들이 리소스를 각각 독립적으로 가지고 있지 않다.
한 작업이 리소스를 기다린다면, 다른 작업을 실행하는 것이 더 효율적
3. 특정 코어에 부하 집중을 피해 분산한다.
4. 동시성으로 한 작업이 오랫동안 cpu를 독점하지 않도록 한다.
(동시성을 통해 여러 작업이 번갈아가며 빠르게 실행되도록 관리)

생각해볼 수 있는 것들

  • 메인 스레드의 작업 분산처리를 어떤 전략, 기술로 해볼 수 있을까?

비동기식으로 서비스를 진행할 거라면, 스레드 풀 설정을 해서 효율성을 올린다.
그럼 스레드 풀은 또 뭐냔 말이다...

스레드 풀 작동 방식
1. corePoolSize만큼 실행하다가
2. Work Queue에 task를 대기하고
3. Work Queue가 가득 참에 따라 스레드 풀이 maxPoolSize만큼 늘어난다.

무언가를 pool로 관리한다는 것은 미리 만들어두고 재사용할 목적이라고 개인적으로 생각한다.
=> 메모리 낭비 싫어서. 그럼 스레드 얼만큼 필요해?

그럼 스레드풀을 어떻게 구현하나 봤더니 또 ThreadPoolExecutor라는 게 있네?
ThreadPool과 Work Queue 구성으로 이루어져 있다.
(자세한 건 별도로)

매번 스레드 만드는 것도 별로 좋은게 아니야. #CachedThreadPool
근데 캐시스레드풀도 양이 많아지거나 실행시간이 길면 불리하다.
=> fixed thread pool 지향하자는 결론이 나옴.

cpu intensive와 io intensive의 차이점이 뭐야?

많은 cpu 사이클을 사용하는 코드. 예) 암/복호화, 비디오 트랜스코딩

- 대부분 백엔드 로직이 io intensive다.
많은 io(네트워킹, 디스크 등)를 사용하는 코드

  • 파일 업/다운로드
  • db 쿼리 수행
  • 웹서버 요청 처리

왜 io intensive가 나왔을까? 이것들을 처리하려면 그에 맞는 스레드 설정을 해주어야 하니까!
스레드 설정 이전에 어떻게 io intensive를 줄일 수 있을까 보자.

  • 캐싱 (메모리에 반복적인 io 작업의 데이터를 캐싱)
  • 로드밸런싱 (i/o 작업을 여러 스레드/프로세스에 분산해서 동시 i/o 작업을 효율 처리한다)

Number of threads = Number of Available Cores Target CPU utilization (1 + Wait time / Service time)

위 공식으로 스레드 설정을 한다
reference: https://velog.io/@vanillacake369/Async-Size-%EC%84%A4%EC%A0%95-%EA%B8%B0%EC%A4%80%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B3%A0%EB%AF%BC%ED%95%B4%EB%B3%B4%EC%9E%90-feat.ThreadPoolQueue

스레드 관련해서 워커 스레드 패턴을 코루틴 공부하면서 들었다.
워커 스레드를 n개 보유하고 있는 곳을 스레드 풀이라고 한다.

워커 스레드 패턴에는 채널이 존재하는데 내부에 requestQueue를 가지고 있어서
워커 스레드를 저장한다.

스프링은 톰캣 서버를 주로 사용하잖아. 톰캣은 스레드 관리를 어떻게 할까?
톰캣은 자바 서블릿 컨테이너다.
서블릿 컨테이너를 사용하지 않으면 커넥션 관리부터 시작해서 데이터 처리, 세션 등
요청에 관련된 스레드 관리를 직접 해주어야 한다.

톰캣 덕분에 기능 개발에 더 집중할 수 있다.

스레드 안전: 멀티스레드 프로그래밍에서 함수/변수/객체가 여러 스레드로부터 동시 접근해도
프로그램 실행에 문제가 없는 것을 말한다.

정합성과 메모리 가시성 개념이 나온다.

2개 이상의 스레드가 레이스 컨디션에 들어가거나 같은 객체에 동시 접근해도
연산 결과는 정합성이 보장될 수 있게 메모리 가시성이 확보된 상태이다.

판단 기준

  • 전역 변수, 힙, 파일과 같이 여러 스레드가 동시 접근 가능한 자원을 사용하는가?
  • 핸들과 포인터를 통한 데이터의 간접 접근이 가능한가?
  • 부수 효과를 가져오는 코드가 있는가?

자바에서 스레드 안전하게 개발하기

  • 싱글톤 패턴으로 개발하기
  • 스프링의 Application Context가 싱글톤 패턴으로 개발되었다.
    (우리가 실제 구현한 싱글톤 패턴은 스레드 안전하지 X)

병렬 프로그래밍 관련 노션
https://valuable-parsnip-9b0.notion.site/2-6c48a85a7862405588dd6410d550504c

  • 모든 자바 객체가 사용가능하다.
  • 자바에서는 synchronized 구문으로 락을 제공함
  • 락은 스레드가 synchronized 블록 접근 전에 확보되며, 예외 발생 여부 관계없이 해당 블록을 벗어날 때 자동 해제된다.

Q. db 락 걸렸을 때 쿼리 수행 timeout걸리고 그랬는데 자바 락도 그런건가?
락이 좀 헷갈린다.
스레드 락이랑 db 락은 다른 건데, 여기서는 스레드락만 신경쓰자.
프로세스 내 공유 자원 접근 시 데이터 일관성 유지다.

db 락 걸렸다는 소리는 현재 A 트랜잭션이 특정 자원에 락을 걸고 작업 중인데
다른 B 트랜잭션이 자원 접근을 시도해서 튕겨진 것이다.
A가 끝날 때까지 기다리거나 기존 트랜잭션을 SHOW PROCESSLIST; KILL 프로세스 ID; 등으로
킬해야 한다.

MYSQL DB도 스레드 기반이다. (프로세스 기반이 아니라는 것에 초점)

동시성 관련 노션 (인프런 강의를 들어보자.)

동시성을 고려한다면 REDIS도 고려 대상이 된다.
동시성 이슈를 위해서는 lock을 구현해야 한다.
lock은 2군데에서 구현할 수 있다.
1. mysql(db) 2. redis

또한 요즘은 분산 환경 추세이기 때문에 단순히 단일 서버만 생각하지 않고
다중화 서버를 고려해서 분산락을 설계해야 한다.

분산 락 기반이라면 Redisson을 사용한다. pub-sub 기반으로 동작하는데
채널을 1개 만들고, 락을 점유하고 있는 스레드가 락을 받으려는 쓰레드에게
점유 해제를 공지한다.

countdownLatch라는 게 있네, 다른 쓰레드를 기다리는 방법이다.

  • 스레드 풀이 완료될 때까지 대기한다.
  • 자식이 완료되기 전까지 부모를 대기하지 말고 모든 스레드가 동시 시작할 때까지 자식 스레드를 대기시킨다.
profile
하나씩 차근차근하게 하자:)

0개의 댓글