├── LICENSE ├── README.md ├── appendix_A ├── Makefile ├── README.md └── fizzbuzz.S ├── chap2 ├── 2.2 │ ├── 2_2_1_1_pthreads_1.c │ ├── 2_2_1_2_pthreads_2.c │ ├── 2_2_2_volatile.c │ ├── 2_2_2_wo_volatile.c │ ├── Makefile │ └── README.md └── README.md ├── chap3 ├── 3.2 │ ├── 3_2_1_cas_1.c │ ├── 3_2_1_cas_2.c │ ├── 3_2_2_tas.c │ ├── Makefile │ └── README.md ├── 3.3 │ ├── 3_3_1_spinlock_1.c │ ├── 3_3_1_spinlock_2.c │ ├── 3_3_1_use_spinlock.c │ ├── 3_3_2_pthreads_mutex.c │ ├── 3_3_bad_mutex.c │ ├── 3_3_good_mutex.c │ ├── Makefile │ └── README.md ├── 3.4 │ ├── 3_4_1_semaphore_llsc.S │ ├── 3_4_2_posix_semaphore.c │ ├── 3_4_semaphore.c │ ├── 3_4_semaphore_llsc.c │ ├── Makefile │ ├── README.md │ └── semtest.c ├── 3.5 │ ├── 3_5_cond.c │ ├── Makefile │ └── README.md ├── 3.6 │ ├── 3_6_1_barrier_spin.c │ ├── 3_6_2_barrier_pthreads.c │ ├── Makefile │ └── README.md ├── 3.7 │ ├── 3_7_1_rwlock_spin.c │ ├── 3_7_2_rwlock_pthreads.c │ ├── 3_7_3_performance.c │ ├── Makefile │ ├── README.md │ ├── barrier.c │ ├── empty.c │ ├── mutex.c │ ├── rwlock.c │ └── rwlock_wr.c ├── 3.8 │ ├── README.md │ ├── ch3_8_1_mutex │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch3_8_2_cond │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch3_8_3_rwlock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch3_8_4_barrier │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch3_8_5_channel │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── channel.rs │ │ │ ├── main.rs │ │ │ └── semaphore.rs │ └── ch3_8_5_semaphore │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ └── semaphore.rs ├── 3.9 │ ├── README.md │ └── ch3_9_bakery │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── README.md ├── chap4 ├── 4.1 │ ├── README.md │ ├── ch4_1_philosophers │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch4_1_rwlock_1_1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch4_1_rwlock_1_2 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch4_1_rwlock_2_1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── ch4_1_rwlock_2_2 │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── 4.3 │ ├── README.md │ └── ch4_3_banker │ │ ├── Cargo.toml │ │ └── src │ │ ├── banker.rs │ │ └── main.rs ├── 4.4 │ ├── README.md │ ├── ch4_4_reent_c │ │ ├── Makefile │ │ └── reent.c │ └── ch4_4_reent_rust │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── 4.5 │ ├── Makefile │ ├── README.md │ └── spurious.c ├── 4.6 │ ├── README.md │ ├── ch4_6_signal_c │ │ ├── Makefile │ │ └── signal_c.c │ └── ch4_6_signal_rust │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── 4.7 │ ├── README.md │ └── ch4_7_barrier │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── README.md ├── chap5 ├── 5.1 │ ├── README.md │ ├── ch5_1_conc │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── ch5_1_iter │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── 5.2 │ ├── README.md │ ├── ch5_2_1_hello │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch5_2_2_sched │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── hello.py ├── 5.3 │ ├── README.md │ └── ch5_3_2_ioselect │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── 5.4 │ ├── README.md │ ├── ch5_4_block │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch5_4_mutex_1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch5_4_mutex_2 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch5_4_oneshot │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch5_4_sleep │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── ch5_4_tokio │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── README.md ├── chap6 ├── README.md └── ch6_mult │ ├── Cargo.toml │ ├── asm │ └── context.S │ ├── build.rs │ └── src │ ├── green.rs │ └── main.rs ├── chap7 ├── 7.1 │ ├── README.md │ ├── ch7_1_1_fairlock │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── fairlock.rs │ │ │ └── main.rs │ ├── ch7_1_2_ticketlock │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── main.rs │ │ │ └── ticketlock.rs │ └── ch7_1_3_mcslock │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ └── mcs.rs ├── 7.2 │ ├── README.md │ └── ch7_2_stm │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ └── tl2.rs ├── 7.3 │ ├── README.md │ └── ch7_3_lockfree │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ ├── stack.rs │ │ └── stack_bad.rs └── README.md ├── chap8 ├── 8.4 │ ├── README.md │ ├── ch8_4_7_barrier │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── ch8_4_8_session_1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── ch8_4_8_session_2 │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── README.md ├── errata.md ├── figs ├── dmb_st_new.png ├── dmb_st_old.png ├── llsc_new.png └── llsc_old.png └── references ├── 머신러닝 실무 프로젝트(2판)_URL 모음.pdf └── 머신러닝 실무 프로젝트(2판)_URL 모음.xlsx /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yuuki Takano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 동시성 프로그래밍 입문 2 | 3 | 이 저장소에서는 [동시성 프로그래밍 입문]()의 샘플 코드를 제공합니다. 4 | 5 | ## 샘플 코드 6 | 7 | - [2장 프로그래밍 기초](./chap2/) 8 | - [3장 동기처리 1](./chap3/) 9 | - [4장 동시성 프로그래밍 특유의 버그와 문제점](./chap4/) 10 | - [5장 비동기 프로그래밍](./chap5/) 11 | - [6장 멀티태스크](./chap6/) 12 | - [7장 동기처리 2](./chap7/) 13 | - [8장 동시 계산 모델](./chap8/) 14 | - [부록 A AArch64 아키텍처](./appendix_A/) 15 | - [URL 모음](./references/) 16 | 17 | ## 주의 사항 18 | 19 | 각 폴더마다 `README.md` 파일이 있습니다. 컴파일 등을 할 때는 해당 파일을 참조하기 바랍니다. 20 | 21 | 일부 Linux에만 대응 또는 AArch64에만 대응하는 코드도 있으므로, 이에 관해서도 `README.md`의 내용을 확인하기 바랍니다. 22 | 23 | ## 정오표 24 | 25 | [정오표](./errata.md)를 업로드하였으므로 확인하기 바랍니다. 26 | 27 | ## 라이선스 28 | 29 | 소스 코드는 [MIT 라이선스](LICENSE)를 따릅니다. 30 | 31 | 개선, 재배포, 상용으로서의 이용도 무료로 가능합니다. 32 | 33 | 재배포를 하거나 상용으로 이용할 때는 저작권 표시와 MIT 라이선스의 전문을 기대하기 바랍니다. 34 | 35 | 단, 이 소스 코드 이용으로 인해 발생하는 여하의 문제에 대해서는 한빛미디어 및 저역자는 일체의 책임을 지지 않습니다. 36 | -------------------------------------------------------------------------------- /appendix_A/Makefile: -------------------------------------------------------------------------------- 1 | all: fizzbuzz 2 | 3 | fizzbuzz: fizzbuzz.o 4 | ld fizzbuzz.o -o fizzbuzz 5 | 6 | fizzbuzz.o: fizzbuzz.S 7 | as fizzbuzz.S -o fizzbuzz.o 8 | 9 | clean: 10 | rm -f *.o fizzbuzz 11 | -------------------------------------------------------------------------------- /appendix_A/README.md: -------------------------------------------------------------------------------- 1 | # Appendix A AArch64 아키텍처 2 | 3 | ## 컴파일 4 | 5 | `make`를 실행하면 컴파일됩니다.AArc64 환경에서만 실행 할 수 있습니다. 6 | 7 | ```sh 8 | $ make 9 | $ ls fizzbuzz 10 | fizzbuzz* 11 | ``` 12 | -------------------------------------------------------------------------------- /appendix_A/fizzbuzz.S: -------------------------------------------------------------------------------- 1 | .text 2 | .global _start // ❶ 3 | 4 | .align 4 // ❷ 5 | _start: // ❸ 6 | mov x19, #1 // ❹ 7 | .Lloop: 8 | cmp x19, #100 9 | b.hi .Lend0 10 | mov x0, x19 11 | bl FizzBuzz // ❺ 12 | add x19, x19, 1 13 | b .Lloop 14 | .Lend0: 15 | mov x0, xzr // ❻ 16 | mov x8, #93 // exit 17 | svc #0 18 | 19 | FizzBuzz: // ❼ 20 | stp x19, x20, [sp, #-16]! 21 | 22 | // if x0 % 15 == 0 then go to .LFB ❽ 23 | mov x20, #15 24 | udiv x19, x0, x20 25 | msub x19, x19, x20, x0 26 | cmp x19, xzr // x0 % 15 == 0 27 | b.eq .LFB 28 | 29 | // if x0 % 3 == 0 then go to .LF 30 | mov x20, #3 31 | udiv x19, x0, x20 32 | msub x19, x19, x20, x0 33 | cmp x19, xzr // x0 % 3 == 0 34 | b.eq .LF 35 | 36 | // if x0 % 5 != 0 then go to .Lend1 37 | mov x20, #5 38 | udiv x19, x0, x20 39 | msub x19, x19, x20, x0 40 | cmp x19, xzr // x0 % 5 == 0 41 | b.ne .Lend1 42 | adr x1, buzzStr // ❾ 43 | mov x2, #5 // legth of "Buzz\n" 44 | b .LWrite 45 | .LF: 46 | adr x1, fizzStr 47 | mov x2, #5 // legth of "Fizz\n" 48 | b .LWrite 49 | .LFB: 50 | adr x1, fizzBuzzStr 51 | mov x2, #9 // legth of "FizzBuzz\n" 52 | .LWrite: 53 | mov x0, xzr 54 | mov x8, #64 // write 55 | svc #0 56 | .Lend1: 57 | ldp x19, x20, [sp], #-16 // ❿ 58 | ret 59 | 60 | .data // ⓫ 61 | fizzBuzzStr: 62 | .string "FizzBuzz\n" 63 | 64 | fizzStr: 65 | .string "Fizz\n" 66 | 67 | buzzStr: 68 | .string "Buzz\n" 69 | -------------------------------------------------------------------------------- /chap2/2.2/2_2_1_1_pthreads_1.c: -------------------------------------------------------------------------------- 1 | #include // ❶ 2 | #include 3 | #include 4 | #include 5 | 6 | #define NUM_THREADS 10 // 생성할 스레드 수 7 | 8 | // 스레드용 함수 9 | void *thread_func(void *arg) { // ❷ 10 | int id = (int)arg; // ❸ 11 | for (int i = 0; i < 5; i++) { // ❹ 12 | printf("id = %d, i = %d\n", id, i); 13 | sleep(1); 14 | } 15 | 16 | return "finished!"; // 반환값 17 | } 18 | 19 | int main(int argc, char *argv[]) { 20 | pthread_t v[NUM_THREADS]; // ⑤ 21 | // 스레드 생성 ⑥ 22 | for (int i = 0; i < NUM_THREADS; i++) { 23 | if (pthread_create(&v[i], NULL, thread_func, (void *)i) != 0) { 24 | perror("pthread_create"); 25 | return -1; 26 | } 27 | } 28 | 29 | // 스레드 종료 대기 ⑦ 30 | for (int i = 0; i < NUM_THREADS; i++) { 31 | char *ptr; 32 | if (pthread_join(v[i], (void **)&ptr) == 0) { 33 | printf("msg = %s\n", ptr); 34 | } else { 35 | perror("pthread_join"); 36 | return -1; 37 | } 38 | } 39 | 40 | return 0; 41 | } -------------------------------------------------------------------------------- /chap2/2.2/2_2_1_2_pthreads_2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // 스레드용 함수 7 | void *thread_func(void *arg) { 8 | for (int i = 0; i < 5; i++) { 9 | printf("i = %d\n", i); 10 | sleep(1); 11 | } 12 | return NULL; 13 | } 14 | 15 | int main(int argc, char *argv[]) { 16 | // 어트리뷰트 초기화 ❶ 17 | pthread_attr_t attr; 18 | if (pthread_attr_init(&attr) != 0) { 19 | perror("pthread_attr_init"); 20 | return -1; 21 | } 22 | 23 | // 디태치 스레드로 설정 ❷ 24 | if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) { 25 | perror("pthread_attr_setdetachstate"); 26 | return -1; 27 | } 28 | 29 | // 어트리뷰터를 지정해 스레드 생성 30 | pthread_t th; 31 | if (pthread_create(&th, &attr, thread_func, NULL) != 0) { 32 | perror("pthread_join"); 33 | return -1; 34 | } 35 | 36 | // 어트리뷰트 파기 37 | if (pthread_attr_destroy(&attr) != 0) { 38 | perror("pthread_attr_destroy"); 39 | return -1; 40 | } 41 | 42 | sleep(7); 43 | 44 | return 0; 45 | } -------------------------------------------------------------------------------- /chap2/2.2/2_2_2_volatile.c: -------------------------------------------------------------------------------- 1 | void wait_while_0(volatile int *p) { 2 | while (*p == 0) {} 3 | } -------------------------------------------------------------------------------- /chap2/2.2/2_2_2_wo_volatile.c: -------------------------------------------------------------------------------- 1 | void wait_while_0(int *p) { 2 | while (*p == 0) {} 3 | } -------------------------------------------------------------------------------- /chap2/2.2/Makefile: -------------------------------------------------------------------------------- 1 | targets = 2_2_1_1_pthreads_1 2_2_1_2_pthreads_2 2_2_2_wo_volatile.s 2_2_2_volatile.s 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | %.s: %.c 8 | $(CC) -O3 -S $< 9 | 10 | .PHONY: clean 11 | clean: 12 | rm -f $(targets) 13 | -------------------------------------------------------------------------------- /chap2/2.2/README.md: -------------------------------------------------------------------------------- 1 | # 2.2 C 언어 2 | 3 | ## 파일 4 | 5 | - `2_2_1_1_pthreads_1.c`: p20 6 | - `2_2_1_2_pthreads_2.c`: p22 7 | - `2_2_2_wo_volatile.c`: p24 위 8 | - `2_2_2_volatile.c`: p24 아래 9 | 10 | ## 컴파일 또는 어셈블리 파일 생성 11 | 12 | `make`를 실행하면 컴파일, 또는 어셈블리 파일을 생성합니다. 13 | 14 | 15 | ```sh 16 | $ make 17 | $ ls *.s 2_2_1_1_pthreads_1 2_2_1_2_pthreads_2 18 | 2_2_1_1_pthreads_1* 2_2_1_2_pthreads_2* 2_2_2_volatile.s 2_2_2_wo_volatile.s 19 | ``` 20 | -------------------------------------------------------------------------------- /chap2/README.md: -------------------------------------------------------------------------------- 1 | # 2장. 프로그래밍 기본 2 | 3 | - [2.2 C 언어](./2.2/) 4 | -------------------------------------------------------------------------------- /chap3/3.2/3_2_1_cas_1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool compare_and_swap(uint64_t *p, uint64_t val, uint64_t newval) 5 | { 6 | if (*p != val) { // ❶ 7 | return false; 8 | } 9 | *p = newval; // ❷ 10 | return true; 11 | } -------------------------------------------------------------------------------- /chap3/3.2/3_2_1_cas_2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool compare_and_swap(uint64_t *p, uint64_t val, uint64_t newval) 5 | { 6 | return __sync_bool_compare_and_swap(p, val, newval); 7 | } -------------------------------------------------------------------------------- /chap3/3.2/3_2_2_tas.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool test_and_set(volatile bool *p) { 4 | return __sync_lock_test_and_set(p, 1); 5 | } 6 | 7 | void tas_release(volatile bool *p) { 8 | return __sync_lock_release(p); 9 | } -------------------------------------------------------------------------------- /chap3/3.2/Makefile: -------------------------------------------------------------------------------- 1 | cas = 3_2_1_cas_1.s 3_2_1_cas_2.s 3_2_2_tas.s 2 | 3 | all: $(cas) 4 | 5 | %.s: %.c 6 | $(CC) -O3 -S $< 7 | 8 | .PHONY: clean 9 | clean: 10 | rm -f *.s 11 | -------------------------------------------------------------------------------- /chap3/3.2/README.md: -------------------------------------------------------------------------------- 1 | # 3.2 아토믹 명령 2 | 3 | ## 파일 4 | 5 | - `3_2_1_cas_1.c`: p57 6 | - `3_2_1_cas_2.c`: p58 7 | - `3_2_2_tas.c`: p61 8 | 9 | ## 어셈블리 파일 생성 10 | 11 | `make`를 실행하면 `.s` 파일이 생성됩니다. 12 | 13 | ```sh 14 | $ make 15 | $ ls *.s 16 | 3_2_1_cas_1.s 3_2_1_cas_2.s 3_2_2_tas.s 17 | ``` 18 | -------------------------------------------------------------------------------- /chap3/3.3/3_3_1_spinlock_1.c: -------------------------------------------------------------------------------- 1 | #include "../3.2/3_2_2_tas.c" 2 | 3 | void spinlock_acquire(bool *lock) { 4 | while (test_and_set(lock)); // ❶ 5 | } 6 | 7 | void spinlock_release(bool *lock) { 8 | tas_release(lock); // ❷ 9 | } -------------------------------------------------------------------------------- /chap3/3.3/3_3_1_spinlock_2.c: -------------------------------------------------------------------------------- 1 | #include "../3.2/3_2_2_tas.c" 2 | 3 | void spinlock_acquire(volatile bool *lock) { // ❶ 4 | for (;;) { 5 | while(*lock); // ❷ 6 | if (!test_and_set(lock)) 7 | break; 8 | } 9 | } 10 | 11 | void spinlock_release(bool *lock) { 12 | tas_release(lock); 13 | } -------------------------------------------------------------------------------- /chap3/3.3/3_3_1_use_spinlock.c: -------------------------------------------------------------------------------- 1 | #include "3_3_1_spinlock_2.c" 2 | 3 | bool lock = false; // 공유 변수 4 | 5 | void some_func() { 6 | for (;;) { 7 | spinlock_acquire(&lock); // 록 획득 ❶ 8 | // 크리티컬 섹션 ❷ 9 | spinlock_release(&lock); // 록 반환 ❸ 10 | } 11 | } -------------------------------------------------------------------------------- /chap3/3.3/3_3_2_pthreads_mutex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; // ❶ 6 | 7 | void* some_func(void *arg) { // 스레드용 함수 8 | if (pthread_mutex_lock(&mut) != 0) { // ❷ 9 | perror("pthread_mutex_lock"); exit(-1); 10 | } 11 | 12 | // 크리티컬 섹션 13 | 14 | if (pthread_mutex_unlock(&mut) != 0) { // ❸ 15 | perror("pthread_mutex_unlock"); exit(-1); 16 | } 17 | 18 | return NULL; 19 | } 20 | 21 | int main(int argc, char *argv[]) { 22 | // 스레드 생성 23 | pthread_t th1, th2; 24 | if (pthread_create(&th1, NULL, some_func, NULL) != 0) { 25 | perror("pthread_create"); return -1; 26 | } 27 | 28 | if (pthread_create(&th2, NULL, some_func, NULL) != 0) { 29 | perror("pthread_create"); return -1; 30 | } 31 | 32 | // 스레드 종료 대기 33 | if (pthread_join(th1, NULL) != 0) { 34 | perror("pthread_join"); return -1; 35 | } 36 | 37 | if (pthread_join(th2, NULL) != 0) { 38 | perror("pthread_join"); return -1; 39 | } 40 | 41 | // 뮤텍스 객체 반환 42 | if (pthread_mutex_destroy(&mut) != 0) { // ❹ 43 | perror("pthread_mutex_destroy"); return -1; 44 | } 45 | 46 | return 0; 47 | } -------------------------------------------------------------------------------- /chap3/3.3/3_3_bad_mutex.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool lock = false; // 공유 변수 ❶ 4 | 5 | void some_func() { 6 | retry: 7 | if (!lock) { // ❷ 8 | lock = true; // 록 획득 9 | // 크리티컬 섹션 10 | } else { 11 | goto retry; 12 | } 13 | lock = false; // 록 반환 ❸ 14 | } -------------------------------------------------------------------------------- /chap3/3.3/3_3_good_mutex.c: -------------------------------------------------------------------------------- 1 | #include "../3.2/3_2_2_tas.c" 2 | 3 | bool lock = false; // 공유 변수 4 | 5 | void some_func() { 6 | retry: 7 | if (!test_and_set(&lock)) { // 검사 및 록 획득 8 | // 크리티컬 섹션 9 | } else { 10 | goto retry; 11 | } 12 | tas_release(&lock); // 록 반환 13 | } -------------------------------------------------------------------------------- /chap3/3.3/Makefile: -------------------------------------------------------------------------------- 1 | targets = 3_3_bad_mutex.o 3_3_good_mutex.o 3_3_1_spinlock_1.o 3_3_1_spinlock_2.o 3_3_1_use_spinlock.o 3_3_2_pthreads_mutex 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f $(targets) 10 | -------------------------------------------------------------------------------- /chap3/3.3/README.md: -------------------------------------------------------------------------------- 1 | # 3.3 뮤텍스 2 | 3 | ## 파일 4 | 5 | - `3_3_bad_mutex.c`: p## 6 | - `3_3_good_mutex.c`: p## 7 | - `3_3_1_spinlock_1.c`: p## 8 | - `3_3_1_spinlock_2.c`: p## 9 | - `3_3_1_use_spinlock.c`: p## 10 | - `3_3_2_pthreads_mutex.c`: p## 11 | 12 | ## 컴파일 13 | 14 | `make`를 실행하면 `.o` 파일, 및 실행 파일이 생성됩니다. 15 | 16 | ```sh 17 | $ make 18 | $ ls *.o 19 | 3_3_1_spinlock_1.o 3_3_1_spinlock_2.o 3_3_1_use_spinlock.o 3_3_bad_mutex.o 3_3_good_mutex.o 20 | $ ls 3_3_2_pthreads_mutex 21 | 3_3_2_pthreads_mutex* 22 | ``` 23 | -------------------------------------------------------------------------------- /chap3/3.4/3_4_1_semaphore_llsc.S: -------------------------------------------------------------------------------- 1 | // AArch64인 Mac에서도 동작하도록 일부를 수정했습니다 2 | 3 | .text 4 | #ifdef __APPLE__ 5 | .global _semaphore_acquire_llsc 6 | #else 7 | .global semaphore_acquire_llsc 8 | #endif 9 | 10 | .align 4 11 | 12 | #ifdef __APPLE__ 13 | _semaphore_acquire_llsc: 14 | #else 15 | semaphore_acquire_llsc: 16 | #endif 17 | 18 | .LBB0_1: 19 | ldr w8, [x0] // while (*x0 > 3); ❶ 20 | cmp w8, #3 21 | b.hi .LBB0_1 22 | .Ltmp1: 23 | ldaxr w2, [x0] // w2 = [x0] ❷ 24 | cmp w2, #4 25 | b.lo .Ltmp2 // if (w2 < 4) then goto .Ltmp2 ❸ 26 | clrex // clear exclusive 27 | b .LBB0_1 // goto .LBB0_1 28 | .Ltmp2: 29 | add w2, w2, #1 // w2 = w2 + 1 ❹ 30 | stlxr w3, w2, [x0] // [x0] = w2 31 | cbnz w3, .Ltmp1 // if (w3 != 0) then goto .Ltmp1 32 | ret -------------------------------------------------------------------------------- /chap3/3.4/3_4_2_posix_semaphore.c: -------------------------------------------------------------------------------- 1 | #include // ❶ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define NUM_THREADS 10 // 스레드 수 10 | #define NUM_LOOP 10 // 스레드 안의 루프 수 11 | 12 | int count = 0; // ❷ 13 | 14 | void *th(void *arg) { // 스레드용 함수 15 | // 이름이 있는 세마포를 연다 ❸ 16 | sem_t *s = sem_open("/mysemaphore", 0); 17 | if (s == SEM_FAILED) { 18 | perror("sem_open"); 19 | exit(1); 20 | } 21 | 22 | for (int i = 0; i < NUM_LOOP; i++) { 23 | // 대기 ❹ 24 | if (sem_wait(s) == -1) { 25 | perror("sem_wait"); 26 | exit(1); 27 | } 28 | 29 | // 카운터를 아토믹하게 인크리먼트 30 | __sync_fetch_and_add(&count, 1); 31 | printf("count = %d\n", count); 32 | 33 | // 10ms 슬립 34 | usleep(10000); 35 | 36 | // 카운터를 아토믹하기 디크리먼트 37 | __sync_fetch_and_sub(&count, 1); 38 | 39 | // 세마포 값을 증가시키고 ❺ 40 | // 크리티컬 섹션에서 벗어난다 41 | if (sem_post(s) == -1) { 42 | perror("sem_post"); 43 | exit(1); 44 | } 45 | } 46 | 47 | // 세마포를 닫는다 ❻ 48 | if (sem_close(s) == -1) 49 | perror("sem_close"); 50 | 51 | return NULL; 52 | } 53 | 54 | int main(int argc, char *argv[]) { 55 | // 이름이 붙은 세마보를 연다. 세마포가 없을 때는 생성한다. 56 | // 자신과 그룹을 이용할 수 있는 세미포로, 57 | // 크리티컬 섹션에 들어갈 수 있는 프로세스는 최대 3개이다. ❼ 58 | sem_t *s = sem_open("/mysemaphore", O_CREAT, 0660, 3); 59 | if (s == SEM_FAILED) { 60 | perror("sem_open"); 61 | return 1; 62 | } 63 | 64 | // 스레드 생성 65 | pthread_t v[NUM_THREADS]; 66 | for (int i = 0; i < NUM_THREADS; i++) { 67 | pthread_create(&v[i], NULL, th, NULL); 68 | } 69 | 70 | // join 71 | for (int i = 0; i < NUM_THREADS; i++) { 72 | pthread_join(v[i], NULL); 73 | } 74 | 75 | // 세마포를 닫는다 76 | if (sem_close(s) == -1) 77 | perror("sem_close"); 78 | 79 | // 세마포 파기 ❽ 80 | if (sem_unlink("/mysemaphore") == -1) 81 | perror("sem_unlink"); 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /chap3/3.4/3_4_semaphore.c: -------------------------------------------------------------------------------- 1 | #define NUM 4 2 | 3 | void semaphore_acquire(volatile int *cnt) { // ❶ 4 | for (;;) { 5 | while (*cnt >= NUM); // ❷ 6 | __sync_fetch_and_add(cnt, 1); // ❸ 7 | if (*cnt <= NUM) // ❹ 8 | break; 9 | __sync_fetch_and_sub(cnt, 1); // ❺ 10 | } 11 | } 12 | 13 | void semaphore_release(int *cnt) { 14 | __sync_fetch_and_sub(cnt, 1); // ❻ 15 | } 16 | 17 | #include "semtest.c" -------------------------------------------------------------------------------- /chap3/3.4/3_4_semaphore_llsc.c: -------------------------------------------------------------------------------- 1 | // LL/SC를 이용한 세마포의 테스트 코드입니다. 2 | // 이 코드는 책에는 실려있지 않습니다. 3 | 4 | #define NUM 4 5 | 6 | void semaphore_acquire_llsc(volatile int *cnt); 7 | 8 | void semaphore_acquire(int *cnt) { 9 | semaphore_acquire_llsc(cnt); 10 | } 11 | 12 | void semaphore_release(int *cnt) { 13 | __sync_fetch_and_sub(cnt, 1); 14 | } 15 | 16 | #include "semtest.c" -------------------------------------------------------------------------------- /chap3/3.4/Makefile: -------------------------------------------------------------------------------- 1 | targets = 3_4_semaphore 3_4_2_posix_semaphore 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | PLATFORM = $(shell uname -m) 6 | 7 | ifeq ($(PLATFORM),arm64) 8 | targets += 3_4_semaphore_llsc 3_4_1_semaphore_llsc.o 9 | else 10 | ifeq ($(PLATFORM),aarch64) 11 | targets += 3_4_semaphore_llsc 3_4_1_semaphore_llsc.o 12 | endif 13 | endif 14 | 15 | all: $(targets) 16 | 17 | 3_4_semaphore_llsc: 3_4_semaphore_llsc.c 3_4_1_semaphore_llsc.o 18 | $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ 19 | 20 | plat: 21 | echo $(PLATFORM) 22 | 23 | .PHONY: clean 24 | clean: 25 | rm -f $(targets) 26 | -------------------------------------------------------------------------------- /chap3/3.4/README.md: -------------------------------------------------------------------------------- 1 | # 3.4 세마포 2 | 3 | ## 파일 4 | 5 | - `3_4_semaphore.c`: p## + 세마포 테스트 코드(책에는 실려있지 않음) 6 | - `3_4_semaphore_llsc.c`: LL/SC 버전 세마포의 테스트 코드(책에는 실려있지 않음) 7 | - `3_4_1_semaphore_llsc.S`: p## 8 | - `3_4_2_posix_semaphore.c`: p## 9 | 10 | ## 컴파일 11 | 12 | `make`를 실행하면 `.o` 파일, 및 실행 파일이 생성됩니다. LL/SC 버전은 AArch64 환경에서만 컴파일하기 바랍니다. 13 | 14 | ```sh 15 | $ make 16 | $ ls *.o 17 | 3_4_1_semaphore_llsc.o 3_4_semaphore.o 18 | $ ls 3_4_semaphore 3_4_semaphore_llsc 3_4_2_posix_semaphore 19 | 3_4_2_posix_semaphore* 3_4_semaphore* 3_4_semaphore_llsc* 20 | ``` 21 | 22 | 각 실행 파일을 실행하면 테스트가 수행됩니다. 23 | -------------------------------------------------------------------------------- /chap3/3.4/semtest.c: -------------------------------------------------------------------------------- 1 | // 테스트 코드 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define NUM_THREADS 10 // 스레드 수 8 | #define NUM_LOOP 10000 // 스레드 안의 루프 수 9 | 10 | int cnt = 0; // 공유 변수 11 | 12 | void *th(void *arg) { 13 | for (int i = 0; i < NUM_LOOP; i++) { 14 | semaphore_acquire(&cnt); 15 | if (cnt > 4) { 16 | printf("cnt = %d\n", cnt); 17 | exit(1); 18 | } 19 | semaphore_release(&cnt); 20 | } 21 | 22 | return NULL; 23 | } 24 | 25 | int main(int argc, char *argv[]) { 26 | // 스레드 생성 27 | pthread_t v[NUM_THREADS]; 28 | for (int i = 0; i < NUM_THREADS; i++) { 29 | pthread_create(&v[i], NULL, th, NULL); 30 | } 31 | 32 | printf("OK!\n"); 33 | 34 | return 0; 35 | } -------------------------------------------------------------------------------- /chap3/3.5/3_5_cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; // ❶ 7 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // ❷ 8 | 9 | volatile bool ready = false; // ❸ 10 | char buf[256]; // 스레드 사이에서 데이터를 주고 받기 위한 버퍼 11 | 12 | void* producer(void *arg) { // 데이터 생성 스레드 ❹ 13 | printf("producer: "); 14 | fgets(buf, sizeof(buf), stdin); // 입력을 받는다 15 | 16 | pthread_mutex_lock(&mut); 17 | ready = true; // ❺ 18 | 19 | if (pthread_cond_broadcast(&cond) !=0) { //전체에 알림 ❻ 20 | perror("pthread_cond_broadcast"); exit(-1); 21 | } 22 | 23 | pthread_mutex_unlock(&mut); 24 | return NULL; 25 | } 26 | 27 | void* consumer(void *arg) { // 데이터 소비 스레드 ❼ 28 | pthread_mutex_lock(&mut); 29 | 30 | while (!ready) { // ready 변수값이 false인 경우 대기 31 | // 록 반환과 대기를 동시에 실행 32 | if (pthread_cond_wait(&cond, &mut) != 0) { // ❽ 33 | perror("pthread_cond_wait"); exit(-1); 34 | } 35 | } 36 | 37 | pthread_mutex_unlock(&mut); 38 | printf("consumer: %s\n", buf); 39 | return NULL; 40 | } 41 | 42 | int main(int argc, char *argv[]) { 43 | // 스레드 생성 44 | pthread_t pr, cn; 45 | pthread_create(&pr, NULL, producer, NULL); 46 | pthread_create(&cn, NULL, consumer, NULL); 47 | 48 | // 스레드 종료 대기 49 | pthread_join(pr, NULL); 50 | pthread_join(cn, NULL); 51 | 52 | // 뮤텍스 객체 반환 53 | pthread_mutex_destroy(&mut); 54 | 55 | // 조건 변수 객체 반환 ❾ 56 | if (pthread_cond_destroy(&cond) != 0) { 57 | perror("pthread_cond_destroy"); return -1; 58 | } 59 | 60 | return 0; 61 | } -------------------------------------------------------------------------------- /chap3/3.5/Makefile: -------------------------------------------------------------------------------- 1 | targets = 3_5_cond 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f $(targets) 10 | -------------------------------------------------------------------------------- /chap3/3.5/README.md: -------------------------------------------------------------------------------- 1 | # 3.5 조건 변수 2 | 3 | ## 파일 4 | 5 | - `3_5_cond.c`: p75 6 | 7 | ## 컴파일 8 | 9 | `make`를 실행하면 실행 파일이 생성됩니다. 10 | 11 | ```sh 12 | $ make 13 | $ ls 3_5_cond 14 | 3_5_cond* 15 | ``` 16 | -------------------------------------------------------------------------------- /chap3/3.6/3_6_1_barrier_spin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void barrier(volatile int *cnt, int max) { // ❶ 5 | __sync_fetch_and_add(cnt, 1); // ❷ 6 | while (*cnt < max); // ❸ 7 | } 8 | 9 | volatile int num = 0; // 공유 변수 10 | 11 | void *worker(void *arg) { // 스레드용 함수 12 | barrier(&num, 10); // 모든 스레드가 여기에 도달할 때까지 기다린다 ❶ 13 | // 무언가의 처리 14 | 15 | return NULL; 16 | } 17 | 18 | int main(int argc, char *argv[]) { 19 | // 스레드 생성 20 | pthread_t th[10]; 21 | for (int i = 0; i < 10; i++) { 22 | if (pthread_create(&th[i], NULL, worker, NULL) != 0) { 23 | perror("pthread_create"); return -1; 24 | } 25 | } 26 | // join은 생략 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /chap3/3.6/3_6_2_barrier_pthreads.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | pthread_mutex_t barrier_mut = PTHREAD_MUTEX_INITIALIZER; 6 | pthread_cond_t barrier_cond = PTHREAD_COND_INITIALIZER; 7 | 8 | void barrier(volatile int *cnt, int max) { 9 | if (pthread_mutex_lock(&barrier_mut) != 0) { 10 | perror("pthread_mutex_lock"); exit(-1); 11 | } 12 | 13 | (*cnt)++; // ❶ 14 | 15 | if (*cnt == max) { // ❷ 16 | // 모든 프로세스가 모였으므로 알림 ❸ 17 | if (pthread_cond_broadcast(&barrier_cond) != 0) { 18 | perror("pthread_cond_broadcast"); exit(-1); 19 | } 20 | } else { 21 | do { // 모든 프로세스가 모일 때까지 대기 ❹ 22 | if (pthread_cond_wait(&barrier_cond, 23 | &barrier_mut) != 0) { 24 | perror("pthread_cond_wait"); exit(-1); 25 | } 26 | } while (*cnt < max); // 의사 각성을 위한 조건 27 | } 28 | 29 | if (pthread_mutex_unlock(&barrier_mut) != 0) { 30 | perror("pthread_mutex_unlock"); exit(-1); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chap3/3.6/Makefile: -------------------------------------------------------------------------------- 1 | targets = 3_6_1_barrier_spin 3_6_2_barrier_pthreads.o 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f $(targets) 10 | -------------------------------------------------------------------------------- /chap3/3.6/README.md: -------------------------------------------------------------------------------- 1 | # 3.6 배리어 동기 2 | 3 | ## 파일 4 | 5 | - `3_6_1_barrier_spin.c`: pxxx 6 | - `3_6_2_barrier_pthreads.c`: pxxx 7 | 8 | ## 컴파일 9 | 10 | `make`를 실행하면 `.o` 파일, 또는 실행 파일이 생성됩니다. 11 | 12 | ```sh 13 | $ make 14 | $ ls 3_6_1_barrier_spin 3_6_2_barrier_pthreads.o 15 | 3_6_1_barrier_spin* 3_6_2_barrier_pthreads.o 16 | ``` 17 | -------------------------------------------------------------------------------- /chap3/3.7/3_7_1_rwlock_spin.c: -------------------------------------------------------------------------------- 1 | #include "../3.3/3_3_1_spinlock_2.c" 2 | 3 | // Reader용 록 획득 함수 ❶ 4 | void rwlock_read_acquire(int *rcnt, volatile int *wcnt) { 5 | for (;;) { 6 | while (*wcnt); // Writer가 있으면 대기 ❷ 7 | __sync_fetch_and_add(rcnt, 1); // ❸ 8 | if (*wcnt == 0) // Writer가 없으면 록 획득 ❹ 9 | break; 10 | __sync_fetch_and_sub(rcnt, 1); 11 | } 12 | } 13 | 14 | // Reader용 록 반환 함수 ❺ 15 | void rwlock_read_release(int *rcnt) { 16 | __sync_fetch_and_sub(rcnt, 1); 17 | } 18 | 19 | // Writer용 록 획득 함수 ❻ 20 | void rwlock_write_acquire(bool *lock, volatile int *rcnt, int *wcnt) { 21 | __sync_fetch_and_add(wcnt, 1); // ❼ 22 | while (*rcnt); // Reader가 있으면 대기 23 | spinlock_acquire(lock); // ❽ 24 | } 25 | 26 | // Writer용 록 반환 함수 ❾ 27 | void rwlock_write_release(bool *lock, int *wcnt) { 28 | spinlock_release(lock); 29 | __sync_fetch_and_sub(wcnt, 1); 30 | } 31 | 32 | // 공유 변수 33 | int rcnt = 0; 34 | int wcnt = 0; 35 | bool lock = false; 36 | 37 | void reader() { // Reader용 함수 38 | for (;;) { 39 | rwlock_read_acquire(&rcnt, &wcnt); 40 | // 크리티컬 섹션(읽기만) 41 | rwlock_read_release(&rcnt); 42 | } 43 | } 44 | 45 | void writer () { // Writer용 함수 46 | for (;;) { 47 | rwlock_write_acquire(&lock, &rcnt, &wcnt); 48 | // 크리티컬 섹션(읽기 및 쓰기) 49 | rwlock_write_release(&lock, &wcnt); 50 | } 51 | } -------------------------------------------------------------------------------- /chap3/3.7/3_7_2_rwlock_pthreads.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; // ❶ 6 | 7 | void* reader(void *arg) { // Reader용 함수 ❷ 8 | if (pthread_rwlock_rdlock(&rwlock) != 0) { 9 | perror("pthread_rwlock_rdlock"); exit(-1); 10 | } 11 | 12 | // 크리티컬 섹션(읽기만) 13 | 14 | if (pthread_rwlock_unlock(&rwlock) != 0) { 15 | perror("pthread_rwlock_unlock"); exit(-1); 16 | } 17 | 18 | return NULL; 19 | } 20 | 21 | void* writer(void *arg) { // Writer용 함수 ❸ 22 | if (pthread_rwlock_wrlock(&rwlock) != 0) { 23 | perror("pthread_rwlock_wrlock"); exit(-1); 24 | } 25 | 26 | // 크리티컬 섹션(읽기) 27 | 28 | if (pthread_rwlock_unlock(&rwlock) != 0) { 29 | perror("pthread_rwlock_unlock"); exit(-1); 30 | } 31 | 32 | return NULL; 33 | } 34 | 35 | int main(int argc, char *argv[]) { 36 | // 스레드 생성 37 | pthread_t rd, wr; 38 | pthread_create(&rd, NULL, reader, NULL); 39 | pthread_create(&wr, NULL, writer, NULL); 40 | 41 | // 스레드 종료 대기 42 | pthread_join(rd, NULL); 43 | pthread_join(wr, NULL); 44 | 45 | // RW록 옵션 반환(해제)❹ 46 | if (pthread_rwlock_destroy(&rwlock) != 0) { 47 | perror("pthread_rwlock_destroy"); return -1; 48 | } 49 | 50 | return 0; 51 | } -------------------------------------------------------------------------------- /chap3/3.7/3_7_3_performance.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // do_lock 함수의 내용 전환 ❶ 8 | 9 | #ifdef RWLOCK 10 | #include "rwlock.c" 11 | #elif defined(RWLOCK_WR) 12 | #include "rwlock_wr.c" 13 | #elif defined(MUTEX) 14 | #include "mutex.c" 15 | #elif defined(EMPTY) 16 | #include "empty.c" 17 | #endif 18 | 19 | #include "barrier.c" 20 | 21 | volatile int flag = 0; // 이 플래그가 0인 동안 루프 22 | 23 | // 배리어 동기용 변수 24 | volatile int waiting_1 = 0; 25 | volatile int waiting_2 = 0; 26 | 27 | uint64_t count[NUM_THREAD - 1]; // ❷ 28 | 29 | void *worker(void *arg) { // 워커 스레드용 함수 ❸ 30 | uint64_t id = (uint64_t)arg; 31 | barrier(&waiting_1, NUM_THREAD); // 배리어 동기 32 | 33 | uint64_t n = 0; // ❹ 34 | while (flag == 0) { 35 | do_lock(); // 필요하다면 록을 획득하고 대기❺ 36 | n++; 37 | } 38 | count[id] = n; // 루프 횟수 기억 39 | 40 | barrier(&waiting_2, NUM_THREAD); // 배리어 동기 41 | 42 | return NULL; 43 | } 44 | 45 | void *timer(void *arg) { // 터이머스레드용 함수 ❻ 46 | barrier(&waiting_1, NUM_THREAD); // 배리어 동기 47 | 48 | sleep(180); 49 | flag = 1; 50 | 51 | barrier(&waiting_2, NUM_THREAD); // 배리어 동기 52 | for (int i = 0; i < NUM_THREAD - 1; i++) { 53 | printf("%lu\n", count[i]); 54 | } 55 | 56 | return NULL; 57 | } 58 | 59 | int main() { 60 | // 어ㅓ커 스레드 실행 61 | for (uint64_t i = 0; i < NUM_THREAD - 1; i++) { 62 | pthread_t th; 63 | pthread_create(&th, NULL, worker, (void *)i); 64 | pthread_detach(th); 65 | } 66 | 67 | // 타이머 스레드 실행 68 | pthread_t th; 69 | pthread_create(&th, NULL, timer, NULL); 70 | pthread_join(th, NULL); 71 | 72 | return 0; 73 | } -------------------------------------------------------------------------------- /chap3/3.7/Makefile: -------------------------------------------------------------------------------- 1 | targets = 3_7_1_rwlock_spin.o 3_7_2_rwlock_pthreads \ 2 | 3_7_3_performance_RWLOCK \ 3 | 3_7_3_performance_RWLOCK_WR \ 4 | 3_7_3_performance_MUTEX \ 5 | 3_7_3_performance_EMPTY 6 | CFLAGS = -O3 7 | LDFLAGS = -pthread 8 | 9 | # 다음 값을 변경하고 재컴파일 하면 소스 코드의 값도 변경된다 10 | NUM_THREAD = 4 11 | HOLDTIME = 100 12 | DEFS = -DNUM_THREAD=$(NUM_THREAD) -DHOLDTIME=$(HOLDTIME) 13 | 14 | all: $(targets) 15 | 16 | 3_7_3_performance_RWLOCK: 3_7_3_performance.c rwlock.c 17 | $(CC) $(CFLAGS) $(LDFLAGS) -DRWLOCK $(DEFS) $< -o $@ 18 | 19 | 3_7_3_performance_RWLOCK_WR: 3_7_3_performance.c rwlock_wr.c 20 | $(CC) $(CFLAGS) $(LDFLAGS) -DRWLOCK_WR $(DEFS) $< -o $@ 21 | 22 | 3_7_3_performance_MUTEX: 3_7_3_performance.c mutex.c 23 | $(CC) $(CFLAGS) $(LDFLAGS) -DMUTEX $(DEFS) $< -o $@ 24 | 25 | 3_7_3_performance_EMPTY: 3_7_3_performance.c empty.c 26 | $(CC) $(CFLAGS) $(LDFLAGS) -DEMPTY $(DEFS) $< -o $@ 27 | 28 | .PHONY: clean 29 | clean: 30 | rm -f $(targets) 31 | -------------------------------------------------------------------------------- /chap3/3.7/README.md: -------------------------------------------------------------------------------- 1 | # 3.7 Readers-Writer록 2 | 3 | ## 파일 4 | 5 | - `3_7_1_rwlock_spin.c`: p##, p## 6 | - `3_7_2_rwlock_pthreads.c`: p## 7 | - `3_7_3_performance.c`: p## 8 | - `empty.c`: p## 9 | - `mutex.c`: p##의 1번째 10 | - `rwlock.c`: p##의 2번째 11 | - `rwlock_wr.c`: p##의 3번째 12 | 13 | ## 컴파일 14 | 15 | `make`를 실행하면 `.o` 파일, 또는 실행 파일이 생성됩니다. 16 | 17 | ```sh 18 | $ make 19 | $ ls *.o 3_7_2_rwlock_pthreads 3_7_3_performance_RWLOCK 3_7_3_performance_RWLOCK_WR 3_7_3_performance_MUTEX 3_7_3_performance_EMPTY 20 | 3_7_1_rwlock_spin.o 3_7_2_rwlock_pthreads* 3_7_3_performance_EMPTY* 3_7_3_performance_MUTEX* 3_7_3_performance_RWLOCK* 3_7_3_performance_RWLOCK_WR* 21 | ``` 22 | -------------------------------------------------------------------------------- /chap3/3.7/barrier.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void barrier(volatile int *cnt, int max) { // ❶ 4 | __sync_fetch_and_add(cnt, 1); // ❷ 5 | while (*cnt < max); // ❸ 6 | } 7 | -------------------------------------------------------------------------------- /chap3/3.7/empty.c: -------------------------------------------------------------------------------- 1 | void do_lock() { 2 | for (uint64_t i = 0; i < HOLDTIME; i++) { 3 | asm volatile("nop"); // 아무것도 하지 않음 4 | } 5 | } -------------------------------------------------------------------------------- /chap3/3.7/mutex.c: -------------------------------------------------------------------------------- 1 | pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 2 | void do_lock() { 3 | pthread_mutex_lock(&lock); // 뮤텍스 4 | for (uint64_t i = 0; i < HOLDTIME; i++) { 5 | asm volatile("nop"); 6 | } 7 | pthread_mutex_unlock(&lock); 8 | } -------------------------------------------------------------------------------- /chap3/3.7/rwlock.c: -------------------------------------------------------------------------------- 1 | pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; 2 | void do_lock() { 3 | pthread_rwlock_rdlock(&lock); // 읽기 록 4 | for (uint64_t i = 0; i < HOLDTIME; i++) { 5 | asm volatile("nop"); 6 | } 7 | pthread_rwlock_unlock(&lock); 8 | } -------------------------------------------------------------------------------- /chap3/3.7/rwlock_wr.c: -------------------------------------------------------------------------------- 1 | pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; 2 | void do_lock() { 3 | pthread_rwlock_wrlock(&lock); // 쓰기 록 4 | for (uint64_t i = 0; i < HOLDTIME; i++) { 5 | asm volatile("nop"); 6 | } 7 | pthread_rwlock_unlock(&lock); 8 | } -------------------------------------------------------------------------------- /chap3/3.8/README.md: -------------------------------------------------------------------------------- 1 | # 3.8 Rust의 동기 처리 라이브러리 2 | 3 | 각 디렉터리에 `Cargo`용 저장호가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시 ```--release```를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일과 실행 예시 8 | 9 | 음과 같이 디렉터리로 이동한 뒤 실행합니다. 10 | 11 | ```sh 12 | $ cd ch3_8_5_channel 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_1_mutex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_8_1_mutex" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_1_mutex/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; // ❶ 2 | use std::thread; 3 | 4 | fn some_func(lock: Arc>) { // ❷ 5 | loop { 6 | // 록을 하지 않으면 Mutex 타입 안의 값은 참조 불가 7 | let mut val = lock.lock().unwrap(); // ❸ 8 | *val += 1; 9 | println!("{}", *val); 10 | } 11 | } 12 | 13 | fn main() { 14 | // Arc는 스레드 세이프한 참조 카운터 타입의 스마트 포인터 15 | let lock0 = Arc::new(Mutex::new(0)); // ❹ 16 | 17 | // 참조 카운터가 인크리먼트될 뿐이며 18 | // 내용은 클론되지 않음 19 | let lock1 = lock0.clone(); // ❺ 20 | 21 | // 스레드 생성 22 | // 클로저 내 변수로 이동 23 | let th0 = thread::spawn(move || { // ❻ 24 | some_func(lock0); 25 | }); 26 | 27 | // 스레드 생성 28 | // 클로저 내 변수로 이동 29 | let th1 = thread::spawn(move || { 30 | some_func(lock1); 31 | }); 32 | 33 | // 약속 34 | th0.join().unwrap(); 35 | th1.join().unwrap(); 36 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_2_cond/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_8_2_cond" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_2_cond/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex, Condvar}; // ❶ 2 | use std::thread; 3 | 4 | // Condvar 타입의 변수가 조건 변수이며 5 | // Mutex와 Condvar를 포함하는 튜플이 Arc에 포함되어 전달된다 6 | fn child(id: u64, p: Arc<(Mutex, Condvar)>) { // ❷ 7 | let &(ref lock, ref cvar) = &*p; 8 | 9 | // 먼저, 뮤텍스 록을 수행한다 10 | let mut started = lock.lock().unwrap(); // ❸ 11 | while !*started { // Mutex 안의 공유 변수가 false인 동안 루프 12 | // wait로 대기 13 | started = cvar.wait(started).unwrap(); // ❹ 14 | 15 | } 16 | 17 | // 다음과 같이 wait_while을 사용할 수도 있음 18 | // cvar.wait_while(started, |started| !*started).unwrap(); 19 | 20 | println!("child {}", id); 21 | } 22 | 23 | fn parent(p: Arc<(Mutex, Condvar)>) { // ❺ 24 | let &(ref lock, ref cvar) = &*p; 25 | 26 | // 먼저 뮤텍스 록을 수행한다 ❻ 27 | let mut started = lock.lock().unwrap(); 28 | *started = true; // 공유 변수 업데이트 29 | cvar.notify_all(); // 알림 30 | println!("parent"); 31 | } 32 | 33 | fn main() { 34 | // 뮤텍스와 조건 변수를 작성 35 | let pair0 = Arc::new((Mutex::new(false), Condvar::new())); 36 | let pair1 = pair0.clone(); 37 | let pair2 = pair0.clone(); 38 | 39 | let c0 = thread::spawn(move || { child(0, pair0) }); 40 | let c1 = thread::spawn(move || { child(1, pair1) }); 41 | let p = thread::spawn(move || { parent(pair2) }); 42 | 43 | c0.join().unwrap(); 44 | c1.join().unwrap(); 45 | p.join().unwrap(); 46 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_3_rwlock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_8_3_rwlock" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_3_rwlock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::RwLock; // ❶ 2 | 3 | fn main() { 4 | let lock = RwLock::new(10); // ❷ 5 | { 6 | // 이뮤터블한 참조를 얻음 ❸ 7 | let v1 = lock.read().unwrap(); 8 | let v2 = lock.read().unwrap(); 9 | println!("v1 = {}", v1); 10 | println!("v2 = {}", v2); 11 | } 12 | 13 | { 14 | // mutable한 참조를 얻음❹ 15 | let mut v = lock.write().unwrap(); 16 | *v = 7; 17 | println!("v = {}", v); 18 | } 19 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_4_barrier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_8_4_barrier" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_4_barrier/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Barrier}; // ❶ 2 | use std::thread; 3 | 4 | fn main() { 5 | // 스레드 핸들러를 저장하는 벡터 6 | let mut v = Vec::new(); // ❷ 7 | 8 | // 10 스레드만큼의 배리어 동기를 Arc로 감쌈 9 | let barrier = Arc::new(Barrier::new(10)); // ❸ 10 | 11 | // 10 스레드 실행 12 | for _ in 0..10 { 13 | let b = barrier.clone(); 14 | let th = thread::spawn(move || { 15 | b.wait(); // 배리어 동기 ❹ 16 | println!("finished barrier"); 17 | }); 18 | v.push(th); 19 | } 20 | 21 | for th in v { 22 | th.join().unwrap(); 23 | } 24 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_channel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_8_5_channel" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_channel/src/channel.rs: -------------------------------------------------------------------------------- 1 | use crate::semaphore::Semaphore; 2 | use std::collections::LinkedList; 3 | use std::sync::{Arc, Condvar, Mutex}; 4 | 5 | // 송신단을 위한 타입 ❶ 6 | #[derive(Clone)] 7 | pub struct Sender { 8 | sem: Arc, // 유한성을 구현하는 세마포 9 | buf: Arc>>, // 큐 10 | cond: Arc, // 읽기 측의 조건 변수 11 | } 12 | 13 | impl Sender { // ❷ 14 | // 송신 함수 15 | pub fn send(&self, data: T) { 16 | self.sem.wait(); // 큐의 최댓값에 도달하면 대기 ❸ 17 | let mut buf = self.buf.lock().unwrap(); 18 | buf.push_back(data); // 인큐 19 | self.cond.notify_one(); // 읽기 측에 대한 알림 ❹ 20 | } 21 | } 22 | 23 | // 수신단을 위한 타입 ❶ 24 | pub struct Receiver { 25 | sem: Arc, // 유한성을 구현하는 세마포 26 | buf: Arc>>, // 큐 27 | cond: Arc, // 읽기 측의 조건 변수 28 | } 29 | 30 | impl Receiver { 31 | pub fn recv(&self) -> T { 32 | let mut buf = self.buf.lock().unwrap(); 33 | loop { 34 | // 큐에서 추출 ❷ 35 | if let Some(data) = buf.pop_front() { 36 | self.sem.post(); // ❸ 37 | return data; 38 | } 39 | // 빈 경우 대기 ❹ 40 | buf = self.cond.wait(buf).unwrap(); 41 | } 42 | } 43 | } 44 | 45 | pub fn channel(max: isize) -> (Sender, Receiver) { 46 | assert!(max > 0); 47 | let sem = Arc::new(Semaphore::new(max)); 48 | let buf = Arc::new(Mutex::new(LinkedList::new())); 49 | let cond = Arc::new(Condvar::new()); 50 | let tx = Sender { 51 | sem: sem.clone(), 52 | buf: buf.clone(), 53 | cond: cond.clone(), 54 | }; 55 | let rx = Receiver { sem, buf, cond }; 56 | (tx, rx) 57 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_channel/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod channel; 2 | pub mod semaphore; 3 | 4 | use channel::channel; 5 | 6 | const NUM_LOOP: usize = 100000; 7 | const NUM_THREADS: usize = 8; 8 | 9 | fn main() { 10 | let (tx, rx) = channel(4); 11 | let mut v = Vec::new(); 12 | 13 | // 수신용 스레드 14 | let t = std::thread::spawn(move || { 15 | let mut cnt = 0; 16 | while cnt < NUM_THREADS * NUM_LOOP { 17 | let n = rx.recv(); 18 | println!("recv: n = {:?}", n); 19 | cnt += 1; 20 | } 21 | }); 22 | 23 | v.push(t); 24 | 25 | // 송신용 스레드 26 | for i in 0..NUM_THREADS { 27 | let tx0 = tx.clone(); 28 | let t = std::thread::spawn(move || { 29 | for j in 0..NUM_LOOP { 30 | tx0.send((i, j)); 31 | } 32 | }); 33 | v.push(t); 34 | } 35 | 36 | for t in v { 37 | t.join().unwrap(); 38 | } 39 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_channel/src/semaphore.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Condvar, Mutex}; 2 | 3 | // 세마포용 타입 ❶ 4 | pub struct Semaphore { 5 | mutex: Mutex, 6 | cond: Condvar, 7 | max: isize, 8 | } 9 | 10 | impl Semaphore { 11 | pub fn new(max: isize) -> Self { // ❷ 12 | Semaphore { 13 | mutex: Mutex::new(0), 14 | cond: Condvar::new(), 15 | max, 16 | } 17 | } 18 | 19 | pub fn wait(&self) { 20 | // 카운터가 최댓값 이상이면 대기 ❸ 21 | let mut cnt = self.mutex.lock().unwrap(); 22 | while *cnt >= self.max { 23 | cnt = self.cond.wait(cnt).unwrap(); 24 | } 25 | *cnt += 1; // ❹ 26 | } 27 | 28 | pub fn post(&self) { 29 | // 카운터를 디크리먼트 ❺ 30 | let mut cnt = self.mutex.lock().unwrap(); 31 | *cnt -= 1; 32 | if *cnt <= self.max { 33 | self.cond.notify_one(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_semaphore/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_8_5_semaphore" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_semaphore/src/main.rs: -------------------------------------------------------------------------------- 1 | mod semaphore; 2 | 3 | use semaphore::Semaphore; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | use std::sync::Arc; 6 | 7 | const NUM_LOOP: usize = 100000; 8 | const NUM_THREADS: usize = 8; 9 | const SEM_NUM: isize = 4; 10 | 11 | static mut CNT: AtomicUsize = AtomicUsize::new(0); 12 | 13 | fn main() { 14 | let mut v = Vec::new(); 15 | // SEM_NUM만큼 동시 실행 가능한 세마포 16 | let sem = Arc::new(Semaphore::new(SEM_NUM)); 17 | 18 | for i in 0..NUM_THREADS { 19 | let s = sem.clone(); 20 | let t = std::thread::spawn(move || { 21 | for _ in 0..NUM_LOOP { 22 | s.wait(); 23 | 24 | // 아토믹하게 인크리먼트 및 디크리먼트 25 | unsafe { CNT.fetch_add(1, Ordering::SeqCst) }; 26 | let n = unsafe { CNT.load(Ordering::SeqCst) }; 27 | println!("semaphore: i = {}, CNT = {}", i, n); 28 | assert!((n as isize) <= SEM_NUM); 29 | unsafe { CNT.fetch_sub(1, Ordering::SeqCst) }; 30 | 31 | s.post(); 32 | } 33 | }); 34 | v.push(t); 35 | } 36 | 37 | for t in v { 38 | t.join().unwrap(); 39 | } 40 | } -------------------------------------------------------------------------------- /chap3/3.8/ch3_8_5_semaphore/src/semaphore.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Condvar, Mutex}; 2 | 3 | // 세마포용 타입 ❶ 4 | pub struct Semaphore { 5 | mutex: Mutex, 6 | cond: Condvar, 7 | max: isize, 8 | } 9 | 10 | impl Semaphore { 11 | pub fn new(max: isize) -> Self { // ❷ 12 | Semaphore { 13 | mutex: Mutex::new(0), 14 | cond: Condvar::new(), 15 | max, 16 | } 17 | } 18 | 19 | pub fn wait(&self) { 20 | // 카운터가 최댓값 이상이면 대기 ❸ 21 | let mut cnt = self.mutex.lock().unwrap(); 22 | while *cnt >= self.max { 23 | cnt = self.cond.wait(cnt).unwrap(); 24 | } 25 | *cnt += 1; // ❹ 26 | } 27 | 28 | pub fn post(&self) { 29 | // 카운터를 디크리먼트 ❺ 30 | let mut cnt = self.mutex.lock().unwrap(); 31 | *cnt -= 1; 32 | if *cnt <= self.max { 33 | self.cond.notify_one(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /chap3/3.9/README.md: -------------------------------------------------------------------------------- 1 | # 3.9 베이커리 알고리즘 2 | 3 | 각 디렉터리에 `Cargo`용 저장소가 있으므로, 디렉터리에 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 ```--release```를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예시 8 | 9 | 다음과 같이 디렉터리에 이동 후 실행합니다. 10 | 11 | ```sh 12 | $ cd ch3_9_bakery 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap3/3.9/ch3_9_bakery/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch3_9_bakery" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap3/3.9/ch3_9_bakery/src/main.rs: -------------------------------------------------------------------------------- 1 | // 최적화 억제 읽기/쓰기용 2 | use std::ptr::{read_volatile, write_volatile}; // ❶ 3 | // 메모리 배리어용 4 | use std::sync::atomic::{fence, Ordering}; // ❷ 5 | use std::thread; 6 | 7 | const NUM_THREADS: usize = 4; // 스레드 수 8 | const NUM_LOOP: usize = 100000; // 각 스레드에서의 루프 수 9 | 10 | // volatile용 매크로 ❸ 11 | macro_rules! read_mem { 12 | ($addr: expr) => { unsafe { read_volatile($addr) } }; 13 | } 14 | 15 | macro_rules! write_mem { 16 | ($addr: expr, $val: expr) => { 17 | unsafe { write_volatile($addr, $val) } 18 | }; 19 | } 20 | 21 | // 베이커리 알고리즘용 타입 ❹ 22 | struct BakeryLock { 23 | entering: [bool; NUM_THREADS], 24 | tickets: [Option; NUM_THREADS], 25 | } 26 | 27 | impl BakeryLock { 28 | // 록 함수. idx는 스레드 번호 29 | fn lock(&mut self, idx: usize) -> LockGuard { 30 | // 여기부터 티켓 취득 처리 ❺ 31 | fence(Ordering::SeqCst); 32 | write_mem!(&mut self.entering[idx], true); 33 | fence(Ordering::SeqCst); 34 | 35 | // 현재 배포되어 있는 티켓의 최댓값 취득 ❻ 36 | let mut max = 0; 37 | for i in 0..NUM_THREADS { 38 | if let Some(t) = read_mem!(&self.tickets[i]) { 39 | max = max.max(t); 40 | } 41 | } 42 | // 최댓값 + 1을 자신의 티멧 번호로 한다 ❼ 43 | let ticket = max + 1; 44 | write_mem!(&mut self.tickets[idx], Some(ticket)); 45 | 46 | fence(Ordering::SeqCst); 47 | write_mem!(&mut self.entering[idx], false); // ❽ 48 | fence(Ordering::SeqCst); 49 | 50 | // 여기부터 대기 처리 ❾ 51 | for i in 0..NUM_THREADS { 52 | if i == idx { 53 | continue; 54 | } 55 | 56 | // 스레드 i가 티켓 취득 중이면 대기 57 | while read_mem!(&self.entering[i]) {} // ❿ 58 | 59 | loop { 60 | // 스레드 i와 자신의 우선 순위를 비교해 61 | // 자신의 우선 순위가 높거나, 62 | // 스레드 i가 처리 중이 아니면 대기 종료 ⓫ 63 | match read_mem!(&self.tickets[i]) { 64 | Some(t) => { 65 | // 스레드 i의 티켓 번호보다 66 | // 자신의 범호가 낮거나, 67 | // 티멧 번호가 같고 68 | // 자신의 스레드 번호가 작으면 69 | // 대기 종료 70 | if ticket < t || 71 | (ticket == t && idx < i) { 72 | break; 73 | } 74 | } 75 | None => { 76 | // 스레드 i가 처리 중이 아니면 77 | // 대기 종료 78 | break; 79 | } 80 | } 81 | } 82 | } 83 | 84 | fence(Ordering::SeqCst); 85 | LockGuard { idx } 86 | } 87 | } 88 | 89 | // 록 관리용 타입 ⓬ 90 | struct LockGuard { 91 | idx: usize, 92 | } 93 | 94 | impl Drop for LockGuard { 95 | // 록 해제 처리 ⓭ 96 | fn drop(&mut self) { 97 | fence(Ordering::SeqCst); 98 | write_mem!(&mut LOCK.tickets[self.idx], None); 99 | } 100 | } 101 | 102 | // 글로벌 변수 ⓮ 103 | static mut LOCK: BakeryLock = BakeryLock { 104 | entering: [false; NUM_THREADS], 105 | tickets: [None; NUM_THREADS], 106 | }; 107 | 108 | static mut COUNT: u64 = 0; 109 | 110 | fn main() { 111 | // NUM_THREADS만큼 스레드를 생성 112 | let mut v = Vec::new(); 113 | for i in 0..NUM_THREADS { 114 | let th = thread::spawn(move || { 115 | // NUM_LOOP 만큼 루프 반복하면서 COUNT를 인크리먼트 116 | for _ in 0..NUM_LOOP { 117 | // 록 획득 118 | let _lock = unsafe { LOCK.lock(i) }; 119 | unsafe { 120 | let c = read_volatile(&COUNT); 121 | write_volatile(&mut COUNT, c + 1); 122 | } 123 | } 124 | }); 125 | v.push(th); 126 | } 127 | 128 | for th in v { 129 | th.join().unwrap(); 130 | } 131 | 132 | println!( 133 | "COUNT = {} (expected = {})", 134 | unsafe { COUNT }, 135 | NUM_LOOP * NUM_THREADS 136 | ); 137 | } -------------------------------------------------------------------------------- /chap3/README.md: -------------------------------------------------------------------------------- 1 | # 3장. 동기처리 1 2 | 3 | - [3.2 아토믹 처리](./3.2/) 4 | - [3.3 뮤텍스](./3.3/) 5 | - [3.4 세마포](./3.4/) 6 | - [3.5 조건 변수](./3.5/) 7 | - [3.6 배리어 동기](./3.6/) 8 | - [3.7 Readers-Writer록](./3.7/) 9 | - [3.8 Rust의 동기 처리 라이브러리](./3.8/) 10 | - [3.9 베이커리 알고리즘](./3.9/) 11 | -------------------------------------------------------------------------------- /chap4/4.1/README.md: -------------------------------------------------------------------------------- 1 | # 4.1 데드록 2 | 3 | 각 디렉토리에 `Cargo`용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시에 ```--release``를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | > ⚠️ **경고**: 몇몇 코드는 데드록에 빠지며 프로그램이 정지하지 않으므로 ctrl + c 로 실행을 중지하기 바랍니다. 8 | 9 | ## 컴파일 실행 예시 10 | 11 | 다음과 같이 디렉터리로 이동 후 실행합니다. 12 | 13 | ```sh 14 | $ cd ch4_1_philosophers 15 | $ cargo run --release 16 | ``` 17 | -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_philosophers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_1_philosophers" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_philosophers/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::thread; 3 | 4 | fn main() { 5 | // 포크 2개 ❶ 6 | let c0 = Arc::new(Mutex::new(())); 7 | let c1 = Arc::new(Mutex::new(())); 8 | 9 | let c0_p0 = c0.clone(); 10 | let c1_p0 = c1.clone(); 11 | 12 | // 철학자 1 13 | let p0 = thread::spawn(move || { 14 | for _ in 0..100000 { 15 | let _n1 = c0_p0.lock().unwrap(); // ❷ 16 | let _n2 = c1_p0.lock().unwrap(); 17 | println!("0: eating"); 18 | } 19 | }); 20 | 21 | // 철학자 2 22 | let p1 = thread::spawn(move || { 23 | for _ in 0..100000 { 24 | let _n1 = c1.lock().unwrap(); 25 | let _n2 = c0.lock().unwrap(); 26 | println!("1: eating"); 27 | } 28 | }); 29 | 30 | p0.join().unwrap(); 31 | p1.join().unwrap(); 32 | } -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_1_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_1_rwlock_1_1" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_1_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | use std::thread; 3 | 4 | fn main() { 5 | let val = Arc::new(RwLock::new(true)); 6 | 7 | let t = thread::spawn(move || { 8 | let flag = val.read().unwrap(); // ❶ 9 | if *flag { 10 | *val.write().unwrap() = false; // ❷ 11 | println!("flag is true"); 12 | } 13 | }); 14 | 15 | t.join().unwrap(); 16 | } -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_1_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_1_rwlock_1_2" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_1_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | use std::thread; 3 | 4 | fn main() { 5 | let val = Arc::new(RwLock::new(true)); 6 | 7 | let t = thread::spawn(move || { 8 | let flag = *val.read().unwrap(); // ❶ 9 | if flag { 10 | *val.write().unwrap() = false; // ❷ 11 | println!("flag is true"); 12 | } 13 | }); 14 | 15 | t.join().unwrap(); 16 | } -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_2_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_1_rwlock_2_1" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_2_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | use std::thread; 3 | 4 | fn main() { 5 | let val = Arc::new(RwLock::new(true)); 6 | 7 | let t = thread::spawn(move || { 8 | let _flag = val.read().unwrap(); // ❶ 9 | *val.write().unwrap() = false; // ❷ 10 | println!("deadlock"); 11 | }); 12 | 13 | t.join().unwrap(); 14 | } -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_2_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_1_rwlock_2_2" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.1/ch4_1_rwlock_2_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | use std::thread; 3 | 4 | fn main() { 5 | let val = Arc::new(RwLock::new(true)); 6 | 7 | let t = thread::spawn(move || { 8 | let _ = val.read().unwrap(); // ❶ 9 | *val.write().unwrap() = false; // ❷ 10 | println!("not deadlock"); 11 | }); 12 | 13 | t.join().unwrap(); 14 | } -------------------------------------------------------------------------------- /chap4/4.3/README.md: -------------------------------------------------------------------------------- 1 | # 4.3 은행원 알고리즘 2 | 3 | 각 디렉토리에 `Cargo`용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시에 ```--release``를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 실행 예시 8 | 9 | 다음과 같이 디렉터리로 이동 후 실행합니다. 10 | 11 | ```sh 12 | $ cd ch4_3_banker 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap4/4.3/ch4_3_banker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_3_banker" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.3/ch4_3_banker/src/banker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | struct Resource { 4 | available: [usize; NRES], // 이용 가능한 리소스 5 | allocation: [[usize; NRES]; NTH], // 스레드 i가 확보 중인 리소스 6 | max: [[usize; NRES]; NTH], // 스레드 i가 필요로 하는 리소스의 최댓값 7 | } 8 | 9 | impl Resource { 10 | fn new(available: [usize; NRES], max: [[usize; NRES]; NTH]) -> Self { 11 | Resource { 12 | available, 13 | allocation: [[0; NRES]; NTH], 14 | max, 15 | } 16 | } 17 | 18 | // 현재 상태가 데드록을 발생시키지 않는가 확인 19 | fn is_safe(&self) -> bool { 20 | let mut finish = [false; NTH]; // 스레드 i는 리소스 획득과 반환에 성공했는가? 21 | let mut work = self.available.clone(); // 이용 가능한 리소스의 시뮬레이션값 22 | 23 | loop { 24 | // 모든 스레드 i와 리소스 j에 대해, 25 | // finish[i] == false && work[j] >= (self.max[i][j] - self.allocation[i][j]) 26 | // 을 만족하는 스레드를 찾는다. 27 | let mut found = false; 28 | let mut num_true = 0; 29 | for (i, alc) in self.allocation.iter().enumerate() { 30 | if finish[i] { 31 | num_true += 1; 32 | continue; 33 | } 34 | 35 | // need[j] = self.max[i][j] - self.allocation[i][j] 를 계산하고, 36 | // 모든 리소스 j에 대해, work[j] >= need[j] 인가를 판정한다. 37 | let need = self.max[i].iter().zip(alc).map(|(m, a)| m - a); 38 | let is_avail = work.iter().zip(need).all(|(w, n)| *w >= n); 39 | if is_avail { 40 | // 스레드 i가 리소스 확보 가능 41 | found = true; 42 | finish[i] = true; 43 | for (w, a) in work.iter_mut().zip(alc) { 44 | *w += *a // 스레드 i가 현재 확보하고 있는 리소스를 반환 45 | } 46 | break; 47 | } 48 | } 49 | 50 | if num_true == NTH { 51 | // 모든 스레드가 리소스 확보 가능하면 안전함 52 | return true; 53 | } 54 | 55 | if !found { 56 | // 스레드가 리소스를 확보할 수 없음 57 | break; 58 | } 59 | } 60 | 61 | false 62 | } 63 | 64 | // id번 째의 스레드가 resource를 하나 얻음 65 | fn take(&mut self, id: usize, resource: usize) -> bool { 66 | // 스레드 번호, 리소스 번호 검사 67 | if id >= NTH || resource >= NRES || self.available[resource] == 0 { 68 | return false; 69 | } 70 | 71 | // 리소스 확보를 시험해 본다 72 | self.allocation[id][resource] += 1; 73 | self.available[resource] -= 1; 74 | 75 | if self.is_safe() { 76 | true // 리소스 확보 성공 77 | } else { 78 | // 리소스 확보에 실패했으므로 상태 원복 79 | self.allocation[id][resource] -= 1; 80 | self.available[resource] += 1; 81 | false 82 | } 83 | } 84 | 85 | // id번 째의 스레드가 resource를 하나 반환 86 | fn release(&mut self, id: usize, resource: usize) { 87 | // 스레드 번호, 리소스 번호를 검사 88 | if id >= NTH || resource >= NRES || self.allocation[id][resource] == 0 { 89 | return; 90 | } 91 | 92 | self.allocation[id][resource] -= 1; 93 | self.available[resource] += 1; 94 | } 95 | } 96 | 97 | #[derive(Clone)] 98 | pub struct Banker { 99 | resource: Arc>>, 100 | } 101 | 102 | impl Banker { 103 | pub fn new(available: [usize; NRES], max: [[usize; NRES]; NTH]) -> Self { 104 | Banker { 105 | resource: Arc::new(Mutex::new(Resource::new(available, max))), 106 | } 107 | } 108 | 109 | pub fn take(&self, id: usize, resource: usize) -> bool { 110 | let mut r = self.resource.lock().unwrap(); 111 | r.take(id, resource) 112 | } 113 | 114 | pub fn release(&self, id: usize, resource: usize) { 115 | let mut r = self.resource.lock().unwrap(); 116 | r.release(id, resource) 117 | } 118 | } -------------------------------------------------------------------------------- /chap4/4.3/ch4_3_banker/src/main.rs: -------------------------------------------------------------------------------- 1 | mod banker; 2 | 3 | use banker::Banker; 4 | use std::thread; 5 | 6 | const NUM_LOOP: usize = 100000; 7 | 8 | fn main() { 9 | // 이용 가능한 포크의 수, 철학자가 이용하는 포크 최대 수 설정 10 | let banker = Banker::<2, 2>::new([1, 1], [[1, 1], [1, 1]]); 11 | let banker0 = banker.clone(); 12 | 13 | let philosopher0 = thread::spawn(move || { 14 | for _ in 0..NUM_LOOP { 15 | // 포크 0과 1을 확보 16 | while !banker0.take(0, 0) {} 17 | while !banker0.take(0, 1) {} 18 | 19 | println!("0: eating"); 20 | 21 | // 포크 0과 1을 반환 22 | banker0.release(0, 0); 23 | banker0.release(0, 1); 24 | } 25 | }); 26 | 27 | let philosopher1 = thread::spawn(move || { 28 | for _ in 0..NUM_LOOP { 29 | // 포크 1과 0을 확보 30 | while !banker.take(1, 1) {} 31 | while !banker.take(1, 0) {} 32 | 33 | println!("1: eating"); 34 | 35 | // 포크 1과 0을 반환 36 | banker.release(1, 1); 37 | banker.release(1, 0); 38 | } 39 | }); 40 | 41 | philosopher0.join().unwrap(); 42 | philosopher1.join().unwrap(); 43 | } -------------------------------------------------------------------------------- /chap4/4.4/README.md: -------------------------------------------------------------------------------- 1 | # 4.4 재귀 록 2 | 3 | ## ch4_4_reent_c 4 | 5 | `ch4_4_reent_c`는 C 언어이므로 `make`를 이용해 컴파일 한 뒤 실행합니다. 6 | 7 | ```sh 8 | $ cd ch4_4_reent_c 9 | $ make 10 | $ ./reent 11 | ``` 12 | 13 | ## ch4_4_reent_rust 14 | 15 | `ch4_4_reent_rust`는 Rust 언어이므로 `cargo`를 이용해 컴파일 및 실행합니다. 16 | 17 | > ⚠️ **경고**: 이 ㅗ드는 데드록에 빠져 프로그램이 정지하지 않으므로 ctrl + c로 실행을 정지하기 바랍니다. 18 | 19 | ```sh 20 | $ cd ch4_4_reent_rust 21 | $ cargo run --release 22 | ``` 23 | -------------------------------------------------------------------------------- /chap4/4.4/ch4_4_reent_c/Makefile: -------------------------------------------------------------------------------- 1 | targets = reent 2 | CFLAGS = -O3 -DNUM_THREADS=4 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f $(targets) 10 | -------------------------------------------------------------------------------- /chap4/4.4/ch4_4_reent_c/reent.c: -------------------------------------------------------------------------------- 1 | #include "../../../chap3/3.3/3_3_1_spinlock_2.c" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // 재진입 가능한 뮤텍스용 타입 ❶ 10 | struct reent_lock { 11 | bool lock; // 록용 공유 변수 12 | int id; // 현재 록을 획득 중인 스레드 ID, 0이 아니면 록 획득중임 13 | int cnt; // 재귀 록 카운트 14 | }; 15 | 16 | // 재귀 록 획득 함수 17 | void reentlock_acquire(struct reent_lock *lock, int id) { 18 | // 록 획득 중이고 자신이 획득 중인지 판정 ❷ 19 | if (lock->lock && lock->id == id) { 20 | // 자신이 획등 중이면 카운트를 인크리먼트 21 | lock->cnt++; 22 | } else { 23 | // 어떤 스레드도 혹을 획득하지 않았거나, 24 | // 다른 스레드가 록 획득 중이면 록 획득 25 | spinlock_acquire(&lock->lock); 26 | // 록윽 획득하면 자신의 스레드 ID를 설정하고 27 | // 마운트를 인크리먼트 28 | lock->id = id; 29 | lock->cnt++; 30 | } 31 | } 32 | 33 | // 재귀 록 해제 함수 34 | void reentlock_release(struct reent_lock *lock) { 35 | // 카운트를 디크리먼트하고, 36 | // 해당 카운트가 0이 되면 록 해제 ❸ 37 | lock->cnt--; 38 | if (lock->cnt == 0) { 39 | lock->id = 0; 40 | spinlock_release(&lock->lock); 41 | } 42 | } 43 | 44 | struct reent_lock lock_var; // 록용 공유 변수 45 | 46 | // n회 재귀적으로 호출해 록을 거는 테스트 함수 47 | void reent_lock_test(int id, int n) { 48 | if (n == 0) 49 | return; 50 | 51 | // 재귀 록 52 | reentlock_acquire(&lock_var, id); 53 | reent_lock_test(id, n - 1); 54 | reentlock_release(&lock_var); 55 | } 56 | 57 | // 스레드용 함수 58 | void *thread_func(void *arg) { 59 | int id = (int)arg; 60 | assert(id != 0); 61 | for (int i = 0; i < 10000; i++) { 62 | reent_lock_test(id, 10); 63 | } 64 | return NULL; 65 | } 66 | 67 | int main(int argc, char *argv[]) { 68 | pthread_t v[NUM_THREADS]; 69 | for (int i = 0; i < NUM_THREADS; i++) { 70 | pthread_create(&v[i], NULL, thread_func, (void *)(i + 1)); 71 | } 72 | for (int i = 0; i < NUM_THREADS; i++) { 73 | pthread_join(v[i], NULL); 74 | } 75 | return 0; 76 | } -------------------------------------------------------------------------------- /chap4/4.4/ch4_4_reent_rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_4_reent_rust" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.4/ch4_4_reent_rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | fn main() { 4 | // 뮤텍스를 Arc로 작성하고 클론 5 | let lock0 = Arc::new(Mutex::new(0)); // ❶ 6 | // Arc의 클론은 참조 카운터를 증가하기만 한다 7 | let lock1 = lock0.clone(); // ❷ 8 | 9 | let a = lock0.lock().unwrap(); 10 | let b = lock1.lock().unwrap(); // 데드록 ❸ 11 | println!("{}", a); 12 | println!("{}", b); 13 | } -------------------------------------------------------------------------------- /chap4/4.5/Makefile: -------------------------------------------------------------------------------- 1 | targets = spurious 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f $(targets) 10 | -------------------------------------------------------------------------------- /chap4/4.5/README.md: -------------------------------------------------------------------------------- 1 | # 4.5 의사 각성 2 | 3 | ## 컴파일 4 | 5 | `make`를 실행해서 컴파일합니다. 6 | 7 | ```sh 8 | $ make 9 | $ ls spurious 10 | spurious* 11 | ``` 12 | -------------------------------------------------------------------------------- /chap4/4.5/spurious.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 9 | pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 10 | 11 | // 시그널 핸들러 ❶ 12 | void handler(int sig) { printf("received signal: %d\n", sig); } 13 | 14 | int main(int argc, char *argv[]) { 15 | // 프로세스 ID를 표시 ❷ 16 | pid_t pid = getpid(); 17 | printf("pid: %d\n", pid); 18 | 19 | // 시그널 핸들러 등록 20 | signal(SIGUSR1, handler); // ❸ 21 | 22 | // wait하고 있지만, 누구도 notify를 하지 않으므로 멈춰있어야 함 ❹ 23 | pthread_mutex_lock(&mutex); 24 | if (pthread_cond_wait(&cond, &mutex) != 0) { 25 | perror("pthread_cond_wait"); 26 | exit(1); 27 | } 28 | printf("sprious wake up\n"); 29 | pthread_mutex_unlock(&mutex); 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /chap4/4.6/README.md: -------------------------------------------------------------------------------- 1 | # 4.6 시그널 2 | 3 | ## ch4_6_signal_c 4 | 5 | `ch4_6_signal_c`는 C 언어이므로 `make`로 컴파일 한 뒤 실행합니다. 6 | 7 | ```sh 8 | $ cd ch4_6_signal_c 9 | $ make 10 | $ ./signal_c 11 | ``` 12 | 13 | ## ch4_6_signal_rust 14 | 15 | `ch4_6_signal_rust`는 Rust 언어이므로 `cargo`로 컴파일 및 실행합니다. 16 | 17 | ```sh 18 | $ cd ch4_6_signal_rust 19 | $ cargo run --release 20 | ``` 21 | -------------------------------------------------------------------------------- /chap4/4.6/ch4_6_signal_c/Makefile: -------------------------------------------------------------------------------- 1 | targets = signal_c 2 | CFLAGS = -O3 3 | LDFLAGS = -pthread 4 | 5 | all: $(targets) 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f $(targets) 10 | -------------------------------------------------------------------------------- /chap4/4.6/ch4_6_signal_c/signal_c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 8 | sigset_t set; 9 | 10 | void *handler(void *arg) { // ❶ 11 | pthread_detach(pthread_self()); // 디태치 ❷ 12 | 13 | int sig; 14 | for (;;) { 15 | if (sigwait(&set, &sig) != 0) { // ❸ 16 | perror("sigwait"); 17 | exit(1); 18 | } 19 | printf("received signal: %d\n", sig); 20 | pthread_mutex_lock(&mutex); 21 | // 무언가의 처리 22 | pthread_mutex_unlock(&mutex); 23 | } 24 | 25 | return NULL; 26 | } 27 | 28 | void *worker(void *arg) { // ❹ 29 | for (int i = 0; i < 10; i++) { 30 | pthread_mutex_lock(&mutex); 31 | // 무언가의 처리 32 | sleep(1); 33 | pthread_mutex_unlock(&mutex); 34 | sleep(1); 35 | } 36 | return NULL; 37 | } 38 | 39 | int main(int argc, char *argv[]) { 40 | // 프로세스 ID를 표시 41 | pid_t pid = getpid(); 42 | printf("pid: %d\n", pid); 43 | 44 | // SIGUSR1 시그널을 블록으로 설정 45 | // 이 설정은 뒤에서 작성될 스레드에서도 이어진다 ❺ 46 | sigemptyset(&set); 47 | sigaddset(&set, SIGUSR1); 48 | if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) { 49 | perror("pthread_sigmask"); 50 | return 1; 51 | } 52 | 53 | pthread_t th, wth; 54 | pthread_create(&th, NULL, handler, NULL); 55 | pthread_create(&wth, NULL, worker, NULL); 56 | pthread_join(wth, NULL); 57 | 58 | return 0; 59 | } -------------------------------------------------------------------------------- /chap4/4.6/ch4_6_signal_rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_6_signal_rust" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.6/ch4_6_signal_rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use signal_hook::{iterator::Signals, SIGUSR1}; // ❶ 2 | use std::{error::Error, process, thread, time::Duration}; 3 | 4 | fn main() -> Result<(), Box> { 5 | // 프로세스 ID를 표시 6 | println!("pid: {}", process::id()); 7 | 8 | let signals = Signals::new(&[SIGUSR1])?; // ❷ 9 | thread::spawn(move || { 10 | // 시그널 수신 11 | for sig in signals.forever() { // ❸ 12 | println!("received signal: {:?}", sig); 13 | } 14 | }); 15 | 16 | // 10초 슬립 17 | thread::sleep(Duration::from_secs(10)); 18 | Ok(()) 19 | } -------------------------------------------------------------------------------- /chap4/4.7/README.md: -------------------------------------------------------------------------------- 1 | # 4.7 메모리 배리어 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 `--release`를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예시 8 | 9 | 다음과 같이 디렉터리에 이동 후 실행합니다. 10 | 11 | ```sh 12 | $ cd ch4_7_barrier 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap4/4.7/ch4_7_barrier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch4_7_barrier" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap4/4.7/ch4_7_barrier/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; // ❶ 2 | use std::ops::{Deref, DerefMut}; // ❷ 3 | use std::sync::atomic::{AtomicBool, Ordering}; // ❸ 4 | use std::sync::Arc; 5 | 6 | const NUM_THREADS: usize = 4; 7 | const NUM_LOOP: usize = 100000; 8 | 9 | // 스핀록용 타입 ❹ 10 | struct SpinLock { 11 | lock: AtomicBool, // 록용 공유 변수 12 | data: UnsafeCell, // 보호 대상 데이터 13 | } 14 | 15 | // 록 해제 및록 중에 보호 대상 데이터를 조작하기 위한 타입 ❺ 16 | struct SpinLockGuard<'a, T> { 17 | spin_lock: &'a SpinLock, 18 | } 19 | 20 | impl SpinLock { 21 | fn new(v: T) -> Self { 22 | SpinLock { 23 | lock: AtomicBool::new(false), 24 | data: UnsafeCell::new(v), 25 | } 26 | } 27 | 28 | // 록 함수 ❻ 29 | fn lock(&self) -> SpinLockGuard { 30 | loop { 31 | // 록용 공유 변수가 false가 될 때까지 대기 32 | while self.lock.load(Ordering::Relaxed) {} 33 | 34 | // 록용 공유 변수를 아토믹하게 씀 35 | if let Ok(_) = 36 | self.lock 37 | .compare_exchange_weak( 38 | false, // false이면 39 | true, // true를 쓴다 40 | Ordering::Acquire, // 성공 시의 오더 41 | Ordering::Relaxed) // 실패 시의 오더 42 | { 43 | break; 44 | } 45 | } 46 | SpinLockGuard { spin_lock: self } // ❼ 47 | } 48 | } 49 | 50 | // SpinLock 타입은 스레드 사이에서 공유 가능하도록 지정 51 | unsafe impl Sync for SpinLock {} // ❽ 52 | unsafe impl Send for SpinLock {} // ❾ 53 | 54 | // 록 획득 후에 자동으로 해제되도록 Drop 트레이트를 구현 ❿ 55 | impl<'a, T> Drop for SpinLockGuard<'a, T> { 56 | fn drop(&mut self) { 57 | self.spin_lock.lock.store(false, Ordering::Release); 58 | } 59 | } 60 | 61 | // 보호 대상 데이터의 이뮤터블한 참조 제외 ⓫ 62 | impl<'a, T> Deref for SpinLockGuard<'a, T> { 63 | type Target = T; 64 | 65 | fn deref(&self) -> &Self::Target { 66 | unsafe { &*self.spin_lock.data.get() } 67 | } 68 | } 69 | 70 | // 보호 대상 데이터의 뮤터블한 참조 제외 ⓬ 71 | impl<'a, T> DerefMut for SpinLockGuard<'a, T> { 72 | fn deref_mut(&mut self) -> &mut Self::Target { 73 | unsafe { &mut *self.spin_lock.data.get() } 74 | } 75 | } 76 | 77 | fn main() { 78 | let lock = Arc::new(SpinLock::new(0)); 79 | let mut v = Vec::new(); 80 | 81 | for _ in 0..NUM_THREADS { 82 | let lock0 = lock.clone(); 83 | // 스레드 생성 84 | let t = std::thread::spawn(move || { 85 | for _ in 0..NUM_LOOP { 86 | // 록 87 | let mut data = lock0.lock(); 88 | *data += 1; 89 | } 90 | }); 91 | v.push(t); 92 | } 93 | 94 | for t in v { 95 | t.join().unwrap(); 96 | } 97 | 98 | println!( 99 | "COUNT = {} (expected = {})", 100 | *lock.lock(), 101 | NUM_LOOP * NUM_THREADS 102 | ); 103 | } -------------------------------------------------------------------------------- /chap4/README.md: -------------------------------------------------------------------------------- 1 | # 4장 병행 프로그래밍 특유의 버그와 문제점 2 | 3 | - [4.1 데드록](./4.1/) 4 | - [4.3 은행원 알고리즘](./4.3/) 5 | - [4.4 재귀록](./4.4/) 6 | - [4.5 의사 각성](./4.5/) 7 | - [4.6 시그널](./4.6/) 8 | - [4.7 메모리 배리어](./4.7/) 9 | -------------------------------------------------------------------------------- /chap5/5.1/README.md: -------------------------------------------------------------------------------- 1 | # 5.1 병행 서버 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시 ```--release```를 설정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일과 실행 예시 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. `epoll`을 이용하는 예시는 Linux에서만 동작합니다. 10 | 11 | ```sh 12 | $ cd ch5_1_conc 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap5/5.1/ch5_1_conc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_1_conc" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | nix = "0.20.0" 11 | -------------------------------------------------------------------------------- /chap5/5.1/ch5_1_conc/src/main.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::epoll::{ 2 | epoll_create1, epoll_ctl, epoll_wait, EpollCreateFlags, EpollEvent, EpollFlags, EpollOp, 3 | }; 4 | use std::collections::HashMap; 5 | use std::io::{BufRead, BufReader, BufWriter, Write}; 6 | use std::net::TcpListener; 7 | use std::os::unix::io::{AsRawFd, RawFd}; 8 | 9 | fn main() { 10 | // epoll 플래그 단축 계열 11 | let epoll_in = EpollFlags::EPOLLIN; 12 | let epoll_add = EpollOp::EpollCtlAdd; 13 | let epoll_del = EpollOp::EpollCtlDel; 14 | 15 | // TCP 10000번 포트를 리슨 16 | let listener = TcpListener::bind("127.0.0.1:10000").unwrap(); 17 | 18 | // epoll영 객체를 생성 19 | let epfd = epoll_create1(EpollCreateFlags::empty()).unwrap(); // ❶ 20 | 21 | // 리슨용 소켓을 감시 대상에 추가 ❷ 22 | let listen_fd = listener.as_raw_fd(); 23 | let mut ev = EpollEvent::new(epoll_in, listen_fd as u64); 24 | epoll_ctl(epfd, epoll_add, listen_fd, &mut ev).unwrap(); 25 | 26 | let mut fd2buf = HashMap::new(); 27 | let mut events = vec![EpollEvent::empty(); 1024]; 28 | 29 | // epoll로 이벤트 발생을 감시 30 | while let Ok(nfds) = epoll_wait(epfd, &mut events, -1) { // ❸ 31 | for n in 0..nfds { // ❹ 32 | if events[n].data() == listen_fd as u64 { 33 | // 리슨 소켓에 이벤트 ❺ 34 | if let Ok((stream, _)) = listener.accept() { 35 | // 읽기, 쓰기 객체를 생성 36 | let fd = stream.as_raw_fd(); 37 | let stream0 = stream.try_clone().unwrap(); 38 | let reader = BufReader::new(stream0); 39 | let writer = BufWriter::new(stream); 40 | 41 | // fd와 reader, writer의 관계를 만듬 42 | fd2buf.insert(fd, (reader, writer)); 43 | 44 | println!("accept: fd = {}", fd); 45 | 46 | // fd를 감시 대상에 등록 47 | let mut ev = 48 | EpollEvent::new(epoll_in, fd as u64); 49 | epoll_ctl(epfd, epoll_add, 50 | fd, &mut ev).unwrap(); 51 | } 52 | } else { 53 | // 클라이언트에서 데이터 도착 ❻ 54 | let fd = events[n].data() as RawFd; 55 | let (reader, writer) = 56 | fd2buf.get_mut(&fd).unwrap(); 57 | 58 | // 1행 읽기 59 | let mut buf = String::new(); 60 | let n = reader.read_line(&mut buf).unwrap(); 61 | 62 | // 커넥션을 클로즈한 경우, epoll 감시 대상에서 재외함 63 | if n == 0 { 64 | let mut ev = 65 | EpollEvent::new(epoll_in, fd as u64); 66 | epoll_ctl(epfd, epoll_del, 67 | fd, &mut ev).unwrap(); 68 | fd2buf.remove(&fd); 69 | println!("closed: fd = {}", fd); 70 | continue; 71 | } 72 | 73 | print!("read: fd = {}, buf = {}", fd, buf); 74 | 75 | // 읽은 데이터를 그대로 쓴다 76 | writer.write(buf.as_bytes()).unwrap(); 77 | writer.flush().unwrap(); 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /chap5/5.1/ch5_1_iter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_1_iter" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap5/5.1/ch5_1_iter/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, BufReader, BufWriter, Write}; 2 | use std::net::TcpListener; 3 | 4 | fn main() { 5 | // TCP 10000번 포트를 리스닝 6 | let listener = TcpListener::bind("127.0.0.1:10000").unwrap(); // ❶ 7 | 8 | // 커넥션 요구를 받아들인다 9 | while let Ok((stream, _)) = listener.accept() { // ❷ 10 | // 읽기, 쓰기 객체를 생성 ❸ 11 | let stream0 = stream.try_clone().unwrap(); 12 | let mut reader = BufReader::new(stream0); 13 | let mut writer = BufWriter::new(stream); 14 | 15 | // 1행씩 읽어 같은 것을 쓴다 ❹ 16 | let mut buf = String::new(); 17 | reader.read_line(&mut buf).unwrap(); 18 | writer.write(buf.as_bytes()).unwrap(); 19 | writer.flush().unwrap(); // ❺ 20 | } 21 | } -------------------------------------------------------------------------------- /chap5/5.2/README.md: -------------------------------------------------------------------------------- 1 | # 5.2 코루틴과 스케줄링 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시 ```--release```를 설정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일과 실행 예시 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. 10 | 11 | ```sh 12 | $ cd ch5_2_1_hello 13 | $ cargo run --release 14 | ``` 15 | 16 | ## hello.py 17 | 18 | hello.py는 Python3로 다음과 같이 실행할 수 있습니다. 19 | 20 | ```sh 21 | $ python3 hello.py 22 | ``` 23 | -------------------------------------------------------------------------------- /chap5/5.2/ch5_2_1_hello/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_2_1_hello" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | futures = "0.3.13" -------------------------------------------------------------------------------- /chap5/5.2/ch5_2_1_hello/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures::future::{BoxFuture, FutureExt}; 2 | use futures::task::{waker_ref, ArcWake}; 3 | use std::future::Future; 4 | use std::pin::Pin; 5 | use std::sync::{Arc, Mutex}; 6 | use std::task::{Context, Poll}; 7 | 8 | struct Hello { // ❶ 9 | state: StateHello, 10 | } 11 | 12 | // 상태 ❷ 13 | enum StateHello { 14 | HELLO, 15 | WORLD, 16 | END, 17 | } 18 | 19 | impl Hello { 20 | fn new() -> Self { 21 | Hello { 22 | state: StateHello::HELLO, // 초기 상태 23 | } 24 | } 25 | } 26 | 27 | impl Future for Hello { 28 | type Output = (); 29 | 30 | // 실행 함수 ❸ 31 | fn poll(mut self: Pin<&mut Self>, 32 | _cx: &mut Context<'_>) -> Poll<()> { 33 | match (*self).state { 34 | StateHello::HELLO => { 35 | print!("Hello, "); 36 | // WORLD 상태로 전이 37 | (*self).state = StateHello::WORLD; 38 | Poll::Pending // 다시 호출 가능 39 | } 40 | StateHello::WORLD => { 41 | println!("World!"); 42 | // END状態に遷移 43 | (*self).state = StateHello::END; 44 | Poll::Pending // 다시 호출 가능 45 | } 46 | StateHello::END => { 47 | Poll::Ready(()) // 종료 48 | } 49 | } 50 | } 51 | } 52 | 53 | // 실행 단위 ❶ 54 | struct Task { 55 | hello: Mutex>, 56 | } 57 | 58 | impl Task { 59 | fn new() -> Self { 60 | let hello = Hello::new(); 61 | Task { 62 | hello: Mutex::new(hello.boxed()), 63 | } 64 | } 65 | } 66 | 67 | // 아무것도 하지 않음 68 | impl ArcWake for Task { 69 | fn wake_by_ref(_arc_self: &Arc) {} 70 | } 71 | 72 | fn main() { 73 | // 초기화 74 | let task = Arc::new(Task::new()); 75 | let waker = waker_ref(&task); 76 | let mut ctx = Context::from_waker(&waker); // ❷ 77 | let mut hello = task.hello.lock().unwrap(); 78 | 79 | // 정지와 재개의 반복 ❸ 80 | hello.as_mut().poll(&mut ctx); 81 | hello.as_mut().poll(&mut ctx); 82 | hello.as_mut().poll(&mut ctx); 83 | } -------------------------------------------------------------------------------- /chap5/5.2/ch5_2_2_sched/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_2_2_sched" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | futures = "0.3.13" -------------------------------------------------------------------------------- /chap5/5.2/ch5_2_2_sched/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures::future::{BoxFuture, FutureExt}; 2 | use futures::task::{waker_ref, ArcWake}; 3 | use std::future::Future; 4 | use std::pin::Pin; 5 | use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; // ❶ 6 | use std::sync::{Arc, Mutex}; 7 | use std::task::{Context, Poll}; 8 | 9 | struct Hello { // ❶ 10 | state: StateHello, 11 | } 12 | 13 | // 상태 ❷ 14 | enum StateHello { 15 | HELLO, 16 | WORLD, 17 | END, 18 | } 19 | 20 | impl Hello { 21 | fn new() -> Self { 22 | Hello { 23 | state: StateHello::HELLO, // 초기 상태 24 | } 25 | } 26 | } 27 | 28 | impl Future for Hello { 29 | type Output = (); 30 | 31 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 32 | match (*self).state { 33 | StateHello::HELLO => { 34 | print!("Hello, "); 35 | (*self).state = StateHello::WORLD; 36 | cx.waker().wake_by_ref(); // 자신을 실행 큐에 인큐 37 | return Poll::Pending; 38 | } 39 | StateHello::WORLD => { 40 | println!("World!"); 41 | (*self).state = StateHello::END; 42 | cx.waker().wake_by_ref(); // 자신을 실행 큐에 인큐 43 | return Poll::Pending; 44 | } 45 | StateHello::END => { 46 | return Poll::Ready(()); 47 | } 48 | } 49 | } 50 | } 51 | 52 | struct Task { 53 | // 실행하는 코루틴 54 | future: Mutex>, // ❶ 55 | // Executor에 스케줄링하기 위한 채널 56 | sender: SyncSender>, // ❷ 57 | } 58 | 59 | impl ArcWake for Task { 60 | fn wake_by_ref(arc_self: &Arc) { // ❸ 61 | // 자신을 스케줄링 62 | let self0 = arc_self.clone(); 63 | arc_self.sender.send(self0).unwrap(); 64 | } 65 | } 66 | 67 | struct Executor { // ❶ 68 | // 실행 큐 69 | sender: SyncSender>, 70 | receiver: Receiver>, 71 | } 72 | 73 | impl Executor { 74 | fn new() -> Self { 75 | // 채널 생성. 큐의 사이즈는 최대 1024개 76 | let (sender, receiver) = sync_channel(1024); 77 | Executor { 78 | sender: sender.clone(), 79 | receiver, 80 | } 81 | } 82 | 83 | // 새롭게 Task를 생성하기 위한 Spawner를 작성 ❷ 84 | fn get_spawner(&self) -> Spawner { 85 | Spawner { 86 | sender: self.sender.clone(), 87 | } 88 | } 89 | 90 | fn run(&self) { // ❸ 91 | // 채널에서 Task를 수신하고 순서대로 실행 92 | while let Ok(task) = self.receiver.recv() { 93 | // 컨텍스트를 생성 94 | let mut future = task.future.lock().unwrap(); 95 | let waker = waker_ref(&task); 96 | let mut ctx = Context::from_waker(&waker); 97 | // poll을 호출해서 실행 98 | let _ = future.as_mut().poll(&mut ctx); 99 | } 100 | } 101 | } 102 | 103 | struct Spawner { // ❶ 104 | sender: SyncSender>, 105 | } 106 | 107 | impl Spawner { 108 | fn spawn(&self, future: impl Future + 'static + Send) { // ❷ 109 | let future = future.boxed(); // Future를 Box화 110 | let task = Arc::new(Task { // Task 생성 111 | future: Mutex::new(future), 112 | sender: self.sender.clone(), 113 | }); 114 | 115 | // 실행 큐에 인큐 116 | self.sender.send(task).unwrap(); 117 | } 118 | } 119 | 120 | fn main() { 121 | let executor = Executor::new(); 122 | executor.get_spawner().spawn(Hello::new()); 123 | executor.run(); 124 | } 125 | 126 | // 5.3.1에서 나타낸 것처럼, 다음처럼 실행도 가능 127 | // fn main() { 128 | // let executor = Executor::new(); 129 | // // async로 Future 트레이트를 구현한 타입의 값으로 변환 130 | // executor.get_spawner().spawn(async { 131 | // let h = Hello::new(); 132 | // h.await; // poll을 호출해서 실행 133 | // }); 134 | // executor.run(); 135 | // } -------------------------------------------------------------------------------- /chap5/5.2/hello.py: -------------------------------------------------------------------------------- 1 | def hello(): 2 | print('Hello,', end='') 3 | yield # 여기에서 중단, 재개 ❶ 4 | print('World!') 5 | yield # 여기까지 실행 ❷ 6 | 7 | h = hello() # 이터레이터를 생성 8 | h.__next__() # 1까지 실행하고 중단 9 | h.__next__() # 1에서 재개하고 2까지 실행 10 | -------------------------------------------------------------------------------- /chap5/5.3/README.md: -------------------------------------------------------------------------------- 1 | # 5.3 async/await 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시 ```--release```를 설정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일과 실행 예시 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. `epoll`을 이용하므로 Linux만 대상이 됩니다. 10 | 11 | ```sh 12 | $ cd ch5_3_2_ioselect 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap5/5.3/ch5_3_2_ioselect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_3_2_ioselect" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | futures = "0.3.13" 11 | nix = "0.20.0" 12 | -------------------------------------------------------------------------------- /chap5/5.3/ch5_3_2_ioselect/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures::{ 2 | future::{BoxFuture, FutureExt}, 3 | task::{waker_ref, ArcWake}, 4 | }; 5 | use nix::{ 6 | errno::Errno, 7 | sys::{ 8 | epoll::{ 9 | epoll_create1, epoll_ctl, epoll_wait, 10 | EpollCreateFlags, EpollEvent, EpollFlags, EpollOp, 11 | }, 12 | eventfd::{eventfd, EfdFlags}, // eventfd용 임포트 ❶ 13 | }, 14 | unistd::write, 15 | }; 16 | use std::{ 17 | collections::{HashMap, VecDeque}, 18 | future::Future, 19 | io::{BufRead, BufReader, BufWriter, Write}, 20 | net::{SocketAddr, TcpListener, TcpStream}, 21 | os::unix::io::{AsRawFd, RawFd}, 22 | pin::Pin, 23 | sync::{ 24 | mpsc::{sync_channel, Receiver, SyncSender}, 25 | Arc, Mutex, 26 | }, 27 | task::{Context, Poll, Waker}, 28 | }; 29 | 30 | fn write_eventfd(fd: RawFd, n: usize) { 31 | // usize를 *const u8로 변환 32 | let ptr = &n as *const usize as *const u8; 33 | let val = unsafe { 34 | std::slice::from_raw_parts( 35 | ptr, std::mem::size_of_val(&n)) 36 | }; 37 | // write 시스템콜 호출 38 | write(fd, &val).unwrap(); 39 | } 40 | 41 | enum IOOps { 42 | ADD(EpollFlags, RawFd, Waker), // epoll에 추가 43 | REMOVE(RawFd), // epoll에서 삭제 44 | } 45 | 46 | struct IOSelector { 47 | wakers: Mutex>, // fd에서 waker 48 | queue: Mutex>, // IO 큐 49 | epfd: RawFd, // epoll의 fd 50 | event: RawFd, // eventfd의 fd 51 | } 52 | 53 | impl IOSelector { 54 | fn new() -> Arc { // ❶ 55 | let s = IOSelector { 56 | wakers: Mutex::new(HashMap::new()), 57 | queue: Mutex::new(VecDeque::new()), 58 | epfd: epoll_create1(EpollCreateFlags::empty()).unwrap(), 59 | // eventfd 생성 60 | event: eventfd(0, EfdFlags::empty()).unwrap(), // ❷ 61 | }; 62 | let result = Arc::new(s); 63 | let s = result.clone(); 64 | 65 | // epoll용 스레드 생성 ❸ 66 | std::thread::spawn(move || s.select()); 67 | 68 | result 69 | } 70 | 71 | // epoll로 감시하기 위한 함수 ❹ 72 | fn add_event( 73 | &self, 74 | flag: EpollFlags, // epoll 플래그 75 | fd: RawFd, // 감시 대상 파일 디스크립터 76 | waker: Waker, 77 | wakers: &mut HashMap, 78 | ) { 79 | // 각 정의의 숏컷 80 | let epoll_add = EpollOp::EpollCtlAdd; 81 | let epoll_mod = EpollOp::EpollCtlMod; 82 | let epoll_one = EpollFlags::EPOLLONESHOT; 83 | 84 | // EPOLLONESHOT을 지정해, 일단 이벤트가 발생하면 85 | // 그 fd로의 이벤트는 재설정할 떄까지 알림이 발생하지 않게 된다 ❺ 86 | let mut ev = 87 | EpollEvent::new(flag | epoll_one, fd as u64); 88 | 89 | // 감시 대상에 추가 90 | if let Err(err) = epoll_ctl(self.epfd, epoll_add, fd, 91 | &mut ev) { 92 | match err { 93 | nix::Error::Sys(Errno::EEXIST) => { 94 | // 이미 추가되어있는 경우에는 재설정❻ 95 | epoll_ctl(self.epfd, epoll_mod, fd, 96 | &mut ev).unwrap(); 97 | } 98 | _ => { 99 | panic!("epoll_ctl: {}", err); 100 | } 101 | } 102 | } 103 | 104 | assert!(!wakers.contains_key(&fd)); 105 | wakers.insert(fd, waker); // ❼ 106 | } 107 | 108 | // epoll의 감시해서 삭제하기 위한 함수 ❽ 109 | fn rm_event(&self, fd: RawFd, wakers: &mut HashMap) { 110 | let epoll_del = EpollOp::EpollCtlDel; 111 | let mut ev = EpollEvent::new(EpollFlags::empty(), 112 | fd as u64); 113 | epoll_ctl(self.epfd, epoll_del, fd, &mut ev).ok(); 114 | wakers.remove(&fd); 115 | } 116 | 117 | fn select(&self) { // ❾ 118 | // 각 장의의 숏컷 119 | let epoll_in = EpollFlags::EPOLLIN; 120 | let epoll_add = EpollOp::EpollCtlAdd; 121 | 122 | // eventfd를 epoll의 감시 대상에 추가 ❿ 123 | let mut ev = EpollEvent::new(epoll_in, 124 | self.event as u64); 125 | epoll_ctl(self.epfd, epoll_add, self.event, 126 | &mut ev).unwrap(); 127 | 128 | let mut events = vec![EpollEvent::empty(); 1024]; 129 | // event 발생을 감시 130 | while let Ok(nfds) = epoll_wait(self.epfd, // ⓫ 131 | &mut events, -1) { 132 | let mut t = self.wakers.lock().unwrap(); 133 | for n in 0..nfds { 134 | if events[n].data() == self.event as u64 { 135 | // eventfd의 경우, 추가 및 삭제 요구를 처리 ⓬ 136 | let mut q = self.queue.lock().unwrap(); 137 | while let Some(op) = q.pop_front() { 138 | match op { 139 | // 추가 140 | IOOps::ADD(flag, fd, waker) => 141 | self.add_event(flag, fd, waker, 142 | &mut t), 143 | // 삭제 144 | IOOps::REMOVE(fd) => 145 | self.rm_event(fd, &mut t), 146 | } 147 | } 148 | } else { 149 | // 실향 큐에 추가 ⓭ 150 | let data = events[n].data() as i32; 151 | let waker = t.remove(&data).unwrap(); 152 | waker.wake_by_ref(); 153 | } 154 | } 155 | } 156 | } 157 | 158 | // 파일 디스크립터 등록용 함수 ⓮ 159 | fn register(&self, flags: EpollFlags, fd: RawFd, waker: Waker) { 160 | let mut q = self.queue.lock().unwrap(); 161 | q.push_back(IOOps::ADD(flags, fd, waker)); 162 | write_eventfd(self.event, 1); 163 | } 164 | 165 | // 파일 디스크립터 삭제용 함수 ⓯ 166 | fn unregister(&self, fd: RawFd) { 167 | let mut q = self.queue.lock().unwrap(); 168 | q.push_back(IOOps::REMOVE(fd)); 169 | write_eventfd(self.event, 1); 170 | } 171 | } 172 | 173 | struct AsyncListener { // ❶ 174 | listener: TcpListener, 175 | selector: Arc, 176 | } 177 | 178 | impl AsyncListener { 179 | // TcpListener의 초기화 처리를 감싼 함수 ❷ 180 | fn listen(addr: &str, selector: Arc) -> AsyncListener { 181 | // 리슨 주소를 지정 182 | let listener = TcpListener::bind(addr).unwrap(); 183 | 184 | // 논블로킹으로 지정 185 | listener.set_nonblocking(true).unwrap(); 186 | 187 | AsyncListener { 188 | listener: listener, 189 | selector: selector, 190 | } 191 | } 192 | 193 | // 커넥션을 억셉트하기 위한 Future를 리턴 ❸ 194 | fn accept(&self) -> Accept { 195 | Accept { listener: self } 196 | } 197 | } 198 | 199 | impl Drop for AsyncListener { 200 | fn drop(&mut self) { // ❹ 201 | self.selector.unregister(self.listener.as_raw_fd()); 202 | } 203 | } 204 | 205 | struct Accept<'a> { 206 | listener: &'a AsyncListener, 207 | } 208 | 209 | impl<'a> Future for Accept<'a> { 210 | // 반환값 타입 211 | type Output = (AsyncReader, // 비동기 읽기 스트림 212 | BufWriter, // 쓰기 스트림 213 | SocketAddr); // 주소 214 | 215 | fn poll(self: Pin<&mut Self>, 216 | cx: &mut Context<'_>) -> Poll { 217 | // 억셉트를 논블로킹으로 실행 218 | match self.listener.listener.accept() { // ❶ 219 | Ok((stream, addr)) => { 220 | // 억셉트한 경우는 221 | // 읽기와 쓰기용 객체 및 주소를 리턴 ❷ 222 | let stream0 = stream.try_clone().unwrap(); 223 | Poll::Ready(( 224 | AsyncReader::new(stream0, self.listener.selector.clone()), 225 | BufWriter::new(stream), 226 | addr, 227 | )) 228 | } 229 | Err(err) => { 230 | // 억세스할 커넥션이 없는 경우는 epoll에 등록 ❸ 231 | if err.kind() == std::io::ErrorKind::WouldBlock { 232 | self.listener.selector.register( 233 | EpollFlags::EPOLLIN, 234 | self.listener.listener.as_raw_fd(), 235 | cx.waker().clone(), 236 | ); 237 | Poll::Pending 238 | } else { 239 | panic!("accept: {}", err); 240 | } 241 | } 242 | } 243 | } 244 | } 245 | 246 | struct AsyncReader { 247 | fd: RawFd, 248 | reader: BufReader, 249 | selector: Arc, 250 | } 251 | 252 | impl AsyncReader { 253 | fn new(stream: TcpStream, 254 | selector: Arc) -> AsyncReader { 255 | // 논블로킹으로 설정 256 | stream.set_nonblocking(true).unwrap(); 257 | AsyncReader { 258 | fd: stream.as_raw_fd(), 259 | reader: BufReader::new(stream), 260 | selector: selector, 261 | } 262 | } 263 | 264 | // 1행 읽기만 하므로 Future를 리턴 265 | fn read_line(&mut self) -> ReadLine { 266 | ReadLine { reader: self } 267 | } 268 | } 269 | 270 | impl Drop for AsyncReader { 271 | fn drop(&mut self) { 272 | self.selector.unregister(self.fd); 273 | } 274 | } 275 | 276 | struct ReadLine<'a> { 277 | reader: &'a mut AsyncReader, 278 | } 279 | 280 | impl<'a> Future for ReadLine<'a> { 281 | // 반환값의 타입 282 | type Output = Option; 283 | 284 | fn poll(mut self: Pin<&mut Self>, 285 | cx: &mut Context<'_>) -> Poll { 286 | let mut line = String::new(); 287 | // 비동기 읽기 288 | match self.reader.reader.read_line(&mut line) { // ❶ 289 | Ok(0) => Poll::Ready(None), // 커넥션 유실 290 | Ok(_) => Poll::Ready(Some(line)), // 1행 읽기 성공 291 | Err(err) => { 292 | // 읽을 수 없는 경우는 epoll에 등록 ❷ 293 | if err.kind() == std::io::ErrorKind::WouldBlock { 294 | self.reader.selector.register( 295 | EpollFlags::EPOLLIN, 296 | self.reader.fd, 297 | cx.waker().clone(), 298 | ); 299 | Poll::Pending 300 | } else { 301 | Poll::Ready(None) 302 | } 303 | } 304 | } 305 | } 306 | } 307 | 308 | struct Task { 309 | // 실행할 코루틴 310 | future: Mutex>, // ❶ 311 | // Executor로 스케줄링 하기 위한 채널 312 | sender: SyncSender>, // ❷ 313 | } 314 | 315 | impl ArcWake for Task { 316 | fn wake_by_ref(arc_self: &Arc) { // ❸ 317 | // 자신을 스케줄링 318 | let self0 = arc_self.clone(); 319 | arc_self.sender.send(self0).unwrap(); 320 | } 321 | } 322 | 323 | struct Executor { // ❶ 324 | // 실행 큐 325 | sender: SyncSender>, 326 | receiver: Receiver>, 327 | } 328 | 329 | impl Executor { 330 | fn new() -> Self { 331 | // 채널 생성. 큐의 크기는 최대 1024개 332 | let (sender, receiver) = sync_channel(1024); 333 | Executor { 334 | sender: sender.clone(), 335 | receiver, 336 | } 337 | } 338 | 339 | // 새롭게 Task를 생성하기 위한 Spawner를 작성 ❷ 340 | fn get_spawner(&self) -> Spawner { 341 | Spawner { 342 | sender: self.sender.clone(), 343 | } 344 | } 345 | 346 | fn run(&self) { // ❸ 347 | // 채널에서 Task를 수신해 순서대로 실행 348 | while let Ok(task) = self.receiver.recv() { 349 | // 컨텍스트를 생성 350 | let mut future = task.future.lock().unwrap(); 351 | let waker = waker_ref(&task); 352 | let mut ctx = Context::from_waker(&waker); 353 | // poll을 호출하고 실행 354 | let _ = future.as_mut().poll(&mut ctx); 355 | } 356 | } 357 | } 358 | 359 | struct Spawner { // ❶ 360 | sender: SyncSender>, 361 | } 362 | 363 | impl Spawner { 364 | fn spawn(&self, future: impl Future + 'static + Send) { // ❷ 365 | let future = future.boxed(); // Future를 Box화 366 | let task = Arc::new(Task { // Task 생성 367 | future: Mutex::new(future), 368 | sender: self.sender.clone(), 369 | }); 370 | 371 | // 실행 큐에 인큐 372 | self.sender.send(task).unwrap(); 373 | } 374 | } 375 | 376 | fn main() { 377 | let executor = Executor::new(); 378 | let selector = IOSelector::new(); 379 | let spawner = executor.get_spawner(); 380 | 381 | let server = async move { // ❶ 382 | // 비동기 억셉트용 리스너 생성 ❷ 383 | let listener = AsyncListener::listen("127.0.0.1:10000", 384 | selector.clone()); 385 | loop { 386 | // 비동기 커넥션 억셉트 ❸ 387 | let (mut reader, mut writer, addr) = 388 | listener.accept().await; 389 | println!("accept: {}", addr); 390 | 391 | // 커넥션 별로 태스크 생성 ❹ 392 | spawner.spawn(async move { 393 | // 1헹 비동기 읽기 ❺ 394 | while let Some(buf) = reader.read_line().await { 395 | print!("read: {}, {}", addr, buf); 396 | writer.write(buf.as_bytes()).unwrap(); 397 | writer.flush().unwrap(); 398 | } 399 | println!("close: {}", addr); 400 | }); 401 | } 402 | }; 403 | 404 | // 태스크를 생성하고 실행 405 | executor.get_spawner().spawn(server); 406 | executor.run(); 407 | } -------------------------------------------------------------------------------- /chap5/5.4/README.md: -------------------------------------------------------------------------------- 1 | # 5.4 비동기 라이브러리 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행합니다. 4 | 5 | 빌드 시 ```--release```를 설정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일과 실행 예시 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. 10 | 11 | ```sh 12 | $ cd ch5_4_tokio 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_block/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_4_block" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "1.4.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_block/src/main.rs: -------------------------------------------------------------------------------- 1 | // 블로킹 함수 2 | fn do_block(n: u64) -> u64 { 3 | let ten_secs = std::time::Duration::from_secs(10); 4 | std::thread::sleep(ten_secs); 5 | n 6 | } 7 | 8 | // async 함수 9 | async fn do_print() { 10 | let sec = std::time::Duration::from_secs(1); 11 | for _ in 0..20 { 12 | tokio::time::sleep(sec).await; 13 | println!("wake up"); 14 | } 15 | } 16 | 17 | #[tokio::main] 18 | pub async fn main() { 19 | // 블로킹 함수 호출 20 | let mut v = Vec::new(); 21 | for n in 0..32 { 22 | let t = tokio::task::spawn_blocking(move || do_block(n)); // ❶ 23 | v.push(t); 24 | } 25 | 26 | // async 함수 호출. 27 | let p = tokio::spawn(do_print()); // ❷ 28 | 29 | for t in v { 30 | let n = t.await.unwrap(); 31 | println!("finished: {}", n); 32 | } 33 | 34 | p.await.unwrap() 35 | } -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_mutex_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_4_mutex_1" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "1.4.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_mutex_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | const NUM_TASKS: usize = 4; // 태스크 수 4 | const NUM_LOOP: usize = 100000; // 루프 수 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), tokio::task::JoinError> { 8 | let val = Arc::new(Mutex::new(0)); // 공유 변수 ❶ 9 | let mut v = Vec::new(); 10 | for _ in 0..NUM_TASKS { 11 | let n = val.clone(); 12 | let t = tokio::spawn(async move { // 태스크 생성 ❷ 13 | for _ in 0..NUM_LOOP { 14 | let mut n0 = n.lock().unwrap(); 15 | *n0 += 1; // 인크리먼트 ❸ 16 | } 17 | }); 18 | 19 | v.push(t); 20 | } 21 | 22 | for i in v { 23 | i.await?; 24 | } 25 | 26 | println!("COUNT = {} (expected = {})", 27 | *val.lock().unwrap(), NUM_LOOP * NUM_TASKS); 28 | Ok(()) 29 | } -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_mutex_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_4_mutex_2" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "1.4.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_mutex_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time}; 2 | use tokio::sync::Mutex; 3 | 4 | const NUM_TASKS: usize = 8; 5 | 6 | // 록만 하는 태스크 ❶ 7 | async fn lock_only(v: Arc>) { 8 | let mut n = v.lock().await; 9 | *n += 1; 10 | } 11 | 12 | // 록 상태에서 await를 수행하는 태스크 ❷ 13 | async fn lock_sleep(v: Arc>) { 14 | let mut n = v.lock().await; 15 | let ten_secs = time::Duration::from_secs(10); 16 | tokio::time::sleep(ten_secs).await; // ❸ 17 | *n += 1; 18 | } 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<(), tokio::task::JoinError> { 22 | let val = Arc::new(Mutex::new(0)); 23 | let mut v = Vec::new(); 24 | 25 | // lock_sleep 태스크 생성 26 | let t = tokio::spawn(lock_sleep(val.clone())); 27 | v.push(t); 28 | 29 | for _ in 0..NUM_TASKS { 30 | let n = val.clone(); 31 | let t = tokio::spawn(lock_only(n)); // lock_only 태스크 생성 32 | v.push(t); 33 | } 34 | 35 | for i in v { 36 | i.await?; 37 | } 38 | Ok(()) 39 | } -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_oneshot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_4_oneshot" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "1.4.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_oneshot/src/main.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::oneshot; // ❶ 2 | 3 | // 미래 언젠가의 시점에서 값이 결정되는 함수 ❷ 4 | async fn set_val_later(tx: oneshot::Sender) { 5 | let ten_secs = std::time::Duration::from_secs(10); 6 | tokio::time::sleep(ten_secs).await; 7 | if let Err(_) = tx.send(100) { // ❸ 8 | println!("failed to send"); 9 | } 10 | } 11 | 12 | #[tokio::main] 13 | pub async fn main() { 14 | let (tx, rx) = oneshot::channel(); // ❹ 15 | 16 | tokio::spawn(set_val_later(tx)); // ❺ 17 | 18 | match rx.await { // 값 읽기 ❻ 19 | Ok(n) => { 20 | println!("n = {}", n); 21 | } 22 | Err(e) => { 23 | println!("failed to receive: {}", e); 24 | return; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_sleep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_4_sleep" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "1.4.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_sleep/src/main.rs: -------------------------------------------------------------------------------- 1 | // 블록되는 슬립 sleep 2 | // use std::{thread, time}; // ❶ 3 | // 4 | // #[tokio::main] 5 | // async fn main() { 6 | // // join으로 종료 대기 7 | // tokio::join!(async move { // ❷ 8 | // // 10초 슬립 ❸ 9 | // let ten_secs = time::Duration::from_secs(10); 10 | // thread::sleep(ten_secs); 11 | // }); 12 | // } 13 | 14 | // Tokio 함수를 가진 슬립 15 | use std::time; 16 | 17 | #[tokio::main] 18 | async fn main() { 19 | // join으로 종료 대기 20 | tokio::join!(async move { 21 | // 10초 슬립 22 | let ten_secs = time::Duration::from_secs(10); 23 | tokio::time::sleep(ten_secs).await; // ❶ 24 | }); 25 | } -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch5_4_tokio" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "1.4.0", features = ["full"] } -------------------------------------------------------------------------------- /chap5/5.4/ch5_4_tokio/src/main.rs: -------------------------------------------------------------------------------- 1 | use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; // ❶ 2 | use tokio::io; 3 | use tokio::net::TcpListener; // ❷ 4 | 5 | #[tokio::main] // ❸ 6 | async fn main() -> io::Result<()> { 7 | // 10000번 포트에서 TCP 리슨 ❹ 8 | let listener = TcpListener::bind("127.0.0.1:10000").await.unwrap(); 9 | 10 | loop { 11 | // TCP 커넥트 억셉트 ❺ 12 | let (mut socket, addr) = listener.accept().await?; 13 | println!("accept: {}", addr); 14 | 15 | // 비동기 태스크 생성 ❻ 16 | tokio::spawn(async move { 17 | // 버퍼 읽기 쓰기용 객체 생성 ❼ 18 | let (r, w) = socket.split(); // ❽ 19 | let mut reader = io::BufReader::new(r); 20 | let mut writer = io::BufWriter::new(w); 21 | 22 | let mut line = String::new(); 23 | loop { 24 | line.clear(); // ❾ 25 | match reader.read_line(&mut line).await { // ❿ 26 | Ok(0) => { // 커넥션 로스 27 | println!("closed: {}", addr); 28 | return; 29 | } 30 | Ok(_) => { 31 | print!("read: {}, {}", addr, line); 32 | writer.write_all(line.as_bytes()).await.unwrap(); 33 | writer.flush().await.unwrap(); 34 | } 35 | Err(e) => { // 에러ー 36 | println!("error: {}, {}", addr, e); 37 | return; 38 | } 39 | } 40 | } 41 | }); 42 | } 43 | } -------------------------------------------------------------------------------- /chap5/README.md: -------------------------------------------------------------------------------- 1 | # 5 비동기 프로그래밍 2 | 3 | - [5.1 병행 서버](./5.1/) 4 | - [5.2 코루틴 스케줄링](./5.2/) 5 | - [5.3 async/await](./5.3/) 6 | - [5.4 비동기 라이브러리](./5.4/) 7 | -------------------------------------------------------------------------------- /chap6/README.md: -------------------------------------------------------------------------------- 1 | # 6장 멀티태스크 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 `--release`를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예시 8 | 9 | 다음과 같이 디렉터리에 이동 후 실행합니다. AArch64 환경에서만 실행하기 바랍니다. 10 | 11 | ```sh 12 | $ cd ch6_mult 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap6/ch6_mult/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch6_mult" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | nix = "0.20.0" 11 | rand = "0.8.3" 12 | -------------------------------------------------------------------------------- /chap6/ch6_mult/asm/context.S: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ // Mac의 경우에는 함수명 처음에 언더스코어가 필요 2 | #define SET_CONTEXT _set_context 3 | #define SWITCH_CONTEXT _switch_context 4 | #else 5 | #define SET_CONTEXT set_context 6 | #define SWITCH_CONTEXT switch_context 7 | #endif 8 | 9 | .global SET_CONTEXT // ❶ 10 | .global SWITCH_CONTEXT 11 | 12 | SET_CONTEXT: // ❷ 13 | // callee 저장 레지스터를 저장 14 | stp d8, d9, [x0] // ❸ 15 | stp d10, d11, [x0, #16] // ❹ 16 | stp d12, d13, [x0, #16 * 2] 17 | stp d14, d15, [x0, #16 * 3] 18 | stp x19, x20, [x0, #16 * 4] 19 | stp x21, x22, [x0, #16 * 5] 20 | stp x23, x24, [x0, #16 * 6] 21 | stp x25, x26, [x0, #16 * 7] 22 | stp x27, x28, [x0, #16 * 8] 23 | 24 | // 스택 포인터와 링크 레지스터를 저장 25 | mov x1, sp 26 | stp x30, x1, [x0, #16 * 9] 27 | 28 | // return 0 ❺ 29 | mov x0, 0 30 | ret 31 | 32 | SWITCH_CONTEXT: // ❻ 33 | // callee 저장 레지스터를 복원 34 | ldp d8, d9, [x0] // ❼ 35 | ldp d10, d11, [x0, #16] // ❽ 36 | ldp d12, d13, [x0, #16 * 2] 37 | ldp d14, d15, [x0, #16 * 3] 38 | ldp x19, x20, [x0, #16 * 4] 39 | ldp x21, x22, [x0, #16 * 5] 40 | ldp x23, x24, [x0, #16 * 6] 41 | ldp x25, x26, [x0, #16 * 7] 42 | ldp x27, x28, [x0, #16 * 8] 43 | 44 | // 스택 포인터와 링크 레지스터를 복원 45 | ldp x30, x2, [x0, #16 * 9] 46 | mov sp, x2 47 | 48 | // return 1 ❾ 49 | mov x0, 1 50 | ret -------------------------------------------------------------------------------- /chap6/ch6_mult/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | const ASM_FILE: &str = "asm/context.S"; 4 | const O_FILE: &str = "asm/context.o"; 5 | const LIB_FILE: &str = "asm/libcontext.a"; 6 | 7 | fn main() { 8 | Command::new("cc").args(&[ASM_FILE, "-c", "-fPIC", "-o"]) 9 | .arg(O_FILE) 10 | .status().unwrap(); 11 | Command::new("ar").args(&["crus", LIB_FILE, O_FILE]) 12 | .status().unwrap(); 13 | 14 | println!("cargo:rustc-link-search=native={}", "asm"); // asm을 라이브러리 검색 경로에 추가 15 | println!("cargo:rustc-link-lib=static=context"); // libcontext.a라는 정적 라이브러리 링크 16 | println!("cargo:rerun-if-changed=asm/context.S"); // asm/context.S라는 파일에 의존 17 | } -------------------------------------------------------------------------------- /chap6/ch6_mult/src/green.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::mman::{mprotect, ProtFlags}; 2 | use rand; 3 | use std::alloc::{alloc, dealloc, Layout}; 4 | use std::collections::{HashMap, HashSet, LinkedList}; 5 | use std::ffi::c_void; 6 | use std::ptr; 7 | 8 | // 모든 스레드 종료 시 돌아올 위치 ❶ 9 | static mut CTX_MAIN: Option> = None; 10 | 11 | // 불필요한 스택 영역 ❷ 12 | static mut UNUSED_STACK: (*mut u8, Layout) = (ptr::null_mut(), Layout::new::()); 13 | 14 | // 스레드 실행 큐 ❸ 15 | static mut CONTEXTS: LinkedList> = LinkedList::new(); 16 | 17 | // 스레드 ID 집합 ❹ 18 | static mut ID: *mut HashSet = ptr::null_mut(); 19 | 20 | // 메시지 큐 ❶ 21 | static mut MESSAGES: *mut MappedList = ptr::null_mut(); 22 | 23 | // 대기 스레드 집합❷ 24 | static mut WAITING: *mut HashMap> = ptr::null_mut(); 25 | 26 | #[repr(C)] // ❶ 27 | struct Registers { // ❷ 28 | // callee 저장 레지스터 29 | d8: u64, d9: u64, d10: u64, d11: u64, d12: u64, 30 | d13: u64, d14: u64, d15: u64, x19: u64, x20: u64, 31 | x21: u64, x22: u64, x23: u64, x24: u64, x25: u64, 32 | x26: u64, x27: u64, x28: u64, 33 | 34 | x30: u64, // 링크 레지스터 35 | sp: u64, // 스택 레지스터 36 | } 37 | 38 | impl Registers { 39 | fn new(sp: u64) -> Self { // ❸ 40 | Registers { 41 | d8: 0, d9: 0, d10: 0, d11: 0, d12: 0, 42 | d13: 0, d14: 0, d15: 0, x19: 0, x20: 0, 43 | x21: 0, x22: 0, x23: 0, x24: 0, x25: 0, 44 | x26: 0, x27: 0, x28: 0, 45 | x30: entry_point as u64, // ❹ 46 | sp, 47 | } 48 | } 49 | } 50 | 51 | extern "C" { 52 | fn set_context(ctx: *mut Registers) -> u64; 53 | fn switch_context(ctx: *const Registers) -> !; 54 | } 55 | 56 | // 스레드 개시 시 실행하는 함수 타입 57 | type Entry = fn(); // ❶ 58 | 59 | // 페이지 크기. Linux에서는 4KiB 60 | const PAGE_SIZE: usize = 4 * 1024; // 4KiB ❷ 61 | 62 | struct MappedList { // ❶ 63 | map: HashMap>, 64 | } 65 | 66 | impl MappedList { 67 | fn new() -> Self { 68 | MappedList { 69 | map: HashMap::new(), 70 | } 71 | } 72 | 73 | // key에 대응하는 리스트의 가장 마지막에 추가 ❷ 74 | fn push_back(&mut self, key: u64, val: T) { 75 | if let Some(list) = self.map.get_mut(&key) { 76 | // 대응하는 리스트가 존재하면 추가 77 | list.push_back(val); 78 | } else { 79 | // 존재하지 않는 경우, 새롭게 리스트를 작성하고 추가 80 | let mut list = LinkedList::new(); 81 | list.push_back(val); 82 | self.map.insert(key, list); 83 | } 84 | } 85 | 86 | // key에 대응하는 리스트의 가장 처음부터 꺼낸다 ❸ 87 | fn pop_front(&mut self, key: u64) -> Option { 88 | if let Some(list) = self.map.get_mut(&key) { 89 | let val = list.pop_front(); 90 | if list.len() == 0 { 91 | self.map.remove(&key); 92 | } 93 | val 94 | } else { 95 | None 96 | } 97 | } 98 | 99 | fn clear(&mut self) { 100 | self.map.clear(); 101 | } 102 | } 103 | 104 | // 컨텍스트 ❸ 105 | struct Context { 106 | regs: Registers, // 레지스터 107 | stack: *mut u8, // 스택 108 | stack_layout: Layout, // 스택 레이아웃 109 | entry: Entry, // 엔트리포인트 110 | id: u64, // 스레드 ID 111 | } 112 | 113 | impl Context { 114 | // 레지스터 정보로의 포인터 취득 115 | fn get_regs_mut(&mut self) -> *mut Registers { 116 | &mut self.regs as *mut Registers 117 | } 118 | 119 | fn get_regs(&self) -> *const Registers { 120 | &self.regs as *const Registers 121 | } 122 | 123 | fn new(func: Entry, stack_size: usize, id: u64) -> Self { // ❹ 124 | // 스택 영역 확보 ❺ 125 | let layout = Layout::from_size_align(stack_size, PAGE_SIZE).unwrap(); 126 | let stack = unsafe { alloc(layout) }; 127 | 128 | // 가드 페이지 설정 ❻ 129 | unsafe { mprotect(stack as *mut c_void, PAGE_SIZE, ProtFlags::PROT_NONE).unwrap() }; 130 | 131 | // 레지스터 초기화 ❼ 132 | let regs = Registers::new(stack as u64 + stack_size as u64); 133 | 134 | // 컨텍스트 초기화 135 | Context { 136 | regs: regs, 137 | stack: stack, 138 | stack_layout: layout, 139 | entry: func, 140 | id: id, 141 | } 142 | } 143 | } 144 | 145 | fn get_id() -> u64 { 146 | loop { 147 | let rnd = rand::random::(); // ❶ 148 | unsafe { 149 | if !(*ID).contains(&rnd) { // ❷ 150 | (*ID).insert(rnd); // ❸ 151 | return rnd; 152 | }; 153 | } 154 | } 155 | } 156 | 157 | pub fn spawn(func: Entry, stack_size: usize) -> u64 { // ❶ 158 | unsafe { 159 | let id = get_id(); // ❷ 160 | CONTEXTS.push_back(Box::new(Context::new(func, stack_size, id))); // ❸ 161 | schedule(); // ❹ 162 | id // ❺ 163 | } 164 | } 165 | 166 | pub fn schedule() { 167 | unsafe { 168 | // 실행 가능한 프로세스가 자신뿐이므로 즉시 리턴 ❶ 169 | if CONTEXTS.len() == 1 { 170 | return; 171 | } 172 | 173 | // 자신의 컨텍스트를 실행 큐의 맨 끝으로 이동 174 | let mut ctx = CONTEXTS.pop_front().unwrap(); // ❷ 175 | // レジスタ保存領域へのポインタを取得 ❸ 176 | let regs = ctx.get_regs_mut(); 177 | CONTEXTS.push_back(ctx); 178 | 179 | // 레지스터를 보존 ❹ 180 | if set_context(regs) == 0 { 181 | // 다음 스레드로 컨텍스트 스위칭 182 | let next = CONTEXTS.front().unwrap(); 183 | switch_context((**next).get_regs()); 184 | } 185 | 186 | // 불필요한 스택 영역을 삭제 187 | rm_unused_stack(); // ❺ 188 | } 189 | } 190 | 191 | extern "C" fn entry_point() { 192 | unsafe { 193 | // 지정된 엔트리 함수를 실행 ❶ 194 | let ctx = CONTEXTS.front().unwrap(); 195 | ((**ctx).entry)(); 196 | 197 | // 아래는 스레드 종료 시 후처리 198 | 199 | // 자신의 컨텍스트를 제거 200 | let ctx = CONTEXTS.pop_front().unwrap(); 201 | 202 | // 스레드 ID를 삭제 203 | (*ID).remove(&ctx.id); 204 | 205 | // 불필요한 스택 영역으로서 보존 206 | // 이 단계에서 해제하면 다음 코드에서 스택을 사용할 수 없게 됨 207 | UNUSED_STACK = ((*ctx).stack, (*ctx).stack_layout); // ❷ 208 | 209 | match CONTEXTS.front() { // ❸ 210 | Some(c) => { 211 | // 다음 스레드로 컨텍스트 스위칭 212 | switch_context((**c).get_regs()); 213 | } 214 | None => { 215 | // 모든 스레드가 종료되었다면 main 함수의 스레드로 돌아감 216 | if let Some(c) = &CTX_MAIN { 217 | switch_context(&**c as *const Registers); 218 | } 219 | } 220 | }; 221 | } 222 | panic!("entry_point"); // ❹ 223 | } 224 | 225 | pub fn spawn_from_main(func: Entry, stack_size: usize) { 226 | unsafe { 227 | // 이미 초기화를 했다면 에러가 된다 228 | if let Some(_) = &CTX_MAIN { 229 | panic!("spawn_from_main is called twice"); 230 | } 231 | 232 | // main 함수용 컨텍스트를 생성 233 | CTX_MAIN = Some(Box::new(Registers::new(0))); 234 | if let Some(ctx) = &mut CTX_MAIN { 235 | // 글로벌 변수를 초기화 ❶ 236 | let mut msgs = MappedList::new(); 237 | MESSAGES = &mut msgs as *mut MappedList; 238 | 239 | let mut waiting = HashMap::new(); 240 | WAITING = &mut waiting as *mut HashMap>; 241 | 242 | let mut ids = HashSet::new(); 243 | ID = &mut ids as *mut HashSet; 244 | 245 | // 모든 스레드 종료 시 돌아갈 위치를 저장 ❷ 246 | if set_context(&mut **ctx as *mut Registers) == 0 { 247 | // 최초에 실행하는 스레드의 컨텍스트를 생성 ❸ 248 | CONTEXTS.push_back(Box::new(Context::new(func, stack_size, get_id()))); 249 | let first = CONTEXTS.front().unwrap(); 250 | switch_context(first.get_regs()); 251 | } 252 | 253 | // 불필요한 스택을 해제 ❹ 254 | rm_unused_stack(); 255 | 256 | // 글로벌 변수 클리어 257 | CTX_MAIN = None; 258 | CONTEXTS.clear(); 259 | MESSAGES = ptr::null_mut(); 260 | WAITING = ptr::null_mut(); 261 | ID = ptr::null_mut(); 262 | 263 | msgs.clear(); // ❺ 264 | waiting.clear(); 265 | ids.clear(); 266 | } 267 | } 268 | } 269 | 270 | unsafe fn rm_unused_stack() { 271 | if UNUSED_STACK.0 != ptr::null_mut() { 272 | // 스택 영역 보호 해제 ❶ 273 | mprotect( 274 | UNUSED_STACK.0 as *mut c_void, 275 | PAGE_SIZE, 276 | ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, 277 | ) 278 | .unwrap(); 279 | // ス스택 영역 해제 ❷ 280 | dealloc(UNUSED_STACK.0, UNUSED_STACK.1); 281 | UNUSED_STACK = (ptr::null_mut(), Layout::new::()); 282 | } 283 | } 284 | 285 | pub fn send(key: u64, msg: u64) { // ❶ 286 | unsafe { 287 | // 메시지 큐의 맨 끝에 추가 288 | (*MESSAGES).push_back(key, msg); 289 | 290 | //스레드 수신 대기 시 실행 큐로 이동 291 | if let Some(ctx) = (*WAITING).remove(&key) { 292 | CONTEXTS.push_back(ctx); 293 | } 294 | } 295 | schedule(); // ❷ 296 | } 297 | 298 | pub fn recv() -> Option { 299 | unsafe { 300 | // 스레드 ID를 취득 301 | let key = CONTEXTS.front().unwrap().id; 302 | 303 | // 메시지가 이미 큐에 있으면 즉시 리턴 304 | if let Some(msg) = (*MESSAGES).pop_front(key) { 305 | return Some(msg); 306 | } 307 | 308 | // 실행 가능한 스레드가 달리 없으면 데드록 309 | if CONTEXTS.len() == 1 { 310 | panic!("deadlock"); 311 | } 312 | 313 | // 실행 중 스레드를 수신 대기 상대로 이동 314 | let mut ctx = CONTEXTS.pop_front().unwrap(); 315 | let regs = ctx.get_regs_mut(); 316 | (*WAITING).insert(key, ctx); 317 | 318 | // 다음 실행 가능한 스레드로 컨텍스트 스위칭 319 | if set_context(regs) == 0 { 320 | let next = CONTEXTS.front().unwrap(); 321 | switch_context((**next).get_regs()); 322 | } 323 | 324 | // 불필요한 스택을 삭제 325 | rm_unused_stack(); 326 | 327 | // 수신한 메시지를 취득 328 | (*MESSAGES).pop_front(key) 329 | } 330 | } -------------------------------------------------------------------------------- /chap6/ch6_mult/src/main.rs: -------------------------------------------------------------------------------- 1 | mod green; 2 | 3 | fn mash() { 4 | green::spawn(ortega, 2 * 1024 * 1024); 5 | for _ in 0..10 { 6 | println!("Mash!"); 7 | green::schedule(); 8 | } 9 | } 10 | 11 | fn ortega() { 12 | for _ in 0..10 { 13 | println!("Ortega!"); 14 | green::schedule(); 15 | } 16 | } 17 | 18 | fn gaia() { 19 | green::spawn(mash, 2 * 1024 * 1024); 20 | for _ in 0..10 { 21 | println!("Gaia!"); 22 | green::schedule(); 23 | } 24 | } 25 | 26 | fn producer() { // ❶ 27 | let id = green::spawn(consumer, 2 * 1024 * 1024); 28 | for i in 0..10 { 29 | green::send(id, i); 30 | } 31 | } 32 | 33 | fn consumer() { // ❷ 34 | for _ in 0..10 { 35 | let msg = green::recv().unwrap(); 36 | println!("received: count = {}", msg); 37 | } 38 | } 39 | 40 | fn main() { 41 | // 6.2 협조적 그린 스레드 구현 실행 예 42 | green::spawn_from_main(gaia, 2 * 1024 * 1024); 43 | 44 | println!("--------------------"); 45 | 46 | // 6.3 액터 모델 실행 예 47 | green::spawn_from_main(producer, 2 * 1024 * 1024); // ❸ 48 | } -------------------------------------------------------------------------------- /chap7/7.1/README.md: -------------------------------------------------------------------------------- 1 | # 7.1 공평한 배타 제어 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 ```--release```를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. 10 | 11 | ```sh 12 | $ cd ch7_1_1_fairlock 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_1_fairlock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch7_1_1_fairlock" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_1_fairlock/src/fairlock.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::sync::atomic::{fence, AtomicBool, AtomicUsize, Ordering}; 4 | 5 | // 스레드의 최대 수 6 | pub const NUM_LOCK: usize = 8; // ❶ 7 | 8 | // NUM_LOCK의 여분을 구하기 위한 비트 마스크 9 | const MASK: usize = NUM_LOCK - 1; // ❷ 10 | 11 | // 공평한 록용 타입 ❸ 12 | pub struct FairLock { 13 | waiting: Vec, // 록 획등 중인 스레드 14 | lock: AtomicBool, // 록용 변수 15 | turn: AtomicUsize, // 록 획득 우선 스레드 16 | data: UnsafeCell, // 보호 대상 데이터 17 | } 18 | 19 | // 록 해제, 보호 대상 데이터로의 접근을 수행하기 위한 타입 ❹ 20 | pub struct FairLockGuard<'a, T> { 21 | fair_lock: &'a FairLock, 22 | idx: usize, // 스레드 번호 23 | } 24 | 25 | impl FairLock { 26 | pub fn new(v: T) -> Self { // ❶ 27 | let mut vec = Vec::new(); 28 | for _ in 0..NUM_LOCK { 29 | vec.push(AtomicBool::new(false)); 30 | } 31 | 32 | FairLock { 33 | waiting: vec, 34 | lock: AtomicBool::new(false), 35 | data: UnsafeCell::new(v), 36 | turn: AtomicUsize::new(0), 37 | } 38 | } 39 | 40 | // 록 함수 ❷ 41 | // idx는 스레드 번호 42 | pub fn lock(&self, idx: usize) -> FairLockGuard { 43 | assert!(idx < NUM_LOCK); // idx가 최대 수 미만인지 검사 ❸ 44 | 45 | // 자신의 스레드를 록 획득 시행 중으로 설정 46 | self.waiting[idx].store(true, Ordering::Relaxed); // ❹ 47 | loop { 48 | // 다른 스레드가 false를 설정한 경우 록 획득❺ 49 | if !self.waiting[idx].load(Ordering::Relaxed) { 50 | break; 51 | } 52 | 53 | // 공유 변수를 이용해 록 획득을 테스트❻ 54 | if !self.lock.load(Ordering::Relaxed) { 55 | if let Ok(_) = self.lock.compare_exchange_weak( 56 | false, // false이면 57 | true, // true를 써넣음 58 | Ordering::Relaxed, // 성공 시 오더 59 | Ordering::Relaxed, // 실패 시 오더 60 | ) { 61 | break; // 록 획득 62 | } 63 | } 64 | } 65 | fence(Ordering::Acquire); 66 | 67 | FairLockGuard { 68 | fair_lock: self, 69 | idx: idx, 70 | } 71 | } 72 | } 73 | 74 | // 록 획득 후에 자동으로 해제되도록 Drop 트레이트를 구현 ❶ 75 | impl<'a, T> Drop for FairLockGuard<'a, T> { 76 | fn drop(&mut self) { 77 | let fl = self.fair_lock; // fair_lock으로의 참조를 획득 78 | 79 | // 자신의 스레드를 록 획득 시도 중이 아닌 상태로 설정❷ 80 | fl.waiting[self.idx].store(false, Ordering::Relaxed); 81 | 82 | // 현재의 록 획득 우선 스레드가 자신이라면 다음 스레드로 설정 ❸ 83 | let turn = fl.turn.load(Ordering::Relaxed); 84 | let next = if turn == self.idx { 85 | (turn + 1) & MASK 86 | } else { 87 | turn 88 | }; 89 | 90 | if fl.waiting[next].load(Ordering::Relaxed) { // ❹ 91 | // 다음 록 획득 우선 스레드가 록 획득 중이면 92 | // 해당 스레드에 록을 전달한다 93 | fl.turn.store(next, Ordering::Relaxed); 94 | fl.waiting[next].store(false, Ordering::Release); 95 | } else { 96 | // 다음 록 획득 우선 스레드가 록 획득 중이 아니면 97 | // 그 다음 스레드를 록 획득 우선 스레드로 설정하고 록을 해제한다 98 | fl.turn.store((next + 1) & MASK, Ordering::Relaxed); 99 | fl.lock.store(false, Ordering::Release); 100 | } 101 | } 102 | } 103 | 104 | // FairLock 타입은 스레드 사이에서 공유 가능하도록 설정 105 | unsafe impl Sync for FairLock {} 106 | unsafe impl Send for FairLock {} 107 | 108 | // 보호 대상 데이터의 이뮤터블한 참조 제외 109 | impl<'a, T> Deref for FairLockGuard<'a, T> { 110 | type Target = T; 111 | 112 | fn deref(&self) -> &Self::Target { 113 | unsafe { &*self.fair_lock.data.get() } 114 | } 115 | } 116 | 117 | // 보호 대상 데이터의 뮤터블한 참조 제외 118 | impl<'a, T> DerefMut for FairLockGuard<'a, T> { 119 | fn deref_mut(&mut self) -> &mut Self::Target { 120 | unsafe { &mut *self.fair_lock.data.get() } 121 | } 122 | } -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_1_fairlock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | const NUM_LOOP: usize = 100000; 4 | const NUM_THREADS: usize = 4; 5 | 6 | mod fairlock; 7 | 8 | fn main() { 9 | let lock = Arc::new(fairlock::FairLock::new(0)); 10 | let mut v = Vec::new(); 11 | 12 | for i in 0..NUM_THREADS { 13 | let lock0 = lock.clone(); 14 | let t = std::thread::spawn(move || { 15 | for _ in 0..NUM_LOOP { 16 | // 스레즈 번호를 전달해서 록 17 | let mut data = lock0.lock(i); 18 | *data += 1; 19 | } 20 | }); 21 | v.push(t); 22 | } 23 | 24 | for t in v { 25 | t.join().unwrap(); 26 | } 27 | 28 | println!( 29 | "COUNT = {} (expected = {})", 30 | *lock.lock(0), 31 | NUM_LOOP * NUM_THREADS 32 | ); 33 | } -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_2_ticketlock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch7_1_2_ticketlock" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_2_ticketlock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | const NUM_LOOP: usize = 100000; 4 | const NUM_THREADS: usize = 4; 5 | 6 | mod ticketlock; 7 | 8 | fn main() { 9 | let lock = Arc::new(ticketlock::TicketLock::new(0)); 10 | let mut v = Vec::new(); 11 | 12 | for _ in 0..NUM_THREADS { 13 | let lock0 = lock.clone(); 14 | let t = std::thread::spawn(move || { 15 | for _ in 0..NUM_LOOP { 16 | let mut data = lock0.lock(); 17 | *data += 1; 18 | } 19 | }); 20 | v.push(t); 21 | } 22 | 23 | for t in v { 24 | t.join().unwrap(); 25 | } 26 | 27 | println!( 28 | "COUNT = {} (expected = {})", 29 | *lock.lock(), 30 | NUM_LOOP * NUM_THREADS 31 | ); 32 | } -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_2_ticketlock/src/ticketlock.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::sync::atomic::{fence, AtomicUsize, Ordering}; 4 | 5 | // 티켓록용 타입 6 | pub struct TicketLock { 7 | ticket: AtomicUsize, // 티켓 8 | turn: AtomicUsize, // 실행 가능한 티켓 9 | data: UnsafeCell, 10 | } 11 | 12 | // 록 해제, 보호 대상 데이터로의 접근을 수행하기 위한 타입 13 | pub struct TicketLockGuard<'a, T> { 14 | ticket_lock: &'a TicketLock, 15 | } 16 | 17 | impl TicketLock { 18 | pub fn new(v: T) -> Self { 19 | TicketLock { 20 | ticket: AtomicUsize::new(0), 21 | turn: AtomicUsize::new(0), 22 | data: UnsafeCell::new(v), 23 | } 24 | } 25 | 26 | // 록용 함수 ❶ 27 | pub fn lock(&self) -> TicketLockGuard { 28 | // 티켓을 취득 29 | let t = self.ticket.fetch_add(1, Ordering::Relaxed); 30 | // 소유하는 티켓의 순서가 될 때까지 스핀 31 | while self.turn.load(Ordering::Relaxed) != t {} 32 | fence(Ordering::Acquire); 33 | 34 | TicketLockGuard { ticket_lock: self } 35 | } 36 | } 37 | 38 | // 록 획득 후에 자동으로 해제되도록 Drop 트레이트를 구현 ❷ 39 | impl<'a, T> Drop for TicketLockGuard<'a, T> { 40 | fn drop(&mut self) { 41 | // 다음 티켓을 실행 가능하도록 설정 42 | self.ticket_lock.turn.fetch_add(1, Ordering::Release); 43 | } 44 | } 45 | 46 | // TicketLock 타입은 스레드 사이에서 공유 가능하도록 설정 47 | unsafe impl Sync for TicketLock {} 48 | unsafe impl Send for TicketLock {} 49 | 50 | // 보호 대상 데이터의 이뮤터블한 참조 제외 51 | impl<'a, T> Deref for TicketLockGuard<'a, T> { 52 | type Target = T; 53 | 54 | fn deref(&self) -> &Self::Target { 55 | unsafe { &*self.ticket_lock.data.get() } 56 | } 57 | } 58 | 59 | // 보호 대상 데이터의 뮤터블한 참조 제외 60 | impl<'a, T> DerefMut for TicketLockGuard<'a, T> { 61 | fn deref_mut(&mut self) -> &mut Self::Target { 62 | unsafe { &mut *self.ticket_lock.data.get() } 63 | } 64 | } -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_3_mcslock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch7_1_3_mcslock" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_3_mcslock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | const NUM_LOOP: usize = 100000; 4 | const NUM_THREADS: usize = 4; 5 | 6 | mod mcs; 7 | 8 | fn main() { 9 | let n = Arc::new(mcs::MCSLock::new(0)); 10 | let mut v = Vec::new(); 11 | 12 | for _ in 0..NUM_THREADS { 13 | let n0 = n.clone(); 14 | let t = std::thread::spawn(move || { 15 | // 노드를 작성하고 록 16 | let mut node = mcs::MCSNode::new(); 17 | for _ in 0..NUM_LOOP { 18 | let mut r = n0.lock(&mut node); 19 | *r += 1; 20 | } 21 | }); 22 | 23 | v.push(t); 24 | } 25 | 26 | for t in v { 27 | t.join().unwrap(); 28 | } 29 | 30 | // 노드를 작성하고 록 31 | let mut node = mcs::MCSNode::new(); 32 | let r = n.lock(&mut node); 33 | println!( 34 | "COUNT = {} (expected = {})", 35 | *r, 36 | NUM_LOOP * NUM_THREADS 37 | ); 38 | } -------------------------------------------------------------------------------- /chap7/7.1/ch7_1_3_mcslock/src/mcs.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::ptr::null_mut; 4 | use std::sync::atomic::{fence, AtomicBool, AtomicPtr, Ordering}; 5 | 6 | pub struct MCSLock { // ❶ 7 | last: AtomicPtr>, // 큐의 맨 마지막 8 | data: UnsafeCell, // 보호 대상 데이터 9 | } 10 | 11 | pub struct MCSNode { // ❷ 12 | next: AtomicPtr>, // 다음 노드 13 | locked: AtomicBool, // true이면 록 획득 중 14 | } 15 | 16 | pub struct MCSLockGuard<'a, T> { 17 | node: &'a mut MCSNode, // 자신의 스레드 노드 18 | mcs_lock: &'a MCSLock, // 큐의 가장 마지막과 보호 대상 데이터로의 참조 19 | } 20 | 21 | // 스레드끼리의 데이터 공유, 및 채널을 이용한 송수신 가능 설정 22 | unsafe impl Sync for MCSLock {} 23 | unsafe impl Send for MCSLock {} 24 | 25 | impl MCSNode { 26 | pub fn new() -> Self { 27 | MCSNode { // MCSNodeの初期化 28 | next: AtomicPtr::new(null_mut()), 29 | locked: AtomicBool::new(false), 30 | } 31 | } 32 | } 33 | 34 | // 보호 대상 데이터의 이뮤터블한 참조 제외 35 | impl<'a, T> Deref for MCSLockGuard<'a, T> { 36 | type Target = T; 37 | 38 | fn deref(&self) -> &Self::Target { 39 | unsafe { &*self.mcs_lock.data.get() } 40 | } 41 | } 42 | 43 | // 보호 대상 데이터의 뮤터블한 참조 제외 44 | impl<'a, T> DerefMut for MCSLockGuard<'a, T> { 45 | fn deref_mut(&mut self) -> &mut Self::Target { 46 | unsafe { &mut *self.mcs_lock.data.get() } 47 | } 48 | } 49 | 50 | impl MCSLock { 51 | pub fn new(v: T) -> Self { 52 | MCSLock { 53 | last: AtomicPtr::new(null_mut()), 54 | data: UnsafeCell::new(v), 55 | } 56 | } 57 | 58 | pub fn lock<'a>(&'a self, node: &'a mut MCSNode) -> MCSLockGuard { 59 | // 자기 스레드용 노드를 초기화 ❶ 60 | node.next = AtomicPtr::new(null_mut()); 61 | node.locked = AtomicBool::new(false); 62 | 63 | let guard = MCSLockGuard { 64 | node, 65 | mcs_lock: self, 66 | }; 67 | 68 | // 자신을 큐의 맨 마지막으로 한다 ❷ 69 | let ptr = guard.node as *mut MCSNode; 70 | let prev = self.last.swap(ptr, Ordering::Relaxed); 71 | 72 | // 맨 마지막이 null이면 나무도 록을 획득하려 하지 않는 것이므로 록을 획득 73 | // null이 아닌 경우에는 자신을 큐의 맨 끝에 추가 74 | if prev != null_mut() { // ❸ 75 | // 록 획득 중이라고 설정 76 | guard.node.locked.store(true, Ordering::Relaxed); // ❹ 77 | 78 | // 자신을 큐의 맨 끝에 추가 ❺ 79 | let prev = unsafe { &*prev }; 80 | prev.next.store(ptr, Ordering::Relaxed); 81 | 82 | // 다른 스레드로부터 false로 설정될 때까지 스핀 >❻ 83 | while guard.node.locked.load(Ordering::Relaxed) {} 84 | } 85 | 86 | fence(Ordering::Acquire); 87 | guard 88 | } 89 | } 90 | 91 | impl<'a, T> Drop for MCSLockGuard<'a, T> { 92 | fn drop(&mut self) { 93 | // 자신의 다음 노트가 null이고 자신이 맨 끝의 노드이면 , 맨 끝을 null로 설정한다 ❶ 94 | if self.node.next.load(Ordering::Relaxed) == null_mut() { 95 | let ptr = self.node as *mut MCSNode; 96 | if let Ok(_) = self.mcs_lock.last.compare_exchange( // ❷ 97 | ptr, 98 | null_mut(), 99 | Ordering::Release, 100 | Ordering::Relaxed, 101 | ) { 102 | return; 103 | } 104 | } 105 | 106 | // 자신의 다음 스레드가 lock 함수 실행 중이므로, 종료될 떄까지 대기한다 ❸ 107 | while self.node.next.load(Ordering::Relaxed) == null_mut() {} 108 | 109 | // 자신의 다음 스레드를 실행 가능하게 설정 ❹ 110 | let next = unsafe { &mut *self.node.next.load(Ordering::Relaxed) }; 111 | next.locked.store(false, Ordering::Release); 112 | } 113 | } -------------------------------------------------------------------------------- /chap7/7.2/README.md: -------------------------------------------------------------------------------- 1 | # 7.2 소프트웨어 트랜잭셔널 메모리 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 ```--release```를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. 10 | 11 | ```sh 12 | $ cd ch7_2_stm 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap7/7.2/ch7_2_stm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch7_2_stm" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap7/7.2/ch7_2_stm/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::{thread, time}; 3 | 4 | mod tl2; 5 | 6 | // 철학자 수 7 | const NUM_PHILOSOPHERS: usize = 8; 8 | 9 | fn philosopher(stm: Arc, n: usize) { // ❶ 10 | // 왼쪽과 오른쪽 포크용 메모리 ❷ 11 | let left = 8 * n; 12 | let right = 8 * ((n + 1) % NUM_PHILOSOPHERS); 13 | 14 | for _ in 0..500000 { 15 | // 포크를 든다 16 | while !stm 17 | .write_transaction(|tr| { 18 | let mut f1 = load!(tr, left); // 왼쪽 포크 ❸ 19 | let mut f2 = load!(tr, right); // 오른쪽 포크 20 | if f1[0] == 0 && f2[0] == 0 { // ❹ 21 | // 양쪽이 모두 비어있으면 1로 설정 22 | f1[0] = 1; 23 | f2[0] = 1; 24 | store!(tr, left, f1); 25 | store!(tr, right, f2); 26 | tl2::STMResult::Ok(true) 27 | } else { 28 | // 양쪽을 들 수 없으면 취득 실패 29 | tl2::STMResult::Ok(false) 30 | } 31 | }) 32 | .unwrap() 33 | { } 34 | 35 | // 포크를 놓는다 ❺ 36 | stm.write_transaction(|tr| { 37 | let mut f1 = load!(tr, left); 38 | let mut f2 = load!(tr, right); 39 | f1[0] = 0; 40 | f2[0] = 0; 41 | store!(tr, left, f1); 42 | store!(tr, right, f2); 43 | tl2::STMResult::Ok(()) 44 | }); 45 | } 46 | } 47 | 48 | // 관측자 49 | fn observer(stm: Arc) { 50 | for _ in 0..10000 { 51 | // 포크의 현재 상태를 얻는다 ❶ 52 | let chopsticks = stm 53 | .read_transaction(|tr| { 54 | let mut v = [0; NUM_PHILOSOPHERS]; 55 | for i in 0..NUM_PHILOSOPHERS { 56 | v[i] = load!(tr, 8 * i)[0]; 57 | } 58 | 59 | tl2::STMResult::Ok(v) 60 | }) 61 | .unwrap(); 62 | 63 | println!("{:?}", chopsticks); 64 | 65 | // 들고 있는 포크의 수가 홀수이면 올바르지 않음 ❷ 66 | let mut n = 0; 67 | for c in &chopsticks { 68 | if *c == 1 { 69 | n += 1; 70 | } 71 | } 72 | 73 | if n & 1 != 0 { 74 | panic!("inconsistent"); 75 | } 76 | 77 | // 100마이크로초 동안 슬립 78 | let us = time::Duration::from_micros(100); 79 | thread::sleep(us); 80 | } 81 | } 82 | 83 | fn main() { 84 | let stm = Arc::new(tl2::STM::new()); 85 | let mut v = Vec::new(); 86 | 87 | // 철학자 스레드 생성 88 | for i in 0..NUM_PHILOSOPHERS { 89 | let s = stm.clone(); 90 | let th = std::thread::spawn(move || philosopher(s, i)); 91 | v.push(th); 92 | } 93 | 94 | // 관측자 스레드 생성 95 | let obs = std::thread::spawn(move || observer(stm)); 96 | 97 | for th in v { 98 | th.join().unwrap(); 99 | } 100 | 101 | obs.join().unwrap(); 102 | } -------------------------------------------------------------------------------- /chap7/7.2/ch7_2_stm/src/tl2.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::collections::HashMap; 3 | use std::collections::HashSet; 4 | use std::sync::atomic::{fence, AtomicU64, Ordering}; 5 | 6 | // 스트라이프 크기 7 | const STRIPE_SIZE: usize = 8; // u64, 8바이트 8 | 9 | // 메모리 합계 크기 10 | const MEM_SIZE: usize = 512; // 512바이트 11 | 12 | // 메모리 타입 13 | pub struct Memory { 14 | mem: Vec, // 메모리 15 | lock_ver: Vec, // lock & version 16 | global_clock: AtomicU64, // global version-clock 17 | 18 | // 주소에서 스트라이프 번호로 변환하기 위한 시프트(이동)량 19 | shift_size: u32, 20 | } 21 | 22 | impl Memory { 23 | pub fn new() -> Self { // ❶ 24 | // 메모리 영역을 생성 25 | let mem = [0].repeat(MEM_SIZE); 26 | 27 | // 주소에서 스트라이프 번호로 변환하기 위한 시프트량을 계산 28 | // 스트라이프의 크기는 2^n에 얼라인먼트되어야 함 29 | let shift = STRIPE_SIZE.trailing_zeros(); // ❷ 30 | 31 | // lock & version을 초기화 ❸ 32 | let mut lock_ver = Vec::new(); 33 | for _ in 0..MEM_SIZE >> shift { 34 | lock_ver.push(AtomicU64::new(0)); 35 | } 36 | 37 | Memory { 38 | mem, 39 | lock_ver, 40 | global_clock: AtomicU64::new(0), 41 | shift_size: shift, 42 | } 43 | } 44 | 45 | // global version-clock을 인크리먼트 ❹ 46 | fn inc_global_clock(&mut self) -> u64 { 47 | self.global_clock.fetch_add(1, Ordering::AcqRel) 48 | } 49 | 50 | // 대상 주소의 버전을 취득 ❺ 51 | fn get_addr_ver(&self, addr: usize) -> u64 { 52 | let idx = addr >> self.shift_size; 53 | let n = self.lock_ver[idx].load(Ordering::Relaxed); 54 | n & !(1 << 63) 55 | } 56 | 57 | // 대상 주소의 버전아 rv 이하로 록 되어있지 않은지 확인 ❻ 58 | fn test_not_modify(&self, addr: usize, rv: u64) -> bool { 59 | let idx = addr >> self.shift_size; 60 | let n = self.lock_ver[idx].load(Ordering::Relaxed); 61 | // 록의 비트는 최상위 비트로 하므로, 62 | // rv와 비교하는 것만으로 간단히 확인 가능 63 | n <= rv 64 | } 65 | 66 | // 대상 주소의 록을 획득 ❼ 67 | fn lock_addr(&mut self, addr: usize) -> bool { 68 | let idx = addr >> self.shift_size; 69 | match self.lock_ver[idx].fetch_update( // ❽ 70 | Ordering::Relaxed, // 쓰기 시의 오더 71 | Ordering::Relaxed, // 일기 시의 오더 72 | |val| { 73 | // 최상위 비트 값 확인 및 설정 74 | let n = val & (1 << 63); 75 | if n == 0 { 76 | Some(val | (1 << 63)) 77 | } else { 78 | None 79 | } 80 | }, 81 | ) { 82 | Ok(_) => true, 83 | Err(_) => false, 84 | } 85 | } 86 | 87 | // 대상 주소의 록 해제 ❾ 88 | fn unlock_addr(&mut self, addr: usize) { 89 | let idx = addr >> self.shift_size; 90 | self.lock_ver[idx].fetch_and(!(1 << 63), 91 | Ordering::Relaxed); 92 | } 93 | } 94 | 95 | pub struct ReadTrans<'a> { // ❶ 96 | read_ver: u64, // read-version 97 | is_abort: bool, // 경쟁을 감지하면 True 98 | mem: &'a Memory, // Memory 타입으로의 참조 99 | } 100 | 101 | impl<'a> ReadTrans<'a> { 102 | fn new(mem: &'a Memory) -> Self { // ❷ 103 | ReadTrans { 104 | is_abort: false, 105 | 106 | // global version-clock 읽기 107 | read_ver: mem.global_clock.load(Ordering::Acquire), 108 | 109 | mem, 110 | } 111 | } 112 | 113 | // 메모리 읽기 함수 ❸ 114 | pub fn load(&mut self, addr: usize) -> Option<[u8; STRIPE_SIZE]> { 115 | // 경쟁을 감지하면 종료 ❹ 116 | if self.is_abort { 117 | return None; 118 | } 119 | 120 | // 주소가 스트라이프의 얼라인먼트에 맞는가를 확인 121 | assert_eq!(addr & (STRIPE_SIZE - 1), 0); // ❺ 122 | 123 | // 읽기 메모리가 록 되어있지 않고, read-version 이하인가 확인 ❻ 124 | if !self.mem.test_not_modify(addr, self.read_ver) { 125 | self.is_abort = true; 126 | return None; 127 | } 128 | 129 | fence(Ordering::Acquire); 130 | 131 | // 메모리 읽기. 단순한 복사임 ❼ 132 | let mut mem = [0; STRIPE_SIZE]; 133 | for (dst, src) in mem 134 | .iter_mut() 135 | .zip(self.mem.mem[addr..addr + STRIPE_SIZE].iter()) 136 | { 137 | *dst = *src; 138 | } 139 | 140 | fence(Ordering::SeqCst); 141 | 142 | // 읽기 메모리가 록 되어있지 않고, read-version 이하인가 확인 ❽ 143 | if !self.mem.test_not_modify(addr, self.read_ver) { 144 | self.is_abort = true; 145 | return None; 146 | } 147 | 148 | Some(mem) 149 | } 150 | } 151 | 152 | pub struct WriteTrans<'a> { 153 | read_ver: u64, // read-version 154 | read_set: HashSet, // read-set 155 | write_set: HashMap, // write-set 156 | locked: Vec, // 록 완료 주소 157 | is_abort: bool, // 경쟁을 감지하면 true 158 | mem: &'a mut Memory, // Memory 타입으로의 참조 159 | } 160 | 161 | impl<'a> Drop for WriteTrans<'a> { 162 | fn drop(&mut self) { 163 | // 록 완료 주소의 록을 해제 164 | for addr in self.locked.iter() { 165 | self.mem.unlock_addr(*addr); 166 | } 167 | } 168 | } 169 | 170 | impl<'a> WriteTrans<'a> { 171 | fn new(mem: &'a mut Memory) -> Self { // ❶ 172 | WriteTrans { 173 | read_set: HashSet::new(), 174 | write_set: HashMap::new(), 175 | locked: Vec::new(), 176 | is_abort: false, 177 | 178 | // global version-clock 읽기 179 | read_ver: mem.global_clock.load(Ordering::Acquire), 180 | 181 | mem, 182 | } 183 | } 184 | 185 | // 메모리 쓰기 함수 ❷ 186 | pub fn store(&mut self, addr: usize, val: [u8; STRIPE_SIZE]) { 187 | // 주소가 스트라이브의 얼라인먼트에 맞는가 확인 188 | assert_eq!(addr & (STRIPE_SIZE - 1), 0); 189 | self.write_set.insert(addr, val); 190 | } 191 | 192 | // 메모리 읽기 함수 ❸ 193 | pub fn load(&mut self, addr: usize) -> Option<[u8; STRIPE_SIZE]> { 194 | // 경쟁을 감지한 경우 종료 195 | if self.is_abort { 196 | return None; 197 | } 198 | 199 | // 주소가 스트라이프의 얼라인먼트에 맞는가 확인 200 | assert_eq!(addr & (STRIPE_SIZE - 1), 0); 201 | 202 | // 읽기 주소 저장 203 | self.read_set.insert(addr); 204 | 205 | // write-set에 있다면 이를 읽음 206 | if let Some(m) = self.write_set.get(&addr) { 207 | return Some(*m); 208 | } 209 | 210 | // 읽기 메모리가 록 되어있지 않고, read-version 이하인지 판정 211 | if !self.mem.test_not_modify(addr, self.read_ver) { 212 | self.is_abort = true; 213 | return None; 214 | } 215 | 216 | fence(Ordering::Acquire); 217 | 218 | // 메모리 읽기. 단순히 복사함. 219 | let mut mem = [0; STRIPE_SIZE]; 220 | for (dst, src) in mem 221 | .iter_mut() 222 | .zip(self.mem.mem[addr..addr + STRIPE_SIZE].iter()) 223 | { 224 | *dst = *src; 225 | } 226 | 227 | fence(Ordering::SeqCst); 228 | 229 | // 읽기 메모리가 록 되어있지 않고, read-version 이하인지 판정 230 | if !self.mem.test_not_modify(addr, self.read_ver) { 231 | self.is_abort = true; 232 | return None; 233 | } 234 | 235 | Some(mem) 236 | } 237 | 238 | // write-set 안의 주소를 록 239 | // 모든 주소의 록읠 획득할 수 있는 경우 true를 리턴한다 ❹ 240 | fn lock_write_set(&mut self) -> bool { 241 | for (addr, _) in self.write_set.iter() { 242 | if self.mem.lock_addr(*addr) { 243 | // 록 획득 가능한 경우에는 locked에 추가 244 | self.locked.push(*addr); 245 | } else { 246 | // 가능하지 않은 경우에는 false를 반환하고 종료 247 | return false; 248 | } 249 | } 250 | true 251 | } 252 | 253 | // read-set 검증 ❺ 254 | fn validate_read_set(&self) -> bool { 255 | for addr in self.read_set.iter() { 256 | // write-set 안에 있는 주소인 경우에는 257 | // 자기 스레드가 록을 획득한 상태임 258 | if self.write_set.contains_key(addr) { 259 | // 버전만 검사 260 | let ver = self.mem.get_addr_ver(*addr); 261 | if ver > self.read_ver { 262 | return false; 263 | } 264 | } else { 265 | // 다른 스레드가 록을 하지 않았는지와 버전을 검사 266 | if !self.mem.test_not_modify(*addr, self.read_ver) { 267 | return false; 268 | } 269 | } 270 | } 271 | true 272 | } 273 | 274 | // 커밋 ❻ 275 | fn commit(&mut self, ver: u64) { 276 | // 모든 주소에 대해 쓰기. 단순한 메모리 복사임. 277 | for (addr, val) in self.write_set.iter() { 278 | let addr = *addr as usize; 279 | for (dst, src) in self.mem.mem[addr..addr + STRIPE_SIZE].iter_mut().zip(val) { 280 | *dst = *src; 281 | } 282 | } 283 | 284 | fence(Ordering::Release); 285 | 286 | // 모든 주소의 록 해제 및 버전 업데이트 287 | for (addr, _) in self.write_set.iter() { 288 | let idx = addr >> self.mem.shift_size; 289 | self.mem.lock_ver[idx].store(ver, Ordering::Relaxed); 290 | } 291 | 292 | // 록 완료 주소 집합을 초기화 293 | self.locked.clear(); 294 | } 295 | } 296 | 297 | pub enum STMResult { 298 | Ok(T), 299 | Retry, // 트랜잭션 재시도 300 | Abort, // 트랜잭션 중단 301 | } 302 | 303 | pub struct STM { 304 | mem: UnsafeCell, // 실제 메모리 305 | } 306 | 307 | // 스레드 사이에서 공유 가능하도록 설정. 채널에서 송수신 가능하도록 설정. 308 | unsafe impl Sync for STM {} 309 | unsafe impl Send for STM {} 310 | 311 | impl STM { 312 | pub fn new() -> Self { 313 | STM { 314 | mem: UnsafeCell::new(Memory::new()), 315 | } 316 | } 317 | 318 | // 읽기 트랜잭션 ❶ 319 | pub fn read_transaction(&self, f: F) -> Option 320 | where 321 | F: Fn(&mut ReadTrans) -> STMResult, 322 | { 323 | loop { 324 | // 1. global version-clock 읽기 ❷ 325 | let mut tr = ReadTrans::new(unsafe { &*self.mem.get() }); 326 | 327 | // 2. 투기적 실행 ❸ 328 | match f(&mut tr) { 329 | STMResult::Abort => return None, // 중단 330 | STMResult::Retry => { 331 | if tr.is_abort { 332 | continue; // 재시도 333 | } 334 | return None; // 중단 335 | } 336 | STMResult::Ok(val) => { 337 | if tr.is_abort == true { 338 | continue; // 재시도 339 | } else { 340 | return Some(val); // 3. 커넷 341 | } 342 | } 343 | } 344 | } 345 | } 346 | 347 | // 쓰기 트랜잭션 ❹ 348 | pub fn write_transaction(&self, f: F) -> Option 349 | where 350 | F: Fn(&mut WriteTrans) -> STMResult, 351 | { 352 | loop { 353 | // 1. global version-clock 읽기 ❺ 354 | let mut tr = WriteTrans::new(unsafe { &mut *self.mem.get() }); 355 | 356 | // 2. 투기적 실행 ❻ 357 | let result; 358 | match f(&mut tr) { 359 | STMResult::Abort => return None, 360 | STMResult::Retry => { 361 | if tr.is_abort { 362 | continue; 363 | } 364 | return None; 365 | } 366 | STMResult::Ok(val) => { 367 | if tr.is_abort { 368 | continue; 369 | } 370 | result = val; 371 | } 372 | } 373 | 374 | // 3. write-set 록 ❼ 375 | if !tr.lock_write_set() { 376 | continue; 377 | } 378 | 379 | // 4. global version-clock 인크리먼트 ❽ 380 | let ver = 1 + tr.mem.inc_global_clock(); 381 | 382 | // 5. read-set 검증 ❾ 383 | if tr.read_ver + 1 != ver && !tr.validate_read_set() { 384 | continue; 385 | } 386 | 387 | // 6. 커밋과 릴리스 ❿ 388 | tr.commit(ver); 389 | 390 | return Some(result); 391 | } 392 | } 393 | } 394 | 395 | // メモリ読み込み用のマクロ ❶ 396 | #[macro_export] 397 | macro_rules! load { 398 | ($t:ident, $a:expr) => { 399 | if let Some(v) = ($t).load($a) { 400 | v 401 | } else { 402 | // 読み込みに失敗したらリトライ 403 | return tl2::STMResult::Retry; 404 | } 405 | }; 406 | } 407 | 408 | // メモリ書き込み用のマクロ ❷ 409 | #[macro_export] 410 | macro_rules! store { 411 | ($t:ident, $a:expr, $v:expr) => { 412 | $t.store($a, $v) 413 | }; 414 | } -------------------------------------------------------------------------------- /chap7/7.3/README.md: -------------------------------------------------------------------------------- 1 | # 7.3 록 프리 데이터 구조와 알고리즘 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 ```--release```를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. AArch64 환경에서만 실행 가능합니다. 인라인 어셈블리를 이용하므로 `nightly`에만 대응합니다. 10 | 11 | ```sh 12 | $ cd ch7_3_lockfree 13 | $ cargo +nightly run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap7/7.3/ch7_3_lockfree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch7_3_lockfree" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap7/7.3/ch7_3_lockfree/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(asm)] 2 | 3 | use std::sync::Arc; 4 | 5 | mod stack; 6 | 7 | const NUM_LOOP: usize = 1000000; // 루프 횟수 8 | const NUM_THREADS: usize = 4; // 스레드 수 9 | 10 | use stack::Stack; 11 | 12 | fn main() { 13 | let stack = Arc::new(Stack::::new()); 14 | let mut v = Vec::new(); 15 | 16 | for i in 0..NUM_THREADS { 17 | let stack0 = stack.clone(); 18 | let t = std::thread::spawn(move || { 19 | if i & 1 == 0 { 20 | // 짝수 스레드는 push 21 | for j in 0..NUM_LOOP { 22 | let k = i * NUM_LOOP + j; 23 | stack0.get_mut().push(k); 24 | println!("push: {}", k); 25 | } 26 | println!("finished push: #{}", i); 27 | } else { 28 | // 홀수 스레드는 pop 29 | for _ in 0..NUM_LOOP { 30 | loop { 31 | // pop None이면 재시도 32 | if let Some(k) = stack0.get_mut().pop() { 33 | println!("pop: {}", k); 34 | break; 35 | } 36 | } 37 | } 38 | println!("finished pop: #{}", i); 39 | } 40 | }); 41 | v.push(t); 42 | } 43 | 44 | for t in v { 45 | t.join().unwrap(); 46 | } 47 | 48 | assert!(stack.get_mut().pop() == None); 49 | } -------------------------------------------------------------------------------- /chap7/7.3/ch7_3_lockfree/src/stack.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | 3 | // 스택의 노드. 리스트 구조로 관리 ❶ 4 | #[repr(C)] 5 | struct Node { 6 | next: *mut Node, 7 | data: T, 8 | } 9 | 10 | // 스택의 선두 ❷ 11 | #[repr(C)] 12 | pub struct StackHead { 13 | head: *mut Node, 14 | } 15 | 16 | impl StackHead { 17 | fn new() -> Self { 18 | StackHead { head: null_mut() } 19 | } 20 | 21 | pub fn push(&mut self, v: T) { // ❸ 22 | //추가할 노드를 작성 23 | let node = Box::new(Node { 24 | next: null_mut(), 25 | data: v, 26 | }); 27 | 28 | // Box 타입의 값으로부터 포인터를 꺼낸다 29 | let ptr = Box::into_raw(node) as *mut u8 as usize; 30 | 31 | // 포인터의 포인터를 취득 32 | // head에 저장되어 있는 메모리를 LL/SC 33 | let head = &mut self.head as *mut *mut Node as *mut u8 as usize; 34 | 35 | // LL/SC를 이용한 push ❹ 36 | unsafe { 37 | asm!("1: 38 | ldxr {next}, [{head}] // next = *head 39 | str {next}, [{ptr}] // *ptr = next 40 | stlxr w10, {ptr}, [{head}] // *head = ptr 41 | // if tmp != 0 then goto 1 42 | cbnz w10, 1b", 43 | next = out(reg) _, 44 | ptr = in(reg) ptr, 45 | head = in(reg) head, 46 | out("w10") _) 47 | }; 48 | } 49 | 50 | pub fn pop(&mut self) -> Option { // ❺ 51 | unsafe { 52 | // 포인터의 포인터를 취득 53 | // head에 저장된 메모리를 LL/SC 54 | let head = &mut self.head as *mut *mut Node as *mut u8 as usize; 55 | 56 | // pop한 노드로의 주소를 저장 57 | let mut result: usize; 58 | 59 | // LL/SC을 이용한 pop ❻ 60 | asm!("1: 61 | ldaxr {result}, [{head}] // result = *head 62 | // if result != NULL then goto 2 63 | cbnz {result}, 2f 64 | 65 | // if NULL 66 | clrex // clear exclusive 67 | b 3f // goto 3 68 | 69 | // if not NULL 70 | 2: 71 | ldr {next}, [{result}] // next = *result 72 | stxr w10, {next}, [{head}] // *head = next 73 | // if tmp != 0 then goto 1 74 | cbnz w10, 1b 75 | 76 | 3:", 77 | next = out(reg) _, 78 | result = out(reg) result, 79 | head = in(reg) head, 80 | out("w10") _); 81 | 82 | if result == 0 { 83 | None 84 | } else { 85 | // 포인터를 Box로 되돌리고, 안의 값을 리턴 86 | let ptr = result as *mut u8 as *mut Node; 87 | let head = Box::from_raw(ptr); 88 | Some((*head).data) 89 | } 90 | } 91 | } 92 | } 93 | 94 | impl Drop for StackHead { 95 | fn drop(&mut self) { 96 | // 데이터 삭제 97 | let mut node = self.head; 98 | while node != null_mut() { 99 | // 포인터를 Box로 되돌리는 조작을 반복함 100 | let n = unsafe { Box::from_raw(node) }; 101 | node = n.next; 102 | } 103 | } 104 | } 105 | 106 | use std::cell::UnsafeCell; 107 | 108 | // StackHead를 UnsafeCell로 저장 109 | pub struct Stack { 110 | data: UnsafeCell>, 111 | } 112 | 113 | impl Stack { 114 | pub fn new() -> Self { 115 | Stack { 116 | data: UnsafeCell::new(StackHead::new()), 117 | } 118 | } 119 | 120 | pub fn get_mut(&self) -> &mut StackHead { 121 | unsafe { &mut *self.data.get() } 122 | } 123 | } 124 | 125 | // 스레드 사이의 데이터 공유 및 채널을 사용한 송수신이 가능하도록 설정 126 | unsafe impl Sync for Stack {} 127 | unsafe impl Send for Stack {} -------------------------------------------------------------------------------- /chap7/7.3/ch7_3_lockfree/src/stack_bad.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | use std::sync::atomic::{AtomicPtr, Ordering}; 3 | 4 | // 스택의 노드. 리스트 구조로 관리 ❶ 5 | struct Node { 6 | next: AtomicPtr>, 7 | data: T, 8 | } 9 | 10 | // 스택의 선두 11 | pub struct StackBad { 12 | head: AtomicPtr>, 13 | } 14 | 15 | impl StackBad { 16 | pub fn new() -> Self { 17 | StackBad { 18 | head: AtomicPtr::new(null_mut()), 19 | } 20 | } 21 | 22 | pub fn push(&self, v: T) { // ❷ 23 | // 추가할 노드를 작성 24 | let node = Box::new(Node { 25 | next: AtomicPtr::new(null_mut()), 26 | data: v, 27 | }); 28 | 29 | // Box 타입의 값으로부터 포인터를 꺼낸다 30 | let ptr = Box::into_raw(node); 31 | 32 | unsafe { 33 | // 아토믹하게 헤드를 업데이트 ❸ 34 | loop { 35 | // head의 값을 취득 36 | let head = self.head.load(Ordering::Relaxed); 37 | 38 | // 추가할 노드의 next를 haed로 설정 39 | (*ptr).next.store(head, Ordering::Relaxed); 40 | 41 | // head의 값이 업데이트되지 않으면, 추가할 노드에 업데이트 42 | if let Ok(_) = 43 | self.head 44 | .compare_exchange_weak( 45 | head, // 값이 head이면 46 | ptr, // ptr로 업데이트 47 | Ordering::Release, // 성공 시 오더 48 | Ordering::Relaxed // 실패 시 오더 49 | ) { 50 | break; 51 | } 52 | } 53 | } 54 | } 55 | 56 | pub fn pop(&self) -> Option { // ❹ 57 | unsafe { 58 | // 아토믹하게 헤드를 업데이트 59 | loop { 60 | // head의 값을 취득 ❺ 61 | let head = self.head.load(Ordering::Relaxed); 62 | if head == null_mut() { 63 | return None; // head가 null이면 None 64 | } 65 | 66 | // head.next를 취득 ❻ 67 | let next = (*head).next.load(Ordering::Relaxed); 68 | 69 | // head의 값이 업데이트되어 있지 않으면, 70 | // head.next를 새로운 header로 업데이트 ❼ 71 | if let Ok(_) = self.head.compare_exchange_weak( 72 | head, // 값이 head이면 73 | next, // next오 업데이트 74 | Ordering::Acquire, // 성공 시 오더 75 | Ordering::Relaxed, // 실패 시 오더 76 | ) { 77 | // 포인터를 Box로 되돌리고, 안의 값을 리턴 78 | let h = Box::from_raw(head); 79 | return Some((*h).data); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | impl Drop for StackBad { 87 | fn drop(&mut self) { 88 | // 데이터 삭제 89 | let mut node = self.head.load(Ordering::Relaxed); 90 | while node != null_mut() { 91 | // 포인터를 Box로 되돌리는 조작을 반복함 92 | let n = unsafe { Box::from_raw(node) }; 93 | node = n.next.load(Ordering::Relaxed) 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /chap7/README.md: -------------------------------------------------------------------------------- 1 | # 7 동기 처리 2 2 | 3 | - [7.1 공평한 배타 제어](./7.1/) 4 | - [7.2 소프트웨어 트랜잭셔널 메모리](./7.2/) 5 | - [7.3 록 프리 데이터 구조와 알고리즘](./7.3/) 6 | -------------------------------------------------------------------------------- /chap8/8.4/README.md: -------------------------------------------------------------------------------- 1 | # 8.4 π 계산 2 | 3 | 각 디렉터리에 Cargo용 저장소가 있으므로, 디렉터리로 이동한 뒤 `cargo`로 컴파일 및 실행하기 바랍니다. 4 | 5 | 빌드 시 ```--release```를 지정하면 올바른 효과를 알 수 있습니다. 6 | 7 | ## 컴파일 및 실행 예 8 | 9 | 다음과 같이 디렉터리로 이동한 뒤 실행합니다. 10 | 11 | ```sh 12 | $ cd ch8_4_7_barrier 13 | $ cargo run --release 14 | ``` 15 | -------------------------------------------------------------------------------- /chap8/8.4/ch8_4_7_barrier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch8_4_7_barrier" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /chap8/8.4/ch8_4_7_barrier/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{channel, Sender}; // ❶ 2 | 3 | fn main() { 4 | let mut v = Vec::new(); 5 | 6 | // 채널을 작성 ❷ 7 | let (tx, rx) = channel::>(); 8 | 9 | // 배리어 동기용 스레드 ❸ 10 | let barrier = move || { 11 | let x = rx.recv().unwrap(); 12 | let y = rx.recv().unwrap(); 13 | let z = rx.recv().unwrap(); 14 | println!("send!"); 15 | x.send(()).unwrap(); 16 | y.send(()).unwrap(); 17 | z.send(()).unwrap(); 18 | }; 19 | let t = std::thread::spawn(barrier); 20 | v.push(t); 21 | 22 | // 클라이언트 스레드 ❹ 23 | for _ in 0..3 { 24 | let tx_c = tx.clone(); // ❺ 25 | let node = move || { 26 | // 배리어 동기 ❻ 27 | let (tx0, rx0) = channel(); 28 | tx_c.send(tx0).unwrap(); 29 | rx0.recv().unwrap(); 30 | println!("received!"); 31 | }; 32 | let t = std::thread::spawn(node); 33 | v.push(t); 34 | } 35 | 36 | for t in v { 37 | t.join().unwrap(); 38 | } 39 | } -------------------------------------------------------------------------------- /chap8/8.4/ch8_4_8_session_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch8_4_8_session_1" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | session_types = { git = "https://github.com/Munksgaard/session-types.git" } 11 | -------------------------------------------------------------------------------- /chap8/8.4/ch8_4_8_session_1/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate session_types; 2 | use session_types as S; // ❶ 3 | use std::thread; 4 | 5 | type Client = S::Send, S::Recv>>; // 클라이언트의 엔드포인트 타입 ❷ 6 | type Server = ::Dual; // 서버의 엔드포인트 타입 ❸ 7 | 8 | enum Op { 9 | Square, // 2제곱 명령 10 | Even, // 짝수 판정 명령 11 | } 12 | 13 | fn server(c: S::Chan<(), Server>) { 14 | let (c, n) = c.recv(); // 데이터 수신 ❶ 15 | match c.offer() { 16 | S::Branch::Left(c) => { // 2제곱 명령 ❷ 17 | c.send(n * n).close(); // ❸ 18 | } 19 | S::Branch::Right(c) => { // 짝수 판정 명령 ❹ 20 | c.send(n & 1 == 0).close(); // ❺ 21 | } 22 | } 23 | } 24 | 25 | fn client(c: S::Chan<(), Client>, n: u64, op: Op) { 26 | let c = c.send(n); // ❶ 27 | match op { 28 | Op::Square => { 29 | let c = c.sel1(); // 1번째 선택지를 선택 ❷ 30 | let (c, val) = c.recv(); // 데이터 수신 ❸ 31 | c.close(); // 세션 종료 ❹ 32 | println!("{}^2 = {}", n, val); 33 | } 34 | Op::Even => { 35 | let c = c.sel2(); // 2번째 선택지를 선택 ❺ 36 | let (c, val) = c.recv(); // 데이터 수신 ❻ 37 | c.close(); // 세션 종료 ❼ 38 | if val { 39 | println!("{} is even", n); 40 | } else { 41 | println!("{} is odd", n); 42 | } 43 | } 44 | }; 45 | } 46 | 47 | fn main() { 48 | // Even 예시 49 | let (server_chan, client_chan) = S::session_channel(); 50 | let srv_t = thread::spawn(move || server(server_chan)); 51 | let cli_t = thread::spawn(move || client(client_chan, 11, Op::Even)); 52 | srv_t.join().unwrap(); 53 | cli_t.join().unwrap(); 54 | 55 | // Square 예시 56 | let (server_chan, client_chan) = S::session_channel(); 57 | let srv_t = thread::spawn(move || server(server_chan)); 58 | let cli_t = thread::spawn(move || client(client_chan, 11, Op::Square)); 59 | srv_t.join().unwrap(); 60 | cli_t.join().unwrap(); 61 | } -------------------------------------------------------------------------------- /chap8/8.4/ch8_4_8_session_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ch8_4_8_session_2" 3 | version = "0.1.0" 4 | authors = ["Yuuki Takano "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | session_types = { git = "https://github.com/Munksgaard/session-types.git" } 11 | -------------------------------------------------------------------------------- /chap8/8.4/ch8_4_8_session_2/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate session_types; 3 | use session_types as S; // ❶ 4 | use std::thread; 5 | use std::collections::HashMap; 6 | 7 | type Put = S::Recv>>; 8 | type Get = S::Recv, S::Var>>; 9 | 10 | type DBServer = S::Rec>>; 11 | type DBClient = ::Dual; 12 | 13 | fn db_server_macro(c: S::Chan<(), DBServer>) { 14 | let mut c_enter = c.enter(); 15 | let mut db = HashMap::new(); 16 | 17 | loop { 18 | let c = c_enter; 19 | offer! {c, // ❶ 20 | Put => { // ❷ 21 | let (c, key) = c.recv(); 22 | let (c, val) = c.recv(); 23 | db.insert(key, val); 24 | c_enter = c.zero(); 25 | }, 26 | Get => { 27 | let (c, key) = c.recv(); 28 | let c = if let Some(val) = db.get(&key) { 29 | c.send(Some(*val)) 30 | } else { 31 | c.send(None) 32 | }; 33 | c_enter = c.zero(); 34 | }, 35 | Quit => { 36 | c.close(); 37 | return; 38 | } 39 | } 40 | } 41 | } 42 | 43 | fn db_server(c: S::Chan<(), DBServer>) { 44 | let mut c_enter = c.enter(); // ❶ 45 | let mut db = HashMap::new(); // DB 데이터 46 | 47 | loop { 48 | match c_enter.offer() { // Put이 선택됨 ❷ 49 | S::Branch::Left(c) => { 50 | let (c, key) = c.recv(); 51 | let (c, val) = c.recv(); 52 | db.insert(key, val); // DB에 데이터 삽입 53 | c_enter = c.zero(); // Rec로 점프 ❸ 54 | } 55 | S::Branch::Right(c) => match c.offer() { // Get or 종료 ❹ 56 | S::Branch::Left(c) => { // Get이 선택됨 ❺ 57 | let (c, key) = c.recv(); 58 | let c = if let Some(val) = db.get(&key) { 59 | c.send(Some(*val)) 60 | } else { 61 | c.send(None) 62 | }; 63 | c_enter = c.zero(); // Rec으로 점프 ❻ 64 | } 65 | S::Branch::Right(c) => { // 종료가 선택됨 ❼ 66 | c.close(); // 세션 클로즈 ❽ 67 | return; 68 | } 69 | }, 70 | } 71 | } 72 | } 73 | 74 | fn db_client(c: S::Chan<(), DBClient>) { 75 | let c = c.enter(); // Rec 안으로 처리를 이행 76 | // Put을 2회 실시 77 | let c = c.sel1().send(10).send(4).zero(); 78 | let c = c.sel1().send(50).send(7).zero(); 79 | 80 | // Get 81 | let (c, val) = c.sel2().sel1().send(10).recv(); 82 | println!("val = {:?}", val); // Some(4) 83 | 84 | let c = c.zero(); // Rec으로 점프 85 | 86 | // Get 87 | let (c, val) = c.sel2().sel1().send(20).recv(); 88 | println!("val = {:?}", val); // None 89 | 90 | // 종료 91 | let _ = c.zero().sel2().sel2().close(); 92 | } 93 | 94 | type SChan = S::Chan<(), S::Send<(), S::Eps>>; // ❶ 95 | type ChanRecv = S::Recv; // ❷ 96 | type ChanSend = ::Dual; 97 | 98 | fn chan_recv(c: S::Chan<(), ChanRecv>) { 99 | let (c, cr) = c.recv(); // 채널 엔드포인트를 수신 ❸ 100 | c.close(); 101 | let cr = cr.send(()); // 수신한 엔드포인트에 대해 송신 ❹ 102 | cr.close(); 103 | } 104 | 105 | fn chan_send(c: S::Chan<(), ChanSend>) { 106 | let (c1, c2) = S::session_channel(); // 채널 생성 107 | let c = c.send(c1); // 채널 엔드포인트를 송신❺ 108 | c.close(); 109 | let (c2, _) = c2.recv(); // 송신한 엔드포인트(端点)의 반대측에서 수신 ❻ 110 | c2.close(); 111 | } 112 | 113 | fn main() { 114 | let (server_chan, client_chan) = S::session_channel(); 115 | let srv_t = thread::spawn(move || db_server(server_chan)); 116 | let cli_t = thread::spawn(move || db_client(client_chan)); 117 | srv_t.join().unwrap(); 118 | cli_t.join().unwrap(); 119 | 120 | println!("--------------------"); 121 | 122 | // 먀크로 이용 예시 123 | let (server_chan, client_chan) = S::session_channel(); 124 | let srv_t = thread::spawn(move || db_server_macro(server_chan)); 125 | let cli_t = thread::spawn(move || db_client(client_chan)); 126 | srv_t.join().unwrap(); 127 | cli_t.join().unwrap(); 128 | 129 | println!("--------------------"); 130 | 131 | // 채널 송수신 이용 예시 132 | let (server_chan, client_chan) = S::session_channel(); 133 | let srv_t = thread::spawn(move || chan_recv(server_chan)); 134 | let cli_t = thread::spawn(move || chan_send(client_chan)); 135 | srv_t.join().unwrap(); 136 | cli_t.join().unwrap(); 137 | } -------------------------------------------------------------------------------- /chap8/README.md: -------------------------------------------------------------------------------- 1 | # 8 병행 계산 모델 2 | 3 | - [8.4 π 계산](./8.4/) 4 | -------------------------------------------------------------------------------- /errata.md: -------------------------------------------------------------------------------- 1 | # 『병행 프로그래밍 입문』정오표 2 | 3 | 아래와 같은 오기가 있어 내용을 정정합니다. 4 | 5 | ## 1쇄 6 | 7 | | 위치 | 수정 전 | 수정 후 | 8 | | ---- | ---- | ---- | 9 | 10 | ## 보충 설명 11 | 12 | ### 3.9 베이커리 알고리즘 13 | 14 | 베이커리 알고리즘의 설명이 부족해 보충 설명한다. 15 | 16 | 베이커리 알고리즘은 아토믹 명령을 사용하지 않고 N개의 스레드 사이에서 Mytex를 실현하는 알고리즘이다. 17 | 18 | 단, N은 고정값이 동적으로 변하지 않는다. 19 | 20 | 21 | P##에서 정의한 `entering`과 `ticket`은 각각 `i`번째의 스레드가 티켓을 얻은 상태인가라는 `i`번째 스레드의 티켓을 저장하는 배열이다. 22 | 23 | 따라서 p## 앞부분에서 정의한 `BakerlyLock` 구조체에는 다음과 같은 주석을 추가하는 편이 좋을 것이다. 24 | 25 | ```rust 26 | // 베이커리 알고리즘용 타입 ❹ 27 | struct BakeryLock { 28 | entering: [bool; NUM_THREADS], // i번째 스레드가 티켓을 획득 중이면 entering[i]는 true 29 | tickets: [Option; NUM_THREADS], // i번째 스레드의 티켓은 ticket[i] 30 | } 31 | ``` 32 | 33 | 자신의 스레드 번호가 i일 때, 그 스레드 i가 록을 획득하려면 티켓 획득, 다른 스레드의 처리 종료 대기라는 2가지를 수행해야 한다. 34 | 35 | 먼저 다음과 같이 티켓을 획득한다. 36 | 37 | 1. `entering[i] = true`로 하고 티켓을 획득 중임을 설정. 38 | 2. `tickets[i]`에서 티켓 번호의 최댓값을 얻음. 39 | 3. 2에서 얻은 최댓값 + 1을 자신의 티켓 번호로 `tickets[i]`에 설정 40 | 4. `entering[i] = false`로 설정 41 | 42 | 이후 다음과 같이 자신 이외의 다른 스레드 `j`(0에서 `NUM_THREADS-1`, 단 `i` 이외)의 처리가 종료되는 것을 대기한다. 구체적인 대기 과정은 다음과 같다. 43 | 44 | 1. `entering[j] == false`이 될 때까지 대기. 즉, 다른 스레드가 티켓을 취득 중이면 대기.. 45 | 2. `ticket[j]`의 값이 자신의 티켓 번호보자 작거나, 티켓 번호가 같고 자신의 스레드 번호가 `j` 볻 작으면 루프로 대기. 46 | 47 | 이 두 가지 처리가 종료되면 록을 획득하게 되며, 3.9에서 나타낸 `lock` 함수가 이를 처리한다. 48 | -------------------------------------------------------------------------------- /figs/dmb_st_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moseskim/concurrent_programming/dfae9be3a563284a0f026fa77983a5251b7e53d4/figs/dmb_st_new.png -------------------------------------------------------------------------------- /figs/dmb_st_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moseskim/concurrent_programming/dfae9be3a563284a0f026fa77983a5251b7e53d4/figs/dmb_st_old.png -------------------------------------------------------------------------------- /figs/llsc_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moseskim/concurrent_programming/dfae9be3a563284a0f026fa77983a5251b7e53d4/figs/llsc_new.png -------------------------------------------------------------------------------- /figs/llsc_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moseskim/concurrent_programming/dfae9be3a563284a0f026fa77983a5251b7e53d4/figs/llsc_old.png -------------------------------------------------------------------------------- /references/머신러닝 실무 프로젝트(2판)_URL 모음.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moseskim/concurrent_programming/dfae9be3a563284a0f026fa77983a5251b7e53d4/references/머신러닝 실무 프로젝트(2판)_URL 모음.pdf -------------------------------------------------------------------------------- /references/머신러닝 실무 프로젝트(2판)_URL 모음.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moseskim/concurrent_programming/dfae9be3a563284a0f026fa77983a5251b7e53d4/references/머신러닝 실무 프로젝트(2판)_URL 모음.xlsx --------------------------------------------------------------------------------