자바 Thread_9
Lock Free 알고리즘 - 데이터 구조 및 기술
멀티 스레드 프로그래밍의 대부분이 락으로 이뤄지고 병행성 문제 대부분을 락으로 쉽고 안전하게 해결할 수 있습니다 락은 오랫동안 사용되어 왔으며 H/W, S/W 큰 도움이 되어 왔습니다
경쟁 상태와 데이터 경쟁과 같은 모든 병행성 문제를 락을 사용으로 해결가능
그러나 엔지니어는 항상 절충을 하게 됩니다
대부분의 문제에는 솔루션이 둘 이상 존재함 (진짜 억까가 아니라면) 따라서 사용할 수 있는 도구가 많을수록 작업에 맞는 도구를 선택할 수 있죠 옳은 도구를 선택하면 좋은 엔지니어가 될 수 있음
1. 데드락
- 애플리케이션에 발생할 수 있는 문제 중 가장 짜증 나는 것일 겁니다 데드락 검출과 해결 로직을 구현할 노력과 시간이 없으면 데드락은 해결할 수 없습니다 데드락으로 애플리케이션이 완전히 멈출 수 있고 애플리케이션에 락이 많을수록 데드락이 걸릴 확률이 높습니다
2. 다른 문제로 같은 락을 사용하는 멀티 스레드가 있는 경우
- 스레드 하나가 나머지보다 락을 더 오래 가지고 있을 수 있습니다
- 스레드 하나만 같은 락으로 보호되는 임계 영역에 들어갈 수 있는데 긴 임계 영역이 다른 스레드를 느리게 할 수 가능성이 있음
- 따라서 나머지 스레드가 전부 가장 느려지는 상황에 처하게 됩니다
3. 우선순위 역전
- 리소스 하나와 그 리소스의 락을 공유하는 스레드가 두 개일 때 생기는 문제입니다
- 스레드 하나가 운영체제에 의해 낮은 우선순위를 갖게 되는 것이죠
- 예를 들어 백그라운드 문서 저장이 낮은 우선순위를 갖고 사용자 인터페이스가 높은 우선순위를 갖는 것이죠
- 스레드에 부여된 이 우선순위로 높은 우선순위의 스레드가 우선적 또는 자주 스케줄링됩니다
- 만약 낮은 우선순위 스레드가 공유 락을 획득하고 운영체제에 의해 선택되면 어떻게 될까요?
- 우선순위 스레드는 이제 고립되는데 운영체제가 낮은 우선순위 스레드를 다시 충분히 스케줄링하지 않기에 락이 릴리스되지 않아 우선순위 스레드가 락을 획득하지 못해 진행하지 못합니다0
- 애플리케이션의 무응답성 문제뿐 아니라 실행 문제도 생기게 되죠 다른 운영체제가 장단점을 가진 다른 방법으로 이 문제를 해결할 수 있습니다
4. 스레드가 락을 지닌 채 그냥 죽거나 인터럽트될 때 (찾을려면 사람이 미쳐버림)
우선순위 역전보다 더 심한 문제이다
모든 스레드가 영원히 정체되고 데드락처럼 상황은 회복되지 않을 확률이 매우 크다.이를 방지하기 위해 개발자는 모든 중요 영역을 타임아웃을 지닌 tryLock을 사용해 try와 block하여 감싸는 등의 복잡한 코드를 쓰도록 요구됩니다
또 성능 오버헤드 문제가 있는데 락을 얻기 위한 스레드 간 다툼이 생겨요 스레드 A가 락을 획득하면 스레드 B가 그 락을 가지려 해 스레드 B가 블록됩니다 그 결과 다음과 같습니다
스레드 B가 블록되어 스레드 B에서 다른 스레드로의 컨텍스트 스위치가 일어나는데, 락이 릴리스될 때 스레드 B를 가져오는 오버헤드가 일어나게 됨.
이런 오버헤드와 지연 시간은 애플리케이션 대부분에서 감지하지 못하지만 밀리초보다 짧은 지연 시간으로 연산하는 초고속 거래 시스템 같은 애플리케이션 같은 경우 이런 오버헤드는 고려해야 할 중요 요소입니다
- 락에 대한 대안으로 무엇이 있을까요? 우선 최초 락이 필요했던 주요 이유를 살펴봅시다 문제의 핵심은 멀티 스레드가 리소스를 공유하고 최소 하나의 스레드가 리소스를 수정하여 원자적 연산이 되지 않은 것이죠
- 어떻게 원자적 연산이 되지 않은것인가??
문제는 소프트웨어 추상화 스택의 가장 아래 단계에 있습니다 Java 연산은 CPU에서 실행하는 하나 이상의 하드웨어 연산으로 변하는데 예를 들어 count++는 최소 세 개의 명령어로 변합니다- 카운트 값을 읽어라
- 새로운 값을 계산하라
- 값을 카운트에 저장하라
여러 하드웨어 명령어의 실행 사이에 다른 스레드가 카운트 값을 수정할 수 있습니다
따라서 lock free 프로그래밍은 연산을 활용하여 단일 하드웨어 명령어로 실행될 수 있게 보증하는 것입니다 단일 하드웨어 명령어는 의미상 원자적이라 스레드는 안전합니다
- long/double을 제외한 모든 원시 유형, 모든 레퍼런스 그리고 volatile long/double의 Read/Assignment가 있습니다.
- 데이터 경쟁을 완전히 피하려면 락 없이 읽고 쓸 수 있는 모든 공유 변수를 volatile로 하는 데 동의할 수 있죠, 그러면 리스트가 짧아지고 쉽게 기억할 수 있습니다
- 원자적으로 실행할 수 있는 두 번째 연산은 JavaUtil ConcurrentAtomic 패키지에 위치한 atomic 클래스에 있습니다
- 이 클래스는 직접 사용을 권하지 않는 비저장 Java 클래스를 활용하고 메서드 일부가 아키텍처가 제공하는 원자적 연산을 포함한 낮은 단계의 플랫폼 특정 연산을 활용할 수 있는 native 메서드에 대한 접근을 제공합니다
(Java 10의 AtomicPackage에서 사용 가능한 클래스 목록입니다) - 각 클래스는 매우 다양하면서 유용한 원자적 연산을 제공하며 이런 원자적 연산을 사용해 아주 강력한 lock free 알고리즘과 데이터 구조를 디자인할 수 있음
출처 - https://kmooc.udemy.com/course/java-multi-threading/ (Java 멀티스레딩, 병행성 및 성능 최적화 - 전문가 되기 )