운영체제 · 동시성과 락 · 실습
사라지는 갱신과 교착을 손으로 만든다
목표
경쟁 상태와 교착을 글로 읽는 대신 직접 만들어 보고 직접 없앱니다. 여덟 단계를 지나면 "락을 걸면 된다"가 아니라 어디까지 감싸야 하는지, 왜 순서를 통일해야 하는지, 언제는 아예 공유하지 않는 편이 나은지를 손으로 확인하게 됩니다.
왜 중요한가
동시성 버그는 대부분의 실행에서 아무 일도 일으키지 않습니다. 그래서 시험을 통과하고 운영에서 터집니다. 이 실습의 2단계는 그것을 숫자로 보여 줍니다. 같은 프로그램을 다섯 번 돌리면 다섯 번 다른 값이 나옵니다.
파이썬에는 전역 인터프리터 잠금이 있어서 스레드가 진짜로 동시에 실행되지는 않습니다. 그런데도 갱신은 사라집니다. 경쟁 상태의 원인은 병렬 실행이 아니라 읽고 고쳐 쓰는 구간이 나뉘어 있다는 사실이기 때문입니다. 인터프리터가 그 사이에서 스레드를 바꾸기만 하면 충분합니다. 이 점이 오히려 개념을 선명하게 만들어 줍니다.
교착도 같습니다. 5단계와 6단계 사이에서 바뀌는 것은 락을 잡는 순서 하나뿐입니다. 락을 더 넣지도, 기다리는 시간을 늘리지도 않습니다.
단계
1. /root/conc/race.py 로 갱신이 사라지는 것을 재현하고 /root/conc/01-race.txt 에 남깁니다.
2. 같은 프로그램을 다섯 번 돌려 /root/conc/02-repeat.txt 에 회차별 결과를 남깁니다.
3. /root/conc/lock.py 로 락을 걸어 손실을 없애고 /root/conc/03-lock.txt 에 남깁니다.
4. /root/conc/local.py 로 아예 공유하지 않는 방법을 만들고 /root/conc/04-local.txt 에 남깁니다.
5. /root/conc/deadlock.py reverse 로 교착을 만들고 /root/conc/05-deadlock.txt 에 남깁니다.
6. 같은 프로그램에 same 갈래를 더해 교착을 없애고 /root/conc/06-order.txt 에 남깁니다.
7. 3단계와 4단계의 걸린 시간을 모아 /root/conc/07-cost.txt 에 남깁니다.
8. 배운 것을 정리해 /root/conc/08-notes.md 에 남깁니다.
참고
- 산출물은 모두
/root/conc/아래에 둡니다.mkdir -p /root/conc를 먼저 해 두세요. - 스레드 수는 4, 스레드 하나가 올리는 횟수는 50,000 으로 맞춥니다. 채점기가 이 값을 기준으로 봅니다.
- 손실이 안 보이면
sys.setswitchinterval(0.000001)을 넣으세요. 스레드가 더 자주 바뀌어 원래 있던 틈이 드러납니다. - 흔한 실수 1:
counter += 1한 줄로 쓰는 것입니다. 틈이 좁아 손실이 잘 안 보입니다. 읽기와 쓰기를 두 문장으로 나누세요. - 흔한 실수 2: 두 번째 락을 시간 제한 없이 잡는 것입니다. 프로그램이 영원히 멈추고 화면에는 아무것도 나오지 않습니다.
acquire(timeout=2)를 쓰세요. - 흔한 실수 3: 6단계에서 락을 하나로 합쳐 버리는 것입니다. 그러면 교착은 사라지지만 배울 것도 사라집니다. 락은 둘 그대로 두고 순서만 바꿉니다.
단계 8개
- 갱신이 사라지는 것을 재현한다
- 같은 프로그램을 다섯 번 돌린다
- 락을 걸어 손실을 없앤다
- 아예 공유하지 않는 방법
- 락을 반대 순서로 잡아 교착을 만든다
- 순서를 통일해 교착을 없앤다
- 락이 얼마를 받아 가는지 잰다
- 무엇이 무엇을 막았는지 정리한다