안녕하세요 😀
유로띠 입니다 😉
실용주의 프로그래머
TIL (Today I Learned)
3줄 요약
✏️ 공유 상태? 어떤 상태? 틀린 상태
✏️ 불규칙한 실패는 동시성인 경우가 많다.
✏️ 상태 비공유 특성(No Shared State) - 액터
DAY 6
오늘 읽은 범위: 6장. 동시성
😉 책에서 기억하고 싶은 내용을 써보세요.
🟢 동시성은 둘 이상의 코드 조각이 실행될 때 동시에 실행 중인 것처럼 행동하는 것이다. 그리고 병렬성이란 실제로 동시에 실행되는 것이다. (P.241)
동시성 -> 파이버, 스레드, 프로세스 등을 사용하여 동시성을 구현
병렬성 -> 두 가지 일을 동시에 할 수 있는 하드웨어가 필요
🟢 사용자와 상호작용하고, 데이터를 불러오고, 외부 서비스를 호출하는 일을 동시에 해야 한다. 만약 이를 순차적으로 하나를 끝낸 다음에 다음 일을 하는 식으로 수행한다면, 시스템은 거북이처럼 느리게 느껴질 것이고 프로그램을 구동하는 하드웨어의 성능도 최대로 활용하지 못할 것이다. (P.242)
🟢 가장 큰 문제는 '공유 상태(shared state)'다. 둘 이상의 코드 뭉치가 하나의 변경 가능한 데이터를 참조하고 있다면 공유 상태가 존재하는 것이다. (P.242)
시간적 결합 깨트리기
🟢 시간에는 우리가 신경 써야 할 측면이 두 가지 있는데, 동시성(동시에 일어나는 일들)과 순서(시간의 흐름 속에서 일들의 상대적인 위치)다. (P.243)
🟢 우리는 동시성을 확보해야 한다. 시간이나 순서에 의존하는 시간적 결합을 끊는 방법을 생각해 내야 한다. 그렇게 함으로써 유연성도 얻을 수 있고, 작업 흐름 분석과 아키텍처, 설계, 배포와 같은 개발의 여러 측면에서 시간과 관련된 의존성도 함께 줄일 수 있다. 결과적으로 분석하기 더 쉽고 응답속도도 더 빠르며 더 안정적인 시스템을 만들 수 있을 것이다. (P.244)
🟢 작업 흐름 분석으로 동시성을 개선하라. (P.244)
활동 다이어그램
최상위 작업(1, 2, 4, 10, 11번)은 제일 먼저 동시에 수행 가능
그 후에 3, 5, 6번 작업을 동시에 수행 가능
그러나, 실제로는 힘들다. 그래서 설계가 필요하다.
8번 작업인 믹서 돌리기에 1분이 걸리기 때문에 그동안 10, 11번을 할 수 있고
어쩌면 그러고도 다른 손님을 응대할 시간이 남을 수도 있다. (P.246)
🟢 동시성은 소프트웨어 동작 방식이고, 병렬성은 하드웨어가 하는 것이다. (P.247)
공유 상태는 틀린 상태
🟢 공유 상태는 틀린 상태다. (P.249)
🟢 레스토랑 예시를 코드로 표현해 보자. (P.250)
종업원1이 진열장의 파이의 개수(pie_count)를 확인하고 작업을 진행한다.
동시에 종업원2 도 진열장을 확인하고 작업을 진행한다.
둘 중 하나가 마지막 남은 파이 조각을 획득하고, 다른 종업원은 일종의 예외 상태에 빠진다.
🟢 문제는 어느 프로세스도 자신이 보는 메모리가 일관되어 있음을 보장할 수 없다는 점이다. (P.250)
🟢 세마포어는 단순히 한 번에 한 사람만이 가질 수 있는 무언가다. 세마포어를 만들어서 다른 리소스의 사용을 제어하는 데 쓸 수 있다. (P.251)
종업원 둘 다 세마포어를 얻으려고(lock) 시도하지만 한 명만 성공한다.
case_semaphore.lock() // 잠금
if display_case.pie_count > 0
promise_pie_to_customer()
display_case.take_pie()
give_pie_to_customer()
end
case_semaphore.unlock() // 잠금 해제
이 접근 방식에는 몇 가지 문제가 있다.
진열장에 접근하는 모든 사람이 빠짐없이 세마포어를 사용해야만 제대로 동작한다는 것이다.
만약 누군가가 세마포어를 획득하고 반납을 깜빡한다면, 대기 상태로 존재하기 때문에 혼돈에 빠진다.
🟢 리소스를 트랜잭션으로 관리하라. (P.252)
현재의 설계가 미흡한 것은 진열장 사용을 보호할 책임을 진열장을 사용하는 사람에게 전가하기 때문이다.
제어를 중앙으로 집중시키자.
즉, 책임을 종업원에게 전가하지 말고 사용되는 진열장으로 제어권을 주자. (제어의 역전; IOC)
# 종업원
slice = display_case.get_pie_if_available()
if slice
give_pie_to_customer()
end
# 진열장
def get_pie_if_available()
@case_semaphore.protect() {
if @slices.size > 0
update_sales_data(:pie)
return @slices.shift
else
return false
end
}
end
여러 리소스와 트랜잭션
파이와 아이스크림이 둘 다 필요한 '파이 알라모드'를 주문하는 경우
slice = display_case.get_pie_if_available()
scoop = display_case.get_ice_cream_ifavailable()
if slice && scoop
give_order_to_customer()
end
하지만 제대로 동작하지 않을 것이다. 파이를 꺼냈지만 아이스크림이 없는 경우가 발생되고 문제를 해결하려면 파이를 다시 반환하는 메서드를 추가해야 한다.
slice = display_case.get_pie_if_available()
if slice
try {
scoop = freezer.get_ice_cream_if_available()
if scoop
try {
give_order_to_customer()
}
rescue {
freezer.give_back(scoop)
}
end
}
rescue {
display_case.give_back(slice)
}
end
여전히 이상적인 코드가 아니다. 코드가 지저분하고 실제로 무슨 일을 하는 것인지 알아보기 어렵다.
이 코드를 새로운 모듈로 옮기고, 클라이언트는 그냥 '아이스크림 올린 애플파이 주세요'라고 요청할 수 있어야 한다. 결과는 성공 아니면 실패뿐이다.
🟢 불규칙한 실패는 동시성 문제인 경우가 많다. (P.257)
🟢 러스트는 데이터의 소유권이라는 개념을 강제한다. 변경 가능한 데이터 조각은 어느 한 시점에 단 하나의 변수나 매개 변수만 참조를 가질 수 있다.(P.257)
액터와 프로세스
🟢 액터와 프로세스를 사용하면 흥미로운 방식으로 동시성을 구현할 수 있다. 공유 메모리 접근을 동기화하느라 고생할 필요도 없다. (P.258)
🟢 액터는 자신만의 비공개 지역 상태를 가진 독립적인 가상 처리 장치다. 각 액터는 우편함을 하나씩 보유하고 있다. 액터가 잠자고 있을 때 우편함에서 메시지가 도착하면 액터가 깨어나면서 메시지를 처리한다. 처리가 끝나면 우편함의 다른 메시지를 처리한다. 만약 우편함이 비어 있으면 다시 잠든다. (P.259)
🟢 액터는 언제나 동시성을 띤다.
액터를 관리하는 것이 하나도 없다.
시스템이 저장하는 상태는 오직 메시지 그리고 각 액터의 지역 상태뿐이다.
모든 메시지는 일방향이다.
액터는 각 메시지를 끝날 때까지 처리하고 중간에 다른 일을 하지 않는다. 즉, 한 번에 하나의 메시지만 처리한다.
액터들은 아무것도 공유하지 않으면서 비동기적으로 동시에 실행된다. (P.259)
🟢 공유 상태 없는 동시성을 위하여 액터를 사용하라. (P.260)
🟢 액터 모델에서는 동시성을 다루는 코드를 쓸 필요가 없다. 공유된 상태가 없기 때문이다(No Shared State). (P.265)
🟢 얼랭의 프로세스는 가볍기 때문에 컴퓨터 한 대에서 수백만 개를 실행시킬 수 있고, 프로세스끼리 메시지를 보내서 통신한다. 프로세스들은 각각 격리되어 있어서 상태를 공유하지 않는다. (P.265)
칠판
🟢 일종의 '자유방임주의'적 동시성이다. (P.268)
🟢 최초의 칠판 시스템 중 하나는 데이비드 겔런터가 만든 린다였다. 린다는 수집한 사실을 유형별 튜플로 저장했다. 애플리케이션은 린다에 새로운 튜플을 쓰거나 저장된 튜플을 패턴 매칭 같은 방법으로 조회할 수 있었다. (P.268)
🟢 칠판 시스템을 법적 요구 사항을 캡슐화하는 규칙 엔진과 함께 사용하면 이러한 어려움을 우아하게 해결할 수 있다. (P.270)
🟢 칠판으로 작업 흐름을 조율하라. (P.270)
🟢 카프카나 NATS 같은 이런 메시징 시스템은 단순히 데이터를 A에서 B로 보내는 것보다 훨씬 많은 일을 한다. 특히 이벤트 로그의 형태로 영속성을 제공하고, 패턴 매칭 형태로 메시지를 가져오는 것도 지원한다. 이 말은 메시징 시스템을 칠판으로도 사용할 수 있고, 여러 액터를 실행하는 플랫폼으로도 사용할 수 있다는 것이다. 심지어 동시에 둘 모두로 사용할 수도 있다. (P.270)
🧐 오늘 익은 소감은? 떠오르는 생각을 가볍게 적어보세요
✅ 동시성은 특히나 다루기가 위험하고 겁난다. 단순히 메시지를 push / pop 하여도 하나의 메시지를 동시에 pop하는 순간 동시성 문제가 발생된다. 그리고 트랜잭션은 대부분 비즈니스 로직에 중요한 부분을 차지한다. 책에 예시가 잘 나와있어서 이해하기가 쉬웠다.
✅ 처음으로 활동 다이어그램과 칠판 시스템을 알게 되었는데, 작업의 흐름을 분석하는데 도움이 될 것 같고 나중에 한번 사용해봐야겠다는 생각이 든다.
✅ 액터 관련 Nact나 Erlang에 대해 공부해보고 싶다는 생각이 든다.
🙋♂️ 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
⭐️ Nact (Node.js용 액터 라이브러리)
Nact는 Node.js에서 실행되는 액터 모델 구현체입니다.
위치 투명성과 상태 비공유 특성은 도메인 주도 개발에서 서브 시스템에 대한 배치에 관한 아키텍처상 결정을 유예할 수 있게 해줍니다.
⭐️ 얼랭(Erlang)
범용 병렬 프로그래밍 언어이다. 함수형 언어가 효율적으로 산업 현장 등에서 사용되는 유명한 사례이다.
1998년 공개된 실시간 시스템을 위한 함수형 프로그래밍 언어이다.
⭐️ 세마포어(semaphore)
깃발이라는 뜻!
옛날에 기찻길에서 깃발 표식으로 파란색이면 지나가도 되고 빨간색이면 섰다가 다른 기차가 지나가면 지나가게끔 하는 용도로 깃발을 사용했다고 합니다.
두 개의 원자적 함수로 조작되는 정수 변수로서, 멀티프로그래밍 환경에서 공유 자원에 대한 접근을 제한하는 방법으로 사용된다.
⭐️ ACID
A (Atomicity) : 원자성 - 트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것을 의미한다.
C (Consistency) : 일관성 - 트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것을 의미한다.
I (Isolation) : 격리성 - 트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것을 의미한다.
D (Durability) : 영속성 - 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함을 의미한다.
⭐️ Array.prototype.shift()
shift() 메서드는 배열에서 첫 번째 요소를 제거하고, 제거된 요소를 반환합니다. 이 메서드는 배열의 길이를 변하게 합니다.
const array1 = [1, 2, 3];
const firstElement = array1.shift();
console.log(array1);
// expected output: Array [2, 3]
console.log(firstElement);
// expected output: 1
'Books > 실용주의 프로그래머' 카테고리의 다른 글
8장. 프로젝트 전에 (0) | 2022.04.04 |
---|---|
7장. 코딩하는 동안 (0) | 2022.04.01 |
5장. 구부러지거나 부러지거나 (0) | 2022.03.30 |
4장. 실용주의 편집증 (0) | 2022.03.25 |
3장. 기본 도구 (0) | 2022.03.23 |