Repository: thanhit95/multi-threading Branch: main Commit: 9557cdf0f2a6 Files: 672 Total size: 824.3 KB Directory structure: gitextract_mqx2volu/ ├── .gitignore ├── LICENSE.txt ├── README.md ├── cpp/ │ ├── .gitignore │ ├── README.md │ ├── cpp-boost/ │ │ ├── demo00.cpp │ │ ├── demo01a01-hello.cpp │ │ ├── demo01a02-hello.cpp │ │ ├── demo01b-hello-class01.cpp │ │ ├── demo01b-hello-class02.cpp │ │ ├── demo01b-hello-class03.cpp │ │ ├── demo01b-hello-functor.cpp │ │ ├── demo02-join.cpp │ │ ├── demo03a-pass-arg.cpp │ │ ├── demo03b-pass-arg.cpp │ │ ├── demo03c-pass-arg.cpp │ │ ├── demo04a-sleep.cpp │ │ ├── demo04b-sleep.cpp │ │ ├── demo05-id.cpp │ │ ├── demo06a-list-threads.cpp │ │ ├── demo06b-list-threads.cpp │ │ ├── demo06c-list-threads.cpp │ │ ├── demo07-terminate.cpp │ │ ├── demo08-return-value.cpp │ │ ├── demo09-detach.cpp │ │ ├── demo10-yield.cpp │ │ ├── demo11a-exec-service.cpp │ │ ├── demo11b-exec-service.cpp │ │ ├── demo12a-race-condition.cpp │ │ ├── demo12b01-data-race-single.cpp │ │ ├── demo12b02-data-race-multi.cpp │ │ ├── demo12c01-race-cond-data-race.cpp │ │ ├── demo12c02-race-cond-data-race.cpp │ │ ├── demo13a-mutex.cpp │ │ ├── demo13b01-mutex.cpp │ │ ├── demo13b02-mutex.cpp │ │ ├── demo13c-mutex-trylock.cpp │ │ ├── demo14-synchronized-block.cpp │ │ ├── demo15a-deadlock.cpp │ │ ├── demo15b-deadlock.cpp │ │ ├── demo16-monitor.cpp │ │ ├── demo17a-reentrant-lock.cpp │ │ ├── demo17b-reentrant-lock.cpp │ │ ├── demo17c-reentrant-lock.cpp │ │ ├── demo18a01-barrier.cpp │ │ ├── demo18a02-barrier.cpp │ │ ├── demo18a03-barrier.cpp │ │ ├── demo18b01-latch.cpp │ │ ├── demo18b02-latch.cpp │ │ ├── demo19a-read-write-lock.cpp │ │ ├── demo19b-read-write-lock.cpp │ │ ├── demo20a01-semaphore.cpp │ │ ├── demo20a02-semaphore.cpp │ │ ├── demo20a03-semaphore-deadlock.cpp │ │ ├── demo20b-semaphore.cpp │ │ ├── demo21a01-condition-variable.cpp │ │ ├── demo21a02-condition-variable.cpp │ │ ├── demo21a03-condition-variable.cpp │ │ ├── demo21b-condition-variable.cpp │ │ ├── demo22a-blocking-queue.cpp │ │ ├── demo22b-blocking-queue.cpp │ │ ├── demo23a-thread-local.cpp │ │ ├── demo23b-thread-local.cpp │ │ ├── demo24-volatile.cpp │ │ ├── demo25a-atomic.cpp │ │ ├── demo25b-atomic.cpp │ │ ├── demoex-async-future.cpp │ │ ├── exer01a-max-div.cpp │ │ ├── exer01b-max-div.cpp │ │ ├── exer01c-max-div.cpp │ │ ├── exer02a01-producer-consumer.cpp │ │ ├── exer02a02-producer-consumer.cpp │ │ ├── exer02a03-producer-consumer.cpp │ │ ├── exer02a04-producer-consumer.cpp │ │ ├── exer02b01-producer-consumer.cpp │ │ ├── exer02b02-producer-consumer.cpp │ │ ├── exer02b03-producer-consumer.cpp │ │ ├── exer02b04-producer-consumer.cpp │ │ ├── exer02c-producer-consumer.cpp │ │ ├── exer03a-readers-writers.cpp │ │ ├── exer03b-readers-writers.cpp │ │ ├── exer04-dining-philosophers.cpp │ │ ├── exer05-util.hpp │ │ ├── exer05a-product-matrix-vector.cpp │ │ ├── exer05b-product-matrix-matrix.cpp │ │ ├── exer06a-blocking-queue.cpp │ │ ├── exer06b01-blocking-queue.cpp │ │ ├── exer06b02-blocking-queue.cpp │ │ ├── exer07a-data-server.cpp │ │ ├── exer07b-data-server.cpp │ │ ├── exer07c-data-server.cpp │ │ ├── exer07d-data-server.cpp │ │ ├── exer08-exec-service-itask.hpp │ │ ├── exer08-exec-service-main.cpp │ │ ├── exer08-exec-service-v0a.hpp │ │ ├── exer08-exec-service-v0b.hpp │ │ ├── exer08-exec-service-v1a.hpp │ │ ├── exer08-exec-service-v1b.hpp │ │ ├── exer08-exec-service-v2a.hpp │ │ ├── exer08-exec-service-v2b.hpp │ │ ├── mylib-blockingqueue.hpp │ │ ├── mylib-random.hpp │ │ ├── mylib-semaphore.hpp │ │ └── mylib-time.hpp │ ├── cpp-pthread/ │ │ ├── demo00.cpp │ │ ├── demo01-hello.cpp │ │ ├── demo02-join.cpp │ │ ├── demo03a01-pass-arg.cpp │ │ ├── demo03a02-pass-arg.cpp │ │ ├── demo03b01-pass-arg.cpp │ │ ├── demo03b02-pass-arg.cpp │ │ ├── demo04-sleep.cpp │ │ ├── demo05-id.cpp │ │ ├── demo06a-list-threads.cpp │ │ ├── demo06b-list-threads.cpp │ │ ├── demo07a-terminate.cpp │ │ ├── demo07b-terminate.cpp │ │ ├── demo08a-return-value.cpp │ │ ├── demo08b-return-value.cpp │ │ ├── demo09a-detach.cpp │ │ ├── demo09b-detach.cpp │ │ ├── demo10-yield.cpp │ │ ├── demo11a-exec-service.cpp │ │ ├── demo11b-exec-service.cpp │ │ ├── demo12a-race-condition.cpp │ │ ├── demo12b01-data-race-single.cpp │ │ ├── demo12b02-data-race-multi.cpp │ │ ├── demo12bex-data-race-fork.cpp │ │ ├── demo12c01-race-cond-data-race.cpp │ │ ├── demo12c02-race-cond-data-race.cpp │ │ ├── demo13a-mutex.cpp │ │ ├── demo13b-mutex-trylock.cpp │ │ ├── demo14-synchronized-block.cpp │ │ ├── demo15a-deadlock.cpp │ │ ├── demo15b-deadlock.cpp │ │ ├── demo16-monitor.cpp │ │ ├── demo17a-reentrant-lock.cpp │ │ ├── demo17b-reentrant-lock.cpp │ │ ├── demo17c-reentrant-lock.cpp │ │ ├── demo18a01-barrier.cpp │ │ ├── demo18a02-barrier.cpp │ │ ├── demo18a03-barrier.cpp │ │ ├── demo18b01-latch.cpp │ │ ├── demo18b02-latch.cpp │ │ ├── demo19-read-write-lock.cpp │ │ ├── demo20a01-semaphore.cpp │ │ ├── demo20a02-semaphore.cpp │ │ ├── demo20a03-semaphore-deadlock.cpp │ │ ├── demo20b-semaphore.cpp │ │ ├── demo21a01-condition-variable.cpp │ │ ├── demo21a02-condition-variable.cpp │ │ ├── demo21a03-condition-variable.cpp │ │ ├── demo21b-condition-variable.cpp │ │ ├── demo22a-blocking-queue.cpp │ │ ├── demo22b-blocking-queue.cpp │ │ ├── demo23a-thread-local.cpp │ │ ├── demo23b-thread-local.cpp │ │ ├── demo24-volatile.cpp │ │ ├── demo25a-atomic.c │ │ ├── demo25b-atomic.c │ │ ├── demoex-attribute.cpp │ │ ├── demoex-oop.cpp │ │ ├── demoex-signal.cpp │ │ ├── exer01a-max-div.cpp │ │ ├── exer01b-max-div.cpp │ │ ├── exer01c-max-div.cpp │ │ ├── exer02a01-producer-consumer.cpp │ │ ├── exer02a02-producer-consumer.cpp │ │ ├── exer02a03-producer-consumer.cpp │ │ ├── exer02a04-producer-consumer.cpp │ │ ├── exer02b01-producer-consumer.cpp │ │ ├── exer02b02-producer-consumer.cpp │ │ ├── exer02b03-producer-consumer.cpp │ │ ├── exer02b04-producer-consumer.cpp │ │ ├── exer02c-producer-consumer.cpp │ │ ├── exer03a-readers-writers.cpp │ │ ├── exer03b-readers-writers.cpp │ │ ├── exer04-dining-philosophers.cpp │ │ ├── exer05-util.hpp │ │ ├── exer05a-product-matrix-vector.cpp │ │ ├── exer05b-product-matrix-matrix.cpp │ │ ├── exer06a-blocking-queue.cpp │ │ ├── exer06b01-blocking-queue.cpp │ │ ├── exer06b02-blocking-queue.cpp │ │ ├── exer07a-data-server.cpp │ │ ├── exer07b-data-server.cpp │ │ ├── exer07c-data-server.cpp │ │ ├── exer07d-data-server.cpp │ │ ├── exer08-exec-service-itask.hpp │ │ ├── exer08-exec-service-main.cpp │ │ ├── exer08-exec-service-v0a.hpp │ │ ├── exer08-exec-service-v0b.hpp │ │ ├── exer08-exec-service-v1a.hpp │ │ ├── exer08-exec-service-v1b.hpp │ │ ├── exer08-exec-service-v2a.hpp │ │ ├── exer08-exec-service-v2b.hpp │ │ ├── exerex-countdown-timer-a.cpp │ │ ├── exerex-countdown-timer-b.cpp │ │ ├── mylib-blockingqueue.hpp │ │ ├── mylib-execservice.hpp │ │ └── mylib-latch.hpp │ └── cpp-std/ │ ├── demo00.cpp │ ├── demo01a01-hello.cpp │ ├── demo01a02-hello.cpp │ ├── demo01b-hello-class01.cpp │ ├── demo01b-hello-class02.cpp │ ├── demo01b-hello-class03.cpp │ ├── demo01b-hello-functor.cpp │ ├── demo01c-hello-lambda.cpp │ ├── demo02-join.cpp │ ├── demo03a-pass-arg.cpp │ ├── demo03b-pass-arg.cpp │ ├── demo03c-pass-arg.cpp │ ├── demo04a-sleep.cpp │ ├── demo04b-sleep.cpp │ ├── demo05-id.cpp │ ├── demo06a-list-threads.cpp │ ├── demo06b-list-threads.cpp │ ├── demo07-terminate.cpp │ ├── demo08a-return-value.cpp │ ├── demo08b-return-value.cpp │ ├── demo08c-return-value.cpp │ ├── demo09-detach.cpp │ ├── demo10-yield.cpp │ ├── demo11a-exec-service.cpp │ ├── demo11b-exec-service.cpp │ ├── demo12a-race-condition.cpp │ ├── demo12b01-data-race-single.cpp │ ├── demo12b02-data-race-multi.cpp │ ├── demo12c01-race-cond-data-race.cpp │ ├── demo12c02-race-cond-data-race.cpp │ ├── demo13a-mutex.cpp │ ├── demo13b01-mutex.cpp │ ├── demo13b02-mutex.cpp │ ├── demo13c-mutex-trylock.cpp │ ├── demo14-synchronized-block.cpp │ ├── demo15a-deadlock.cpp │ ├── demo15b-deadlock.cpp │ ├── demo16-monitor.cpp │ ├── demo17a-reentrant-lock.cpp │ ├── demo17b-reentrant-lock.cpp │ ├── demo17c-reentrant-lock.cpp │ ├── demo18a01-barrier.cpp │ ├── demo18a02-barrier.cpp │ ├── demo18a03-barrier.cpp │ ├── demo18b01-latch.cpp │ ├── demo18b02-latch.cpp │ ├── demo19a-read-write-lock.cpp │ ├── demo19b-read-write-lock.cpp │ ├── demo20a01-semaphore.cpp │ ├── demo20a02-semaphore.cpp │ ├── demo20a03-semaphore-deadlock.cpp │ ├── demo20b-semaphore.cpp │ ├── demo21a01-condition-variable.cpp │ ├── demo21a02-condition-variable.cpp │ ├── demo21a03-condition-variable.cpp │ ├── demo21b-condition-variable.cpp │ ├── demo22a-blocking-queue.cpp │ ├── demo22b-blocking-queue.cpp │ ├── demo23a01-thread-local.cpp │ ├── demo23a02-thread-local.cpp │ ├── demo23b-thread-local.cpp │ ├── demo24-volatile.cpp │ ├── demo25a-atomic.cpp │ ├── demo25b-atomic.cpp │ ├── demo25c-atomic-gcc.cpp │ ├── demoex-async-future.cpp │ ├── demoex-jthread.cpp │ ├── exer01a-max-div.cpp │ ├── exer01b-max-div.cpp │ ├── exer01c-max-div.cpp │ ├── exer02a01-producer-consumer.cpp │ ├── exer02a02-producer-consumer.cpp │ ├── exer02a03-producer-consumer.cpp │ ├── exer02a04-producer-consumer.cpp │ ├── exer02b01-producer-consumer.cpp │ ├── exer02b02-producer-consumer.cpp │ ├── exer02b03-producer-consumer.cpp │ ├── exer02b04-producer-consumer.cpp │ ├── exer02c-producer-consumer.cpp │ ├── exer03a-readers-writers.cpp │ ├── exer03b-readers-writers.cpp │ ├── exer04-dining-philosophers.cpp │ ├── exer05-util.hpp │ ├── exer05a-product-matrix-vector.cpp │ ├── exer05b-product-matrix-matrix.cpp │ ├── exer06a-blocking-queue.cpp │ ├── exer06b01-blocking-queue.cpp │ ├── exer06b02-blocking-queue.cpp │ ├── exer07a-data-server.cpp │ ├── exer07b-data-server.cpp │ ├── exer07c-data-server.cpp │ ├── exer07d-data-server.cpp │ ├── exer08-exec-service-itask.hpp │ ├── exer08-exec-service-main.cpp │ ├── exer08-exec-service-v0a.hpp │ ├── exer08-exec-service-v0b.hpp │ ├── exer08-exec-service-v1a.hpp │ ├── exer08-exec-service-v1b.hpp │ ├── exer08-exec-service-v2a.hpp │ ├── exer08-exec-service-v2b.hpp │ ├── exerex-countdown-timer.cpp │ ├── mylib-blockingqueue.hpp │ ├── mylib-execservice.hpp │ ├── mylib-random.hpp │ └── mylib-time.hpp ├── csharp/ │ ├── .gitignore │ ├── IRunnable.cs │ ├── Program.cs │ ├── demo/ │ │ ├── demo00-intro.cs │ │ ├── demo01a-hello.cs │ │ ├── demo01b01-hello.cs │ │ ├── demo01b02-hello.cs │ │ ├── demo01ex-name.cs │ │ ├── demo02a-join.cs │ │ ├── demo02b-join.cs │ │ ├── demo03a-pass-arg.cs │ │ ├── demo03b-pass-arg.cs │ │ ├── demo03c-pass-arg.cs │ │ ├── demo03d-pass-arg.cs │ │ ├── demo04-sleep.cs │ │ ├── demo05-id.cs │ │ ├── demo06a-list-threads.cs │ │ ├── demo06b-list-threads.cs │ │ ├── demo07-terminate.cs │ │ ├── demo08a-return-value.cs │ │ ├── demo08b-return-value.cs │ │ ├── demo09-detach.cs │ │ ├── demo10-yield.cs │ │ ├── demo11a01-exec-service.cs │ │ ├── demo11a02-exec-service.cs │ │ ├── demo11a03-exec-service.cs │ │ ├── demo11a04-exec-service.cs │ │ ├── demo11a05-exec-service.cs │ │ ├── demo11b01-exec-service-parallel.cs │ │ ├── demo11b02-exec-service-parallel.cs │ │ ├── demo11c-exec-service.cs │ │ ├── demo12a-race-condition.cs │ │ ├── demo12b01-data-race-single.cs │ │ ├── demo12b02-data-race-multi.cs │ │ ├── demo12c01-race-cond-data-race.cs │ │ ├── demo12c02-race-cond-data-race.cs │ │ ├── demo13a-mutex.cs │ │ ├── demo13b-mutex-trylock.cs │ │ ├── demo14-synchronized-block.cs │ │ ├── demo15a-deadlock.cs │ │ ├── demo15b-deadlock.cs │ │ ├── demo16-monitor.cs │ │ ├── demo17a-reentrant-lock.cs │ │ ├── demo17b-reentrant-lock.cs │ │ ├── demo18a01-barrier.cs │ │ ├── demo18a03-barrier.cs │ │ ├── demo18b01-latch.cs │ │ ├── demo18b02-latch.cs │ │ ├── demo19-read-write-lock.cs │ │ ├── demo20a01-semaphore.cs │ │ ├── demo20a02-semaphore.cs │ │ ├── demo20a03-semaphore-deadlock.cs │ │ ├── demo20b-semaphore.cs │ │ ├── demo21a01-condition-variable.cs │ │ ├── demo21a02-condition-variable.cs │ │ ├── demo21a03-condition-variable.cs │ │ ├── demo21b-condition-variable.cs │ │ ├── demo22a-blocking-queue.cs │ │ ├── demo22b-blocking-queue.cs │ │ ├── demo23a01-thread-local.cs │ │ ├── demo23a02-thread-local.cs │ │ ├── demo23b-thread-local.cs │ │ ├── demo24-volatile.cs │ │ ├── demo25a-atomic.cs │ │ └── demo25b-atomic.cs │ ├── demoex/ │ │ ├── demoex-async-future-a01.cs │ │ ├── demoex-async-future-a02.cs │ │ ├── demoex-async-future-a03.cs │ │ ├── demoex-async-future-a04.cs │ │ ├── demoex-async-future-a05.cs │ │ ├── demoex-async-future-b01.cs │ │ ├── demoex-async-future-b02.cs │ │ ├── demoex-async-future-b03.cs │ │ ├── demoex-async-future-b04.cs │ │ ├── demoex-async-future-c01.cs │ │ └── demoex-async-future-c02.cs │ ├── exercise/ │ │ ├── exer01a-max-div.cs │ │ ├── exer01b-max-div.cs │ │ ├── exer01c-max-div.cs │ │ ├── exer02a01-producer-consumer.cs │ │ ├── exer02a02-producer-consumer.cs │ │ ├── exer02a03-producer-consumer.cs │ │ ├── exer02a04-producer-consumer.cs │ │ ├── exer02b01-producer-consumer.cs │ │ ├── exer02b02-producer-consumer.cs │ │ ├── exer02b03-producer-consumer.cs │ │ ├── exer02b04-producer-consumer.cs │ │ ├── exer02c-producer-consumer.cs │ │ ├── exer03a-readers-writers.cs │ │ ├── exer03b-readers-writers.cs │ │ ├── exer04a-dining-philosophers.cs │ │ ├── exer04b-dining-philosophers.cs │ │ ├── exer05-util.cs │ │ ├── exer05a-product-matrix-vector.cs │ │ ├── exer05b-product-matrix-matrix.cs │ │ ├── exer06a-blocking-queue.cs │ │ ├── exer06b01-blocking-queue.cs │ │ ├── exer06b02-blocking-queue.cs │ │ ├── exer07a-data-server.cs │ │ ├── exer07b-data-server.cs │ │ ├── exer07c-data-server.cs │ │ ├── exer07d-data-server.cs │ │ ├── exer08-exec-service-main.cs │ │ ├── exer08-exec-service-v0a.cs │ │ ├── exer08-exec-service-v0b.cs │ │ ├── exer08-exec-service-v1a.cs │ │ ├── exer08-exec-service-v1b.cs │ │ ├── exer08-exec-service-v2a.cs │ │ └── exer08-exec-service-v2b.cs │ ├── multithreading.csproj │ └── multithreading.sln ├── java/ │ ├── .gitignore │ ├── README.md │ └── src/ │ ├── demo00_intro/ │ │ └── App.java │ ├── demo01_hello/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ ├── AppB01.java │ │ ├── AppB02.java │ │ ├── AppB03.java │ │ ├── AppC01.java │ │ ├── AppC02.java │ │ └── AppExtra.java │ ├── demo02_join/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── demo03_pass_arg/ │ │ ├── AppA.java │ │ ├── AppB.java │ │ └── AppC.java │ ├── demo04_sleep/ │ │ └── App.java │ ├── demo05_id/ │ │ └── App.java │ ├── demo06_list_threads/ │ │ ├── AppA.java │ │ ├── AppB01.java │ │ └── AppB02.java │ ├── demo07_terminate/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── demo08_return_value/ │ │ ├── AppA.java │ │ ├── AppB.java │ │ └── AppC.java │ ├── demo09_detach/ │ │ └── App.java │ ├── demo10_yield/ │ │ └── App.java │ ├── demo11_exec_service/ │ │ ├── AppA.java │ │ ├── AppB01.java │ │ ├── AppB02.java │ │ ├── AppC01.java │ │ ├── AppC02.java │ │ ├── AppC03.java │ │ ├── AppC04.java │ │ ├── AppC05.java │ │ ├── AppC06.java │ │ └── AppExtra.java │ ├── demo12_race_condition/ │ │ ├── AppA.java │ │ ├── AppB01.java │ │ ├── AppB02.java │ │ ├── AppC01.java │ │ └── AppC02.java │ ├── demo13_mutex/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── demo14_synchronized_block/ │ │ ├── AppA.java │ │ ├── AppB01.java │ │ ├── AppB02.java │ │ └── AppC.java │ ├── demo15_deadlock/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── demo16_monitor/ │ │ └── App.java │ ├── demo17_reentrant_lock/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ ├── AppB01.java │ │ └── AppB02.java │ ├── demo18_barrier_latch/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ ├── AppA03.java │ │ ├── AppB01.java │ │ └── AppB02.java │ ├── demo19_read_write_lock/ │ │ └── App.java │ ├── demo20_semaphore/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ ├── AppA03.java │ │ └── AppB.java │ ├── demo21_condition_variable/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ ├── AppA03.java │ │ └── AppB.java │ ├── demo22_blocking_queue/ │ │ ├── AppA.java │ │ ├── AppB.java │ │ └── AppExtra.java │ ├── demo23_thread_local/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ └── AppB.java │ ├── demo24_volatile/ │ │ └── App.java │ ├── demo25_atomic/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── demoex/ │ │ └── async/ │ │ ├── AppA.java │ │ ├── AppB01.java │ │ ├── AppB02.java │ │ ├── AppB03.java │ │ ├── AppB04.java │ │ ├── AppC01.java │ │ └── AppC02.java │ ├── exer01_max_div/ │ │ ├── AppA.java │ │ ├── AppB.java │ │ └── AppC.java │ ├── exer02_producer_consumer/ │ │ ├── AppA01.java │ │ ├── AppA02.java │ │ ├── AppA03.java │ │ ├── AppA04.java │ │ ├── AppB01.java │ │ ├── AppB02.java │ │ ├── AppB03.java │ │ ├── AppB04.java │ │ └── AppC.java │ ├── exer03_readers_writers/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── exer04_dining_philosophers/ │ │ ├── AppA.java │ │ └── AppB.java │ ├── exer05_product_matrix/ │ │ ├── AppA.java │ │ ├── AppB.java │ │ └── MyUtil.java │ ├── exer06_blocking_queue/ │ │ ├── AppA.java │ │ ├── AppB01.java │ │ └── AppB02.java │ ├── exer07_data_server/ │ │ ├── AppA.java │ │ ├── AppB.java │ │ ├── AppC.java │ │ └── AppD.java │ └── exer08_exec_service/ │ ├── App.java │ ├── MyExecServiceV0A.java │ ├── MyExecServiceV0B.java │ ├── MyExecServiceV1A.java │ ├── MyExecServiceV1B.java │ ├── MyExecServiceV2A.java │ └── MyExecServiceV2B.java ├── js-nodejs/ │ ├── .gitignore │ ├── demo00.js │ ├── demo01a-hello.js │ ├── demo01b-hello-worker.js │ ├── demo01b-hello.js │ ├── demo02-join.js │ ├── demo03a-pass-arg.js │ ├── demo03b-pass-arg.js │ ├── demo04-sleep.js │ ├── demo05-id.js │ ├── demo06a-list-threads.js │ ├── demo06b-list-threads.js │ ├── demo07-terminate.js │ ├── demo08a-return-value.js │ ├── demo08b-return-value.js │ ├── demo11a-exec-service.js │ ├── demo11b-exec-service.js │ ├── demo12-race-condition.js │ ├── demo12b01-data-race-single.js │ ├── demo12b02-data-race-multi.js │ ├── demo12c-race-cond-data-race.js │ ├── demo13-mutex.js │ ├── demo13ex-mutex-problem.js │ ├── demo13ex-mutex-solve.js │ ├── demo15a-deadlock.js │ ├── demo15b-deadlock.js │ ├── demo15ex-deadlock.js │ ├── demo25-atomic.js │ ├── exer01a-max-div.js │ ├── exer01c-max-div.js │ ├── exerex-userhash-problem.js │ ├── exerex-userhash-solve01.js │ ├── exerex-userhash-solve02-faster.js │ ├── exerex-userhash-solve03-faster.js │ ├── exerex-userhash-util.js │ ├── mylib.js │ ├── mylib_mutex.js │ └── package.json ├── notes-articles.md ├── notes-demos-exercises.md ├── old/ │ ├── cpppthread-reentrant-lock-a.cpp │ ├── cpppthread-reentrant-lock-b.cpp │ ├── cppstd-data-race.cpp │ ├── cppstd-reentrant-lock-a.cpp │ └── cppstd-reentrant-lock-b.cpp ├── python/ │ ├── .gitignore │ ├── demo00.py │ ├── demo01_hello.py │ ├── demo01ex_name.py │ ├── demo02a_join.py │ ├── demo02b_join.py │ ├── demo03a_pass_arg.py │ ├── demo03b_pass_arg.py │ ├── demo04_sleep.py │ ├── demo05_id.py │ ├── demo06_list_threads.py │ ├── demo07_terminate.py │ ├── demo08a_return_value.py │ ├── demo08b_return_value.py │ ├── demo09_detach.py │ ├── demo10_yield.py │ ├── demo11a_exec_service.py │ ├── demo11b_exec_service.py │ ├── demo11c01_exec_service.py │ ├── demo11c02_exec_service.py │ ├── demo12a_race_condition.py │ ├── demo12b01_data_race_single.py │ ├── demo12b02_data_race_multi.py │ ├── demo12c01_race_cond_data_race.py │ ├── demo12c02_race_cond_data_race.py │ ├── demo13a_mutex.py │ ├── demo14_synchronized_block.py │ ├── demo15a_deadlock.py │ ├── demo15b_deadlock.py │ ├── demo16_monitor.py │ ├── demo17a_reentrant_lock.py │ ├── demo17b_reentrant_lock.py │ ├── demo17c_reentrant_lock.py │ ├── demo18a01_barrier.py │ ├── demo18a02_barrier.py │ ├── demo18a03_barrier.py │ ├── demo18b01_latch.py │ ├── demo18b02_latch.py │ ├── demo19_read_write_lock.py │ ├── demo20a01_semaphore.py │ ├── demo20a02_semaphore.py │ ├── demo20a03_semaphore_deadlock.py │ ├── demo20b_semaphore.py │ ├── demo21a01_condition_variable.py │ ├── demo21a02_condition_variable.py │ ├── demo21a03_condition_variable.py │ ├── demo21b_condition_variable.py │ ├── demo22a_blocking_queue.py │ ├── demo22b_blocking_queue.py │ ├── demo23a_thread_local.py │ ├── demo23b_thread_local.py │ ├── demo24_volatile.py │ ├── demo25_atomic.py │ ├── demoex_event.py │ ├── demoex_timer.py │ ├── exer01a_max_div.py │ ├── exer01b_max_div.py │ ├── exer01c_max_div.py │ ├── exer02a01_producer_consumer.py │ ├── exer02a02_producer_consumer.py │ ├── exer02a03_producer_consumer.py │ ├── exer02a04_producer_consumer.py │ ├── exer02b01_producer_consumer.py │ ├── exer02b02_producer_consumer.py │ ├── exer02b03_producer_consumer.py │ ├── exer02b04_producer_consumer.py │ ├── exer02c_producer_consumer.py │ ├── exer03a_readers_writers.py │ ├── exer03b_readers_writers.py │ ├── exer04_dining_philosophers.py │ ├── exer05a_product_matrix_vector.py │ ├── exer05b_product_matrix_vector.py │ ├── exer06a_blocking_queue.py │ ├── exer06b01_blocking_queue.py │ ├── exer06b02_blocking_queue.py │ ├── exer07a_data_server.py │ ├── exer07b_data_server.py │ ├── exer07c_data_server.py │ ├── exer07d_data_server.py │ ├── exer08_exec_service_itask.py │ ├── exer08_exec_service_main.py │ ├── exer08_exec_service_v0a.py │ ├── exer08_exec_service_v0b.py │ ├── exer08_exec_service_v1a.py │ ├── exer08_exec_service_v1b.py │ ├── exer08_exec_service_v2a.py │ ├── exer08_exec_service_v2b.py │ ├── mylib_latch.py │ ├── mylib_rwlock.py │ └── mylib_rwlock2.py └── references.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .vscode/ ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2021, Thanh Nguyen, thanh.it1995 (at) gmail.com All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Thanh Nguyen nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # MULTIPLE THREADING IN PRACTICE ## DESCRIPTION This repo helps you to practise multithreading in a logical sequence, which is divided into several demonstrations. Plus, you could apply your learning better by doing exercises. The repo consists of two main sections: - "demo" (demostrations). - "exer" (exercises). All the demos (and exers) are really simple, easy to understand, even for difficult terms. If you find it helpful, please give my repo a star. Thank you.   ## AUTHOR & LICENSE Author: Thanh Nguyen - Email: thanh.it1995@gmail.com - Facebook: This repo is licensed under the [3-Clause BSD License](LICENSE.txt).   ## LANGUAGES SUPPORTED | Directory name | Description | | -------------- | ---------------------------- | | `cpp-std` | C++20 std threading | | `cpp-pthread` | C++11 POSIX threading | | `cpp-boost` | C++98 Boost threading | | `csharp` | C# 7.3 with Dot Net 6 | | `java` | Java JDK 17 | | `python` | Python 3.10 | | `js-nodejs` | Javascript ES2019/Nodejs 18 | Special notes for C++ demos/exers: Please read the specified `readme.md` in corresponding directory.   ## THE NOTES AND ARTICLES The notes and articles are the additional resources for the source code, which guides you for better research, step by step. You may consider it the comment/description at the beginning of the source code. ```text ORIGINAL SOURCE CODE FILE SOURCE CODE FILE NOTES AND ARTICLES ------------------------------ ------------------------------ ---------------------- | | | | | | | /* THE COMMENTS... */ | | | | THE COMMENTS | | | | | | | | #include | | #include | | | | using namespace std; | | using namespace std; | | | | | ===> | | + | | | int main() { | | int main() { | | | | cout << "Hello thread"; | | cout << "Hello thread"; | | | | return 0; | | return 0; | | | | } | | } | | | | | | | | | ------------------------------ ------------------------------ ---------------------- ``` There are 2 notes: - [notes-demos-exercises.md](notes-demos-exercises.md): The notes that go along with original source code. - [notes-articles.md](notes-articles.md): Extra helpful notes during my research.   **For your best result, I strongly recommend that you read [notes-demos-exercises.md](notes-demos-exercises.md) while enjoying source code (demos and exercises).**   ## ROADMAP FOR THE LEARNERS This is the roadmap for you, which is composed and researched carefully with all my heart. You should learn in the sequence listed below. **If you just want to learn the basis to understand the taste of multithreading:** - Demo: hello, join, pass arg, sleep, list-threads, race-condition, mutex, synchronized-block. - Exer: max-div. **If you are oriented to be a Software Developer:** - Demo: hello, join, pass arg, sleep, list-threads, terminate, return-value, exec-service, race-condition, mutex, synchronized-block, deadlock, blocking-queue, atomic. - Exer: max-div, producer-consumer, product-matrix, data-server. **If you really want to do an in-depth research:** Learn all!!!   --- ## INTRODUCTION TO MULTITHREADING ### GETTING STARTED Bob sends four messages to Alice: `I love`, `you`, `not`, `her`. Surprisingly, Alice receives `I love`, `her`, `not`, `you` (That means "I love her not you"). So sad! ```text TRADITIONAL (ONE THREAD) ===========================================> Time Main thread ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~> "I love" "you" "not" "her" MULTITHREADING (FOUR THREADS) ===========================================> Time ~~~~~~~~~~~~> "I love" ~~~~~~~~~~~~> "you" ~~~~~~~~~~~~> "not" ~~~~~~~~~~~~> "her" ``` If you use multithreading or something similar, the context above is truly possible. The reason is that multithreading allows four messages to be sent in parallel, so message order is changed unpredictably when they come to Alice. In a traditional simple app, there is only one thread (the "main thread"). If you apply multithreading then your app may have multiple threads (including the "main thread"). By learning multithreading: - You get closer to the operating system. - You can understand various terms: concurrency, parallel, asynchronous, synchronization. - You have additional knowledge to learn asynchronous programming and parallel programming. So, why multithreading? ### WHY MULTITHREADING Multithreaded programs can improve performance compared to traditional simple programs (which use only a single thread). Multithreading is used as an underlying technique in various fields: - Web browsers (Chrome, Edge, Firefox...). - Web servers. - Graphic editors (Adobe Photoshop, Corel Draw...). - Computer games. - Database management systems. - Networking programming. - Video encoders. - And more... Benefits of multithreading: - Improving application responsiveness. - Any program in which many activities are not dependent upon each other can be redesigned so that each activity is defined as a thread. For example, the user of a multithreaded GUI does not have to wait for one activity to complete before starting another. - Using multiprocessors efficiently. - Typically, applications that express concurrency requirements with threads need not take into account the number of available processors. The performance of the application improves transparently with additional processors. - Numerical algorithms and applications with a high degree of parallelism, such as matrix multiplications, can run much faster when implemented with threads on a multiprocessor. - Improving throughput. - Many concurrent compute operations and I/O requests within a single process. - Program structure simplification. - Threads can be used to simplify the structure of complex applications, such as server-class and multimedia applications. Simple routines can be written for each activity, making complex programs easier to design and code, and more adaptive to a wide variation in user demands. - Using fewer system resources. - Threads impose minimal impact on system resources. Threads require less overhead to create, maintain, and manage than a traditional process. - Better communication. - Thread synchronization functions can be used to provide enhanced process-to-process communication. - In addition, sharing large amounts of data through separate threads of execution within the same address space provides extremely high-bandwidth, low-latency communication between separate tasks within an application.   If you want to explore more articles, read here: [notes-articles.md](notes-articles.md).   Article references: - [Oracle Documentation Home, Multithreaded Programming Guide, Chapter 1 Covering Multithreading Basics, Benefiting From Multithreading](https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032d/index.html) - [Oracle Documentation Home, JDK 1.1 for Solaris Developer's Guide, Chapter 2 Multithreading, Benefits of Multithreading](https://docs.oracle.com/cd/E19455-01/806-3461/6jck06gqj/index.html)   --- ## REFERENCES All general references in my repo. Read here: [references.md](references.md). ================================================ FILE: cpp/.gitignore ================================================ a a.out tmp* ================================================ FILE: cpp/README.md ================================================ # C++ MULTITHREADING ## DESCRIPTION Multithreading in C++.   ## PROJECT STRUCTURE | Directory name | Description | Notes | | -------------- | --------------------- | ----- | | `cpp-std` | C++20 std threading | Most source code files are in C++11. Some features require newer standard. | | `cpp-pthread` | C++11 POSIX threading | | | `cpp-boost` | C++98 Boost threading | |   ## COMPILATION Ensure that your compiler meets the C++ standard as mentioned above. ### gcc compiler To compile with specified C++ standard, use option `-std`: - C++98: `g++ -o exec_filename filename.cpp -std=c++98` - C++11: `g++ -o exec_filename filename.cpp -std=c++11` - C++20: `g++ -o exec_filename filename.cpp -std=c++20` Usually in Linux/Unix environments, we shall use POSIX threading. This leads to linking objects with pthread by option `-lpthread`. Additionally, if you use Boost: - `-lboost_thread` for all code. - `-lboost_chrono` for the code using boost::chrono. - `-lboost_random` for the code using boost::random.   Example 1: ```shell # Compile g++ -o output_exe demo00.cpp -lpthread -std=c++20 # Run ./output_exe ``` Example 2 for lib Boost: ```shell # Compile g++ -o output_exe demo04a-sleep.cpp -lpthread -lboost_thread -lboost_chrono # Run ./output_exe ```   ### Other compilers and/or environments You may consider a suitable IDE/compiler (e.g. Microsoft Visual Studio, mingw...). ================================================ FILE: cpp/cpp-boost/demo00.cpp ================================================ /* INTRODUCTION TO MULTITHREADING You should try running this app several times and see results. */ #include #include using namespace std; void doTask() { for (int i = 0; i < 300; ++i) cout << "B"; } int main() { boost::thread th(&doTask); for (int i = 0; i < 300; ++i) cout << "A"; th.join(); cout << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo01a01-hello.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version A01: Using functions */ #include #include using namespace std; void doTask() { cout << "Hello from example thread" << endl; } int main() { boost::thread th(&doTask); cout << "Hello from main thread" << endl; th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo01a02-hello.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version A02: Using functions allowing passing 2 arguments */ #include #include using namespace std; void doTask(char const* message, int number) { cout << message << " " << number << endl; } int main() { boost::thread th(&doTask, "Good day", 19); th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo01b-hello-class01.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using class methods */ #include #include #include using namespace std; class Example { public: void doTask(string message) { cout << message << endl; } }; int main() { Example example; boost::thread th(&Example::doTask, &example, "Good day"); // boost::thread th(boost::bind(&Example::doTask, &example, "Good day")); th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo01b-hello-class02.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using class methods */ #include #include #include #include using namespace std; class Example { public: void run() { boost::thread th(&Example::doTask, this, "Good day"); // boost::thread th(boost::bind(&Example::doTask, this, "Good day")); th.join(); } private: void doTask(string message) { cout << message << endl; } }; int main() { Example example; example.run(); return 0; } ================================================ FILE: cpp/cpp-boost/demo01b-hello-class03.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using class methods */ #include #include #include using namespace std; class Example { public: void run() { boost::thread th(&Example::doTask, "Good day"); th.join(); } private: static void doTask(string message) { cout << message << endl; } }; int main() { Example example; example.run(); return 0; } ================================================ FILE: cpp/cpp-boost/demo01b-hello-functor.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using functors */ #include #include #include using namespace std; class Example { public: void operator()(string message) { cout << message << endl; } }; int main() { Example example; boost::thread th(example, "Good day"); th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo02-join.cpp ================================================ /* THREAD JOINS */ #include #include using namespace std; void doHeavyTask() { // Do a heavy task, which takes a little time for (int i = 0; i < 2000000000; ++i); cout << "Done!" << endl; } int main() { boost::thread th(&doHeavyTask); th.join(); cout << "Good bye!" << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo03a-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version A: Passing multiple arguments with various data types */ #include #include #include #include using namespace std; struct Point { int x; int y; Point(int x, int y): x(x), y(y) { } }; void doTask(int a, double b, string c, char const* d, Point e) { char buffer[50] = { 0 }; std::sprintf(buffer, "%d %.1f %s %s (%d %d)", a, b, c.data(), d, e.x, e.y); cout << buffer << endl; } int main() { boost::thread thFoo(&doTask, 1, 2, "red", "red", Point(0, 0)); boost::thread thBar(&doTask, 3, 4, "blue", "blue", Point(9, 9)); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo03b-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version B: Passing constant references */ #include #include #include using namespace std; void doTask(const string& msg) { cout << msg << endl; } void doTask(const string& msg) { cout << msg << endl; } int main() { boost::thread thFoo(&doTask, "foo"); boost::thread thBar(&doTask, "bar"); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo03c-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version C: Passing normal references */ #include #include #include #include using namespace std; void doTask(string& msg) { cout << msg << endl; } int main() { string a = "lorem ipsum"; string b = "dolor amet"; // We should use boost:ref to pass references boost::thread thFoo(&doTask, boost::ref(a)); boost::thread thBar(&doTask, boost::ref(b)); // boost::thread thFoo(&doTask, a); // boost::thread thBar(&doTask, b); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo04a-sleep.cpp ================================================ /* SLEEP Version A: Sleep for a specific duration */ #include #include #include #include using namespace std; void doTask(string name) { cout << name << " is sleeping" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(3)); cout << name << " wakes up" << endl; } int main() { boost::thread thFoo(&doTask, "foo"); thFoo.join(); cout << "Good bye" << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo04b-sleep.cpp ================================================ /* SLEEP Version B: Sleep until a specific time point */ #include #include #include #include #include "mylib-time.hpp" using namespace std; typedef boost::chrono::system_clock sysclock; void doTask(string name, sysclock::time_point tpWakeUp) { boost::this_thread::sleep_until(tpWakeUp); cout << name << " wakes up" << endl; } int main() { sysclock::time_point tpNow = sysclock::now(); sysclock::time_point tpWakeUpFoo = tpNow + boost::chrono::seconds(7); sysclock::time_point tpWakeUpBar = tpNow + boost::chrono::seconds(3); cout << "foo will sleep until " << mylib::getTimePointStr(tpWakeUpFoo) << endl; cout << "bar will sleep until " << mylib::getTimePointStr(tpWakeUpBar) << endl; boost::thread thFoo(&doTask, "foo", tpWakeUpFoo); boost::thread thBar(&doTask, "bar", tpWakeUpBar); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo05-id.cpp ================================================ /* GETTING THREAD'S ID */ #include #include #include using namespace std; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(2)); cout << boost::this_thread::get_id() << endl; } int main() { boost::thread thFoo(&doTask); boost::thread thBar(&doTask); cout << "foo's id: " << thFoo.get_id() << endl; cout << "bar's id: " << thBar.get_id() << endl; thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo06a-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version A: Using standard arrays */ #include #include using namespace std; void doTask(int index) { cout << index; } int main() { const int NUM_THREADS = 5; boost::thread lstTh[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; ++i) { lstTh[i] = boost::thread(&doTask, i); } for (int i = 0; i < NUM_THREADS; ++i) { lstTh[i].join(); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo06b-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version B: Using the std::vector */ #include #include #include #include using namespace std; typedef boost::shared_ptr threadptr; void doTask(int index) { cout << index; } int main() { const int NUM_THREADS = 5; vector lstTh; for (int i = 0; i < NUM_THREADS; ++i) { threadptr ptr = boost::make_shared(&doTask, i); lstTh.push_back(ptr); } for (int i = 0; i < lstTh.size(); ++i) { lstTh[i]->join(); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo06c-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version C: Using the boost::thread_group */ #include #include #include using namespace std; void doTask(int index) { cout << index; } int main() { const int NUM_THREADS = 5; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask, i)); // lstTh.create_thread(boost::bind(&doTask, i)); } lstTh.join_all(); cout << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo07-terminate.cpp ================================================ /* FORCING A THREAD TO TERMINATE (i.e. killing the thread) */ #include #include #include using namespace std; volatile bool isRunning; void doTask() { while (isRunning) { cout << "Running..." << endl; boost::this_thread::sleep_for(boost::chrono::seconds(2)); } } int main() { isRunning = true; boost::thread th(&doTask); boost::this_thread::sleep_for(boost::chrono::seconds(6)); isRunning = false; th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo08-return-value.cpp ================================================ /* GETTING RETURNED VALUES FROM THREADS Using pointers or references (traditional way) */ #include #include #include using namespace std; void doubleValue(int arg, int* res) { (*res) = arg * 2; } void squareValue(int arg, int& res) { res = arg * arg; } int main() { int result[3]; boost::thread thFoo(&doubleValue, 5, &result[0]); boost::thread thBar(&doubleValue, 80, &result[1]); boost::thread thEgg(&squareValue, 7, boost::ref(result[2])); thFoo.join(); thBar.join(); thEgg.join(); cout << result[0] << endl; cout << result[1] << endl; cout << result[2] << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo09-detach.cpp ================================================ /* THREAD DETACHING */ #include #include #include using namespace std; void foo() { cout << "foo is starting..." << endl; boost::this_thread::sleep_for(boost::chrono::seconds(2)); cout << "foo is exiting..." << endl; } int main() { boost::thread thFoo(&foo); thFoo.detach(); // If I comment this statement, // thFoo will be forced into terminating with main thread boost::this_thread::sleep_for(boost::chrono::seconds(3)); cout << "Main thread is exiting" << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo10-yield.cpp ================================================ /* THREAD YIELDING */ #include #include #include #include "mylib-time.hpp" using namespace std; typedef boost::chrono::microseconds chrmicro; typedef boost::chrono::steady_clock::time_point time_point; typedef mylib::HiResClock hrclock; void littleSleep(int us) { time_point tpStart = hrclock::now(); time_point tpEnd = tpStart + chrmicro(us); do { boost::this_thread::yield(); } while (hrclock::now() < tpEnd); } int main() { time_point tpStartMeasure = hrclock::now(); littleSleep(130); chrmicro timeElapsed = hrclock::getTimeSpan(tpStartMeasure); cout << "Elapsed time: " << timeElapsed.count() << " microseonds" << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo11a-exec-service.cpp ================================================ /* EXECUTOR SERVICES AND THREAD POOLS */ #include #include #include #include using namespace std; void doTask() { cout << "Hello the Executor Service" << endl; } class MyFunctor { public: void operator()() { cout << "Hello Multithreading" << endl; } }; int main() { // INIT THE EXECUTOR SERVICE WITH 2 THREADS boost::asio::thread_pool pool(2); // SUBMIT boost::asio::post(pool, boost::bind(&doTask /* , argument1, argument2,... */)); boost::asio::post(pool, MyFunctor()); // WAIT FOR THE COMPLETION OF ALL TASKS AND SHUTDOWN EXECUTOR SERVICE pool.join(); pool.stop(); return 0; } ================================================ FILE: cpp/cpp-boost/demo11b-exec-service.cpp ================================================ /* EXECUTOR SERVICES AND THREAD POOLS */ #include #include #include #include #include using namespace std; void doTask(char id) { cout << "Task " << id << " is starting" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(3)); cout << "Task " << id << " is completed" << endl; } int main() { const int NUM_THREADS = 2; const int NUM_TASKS = 5; boost::asio::thread_pool pool(NUM_THREADS); for (int i = 0; i < NUM_TASKS; ++i) { boost::asio::post(pool, boost::bind(&doTask, 'A' + i)); } cout << "All tasks are submitted" << endl; pool.join(); cout << "All tasks are completed" << endl; pool.stop(); return 0; } ================================================ FILE: cpp/cpp-boost/demo12a-race-condition.cpp ================================================ /* RACE CONDITIONS */ #include #include #include using namespace std; void doTask(int index) { boost::this_thread::sleep_for(boost::chrono::seconds(1)); cout << index; } int main() { const int NUM_THREADS = 4; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask, i)); } lstTh.join_all(); cout << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo12b01-data-race-single.cpp ================================================ /* DATA RACES Version 01: Without multithreading */ #include #include #include using namespace std; int getResult(int N) { vector a; a.resize(N + 1, false); for (int i = 1; i <= N; ++i) if (0 == i % 2 || 0 == i % 3) a[i] = true; // result = sum of a (i.e. counting number of true values in a) int result = std::accumulate(a.begin(), a.end(), 0); return result; } int main() { const int N = 8; int result = getResult(N); cout << "Number of integers that are divisible by 2 or 3 is: " << result << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo12b02-data-race-multi.cpp ================================================ /* DATA RACES Version 02: Multithreading */ #include #include #include #include #include using namespace std; void markDiv2(vector & a, int N) { for (int i = 2; i <= N; i += 2) a[i] = true; } void markDiv3(vector & a, int N) { for (int i = 3; i <= N; i += 3) a[i] = true; } int main() { const int N = 8; vector a; a.resize(N + 1, false); boost::thread thDiv2(&markDiv2, boost::ref(a), N); boost::thread thDiv3(&markDiv3, boost::ref(a), N); thDiv2.join(); thDiv3.join(); // result = sum of a (i.e. counting numbers of true values in a) int result = std::accumulate(a.begin(), a.end(), 0); cout << "Number of integers that are divisible by 2 or 3 is: " << result << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo12c01-race-cond-data-race.cpp ================================================ /* RACE CONDITIONS AND DATA RACES */ #include #include #include using namespace std; int counter = 0; void increaseCounter() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); for (int i = 0; i < 1000; ++i) { counter += 1; } } int main() { const int NUM_THREADS = 16; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&increaseCounter)); } lstTh.join_all(); cout << "counter = " << counter << endl; // We are NOT sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-boost/demo12c02-race-cond-data-race.cpp ================================================ /* RACE CONDITIONS AND DATA RACES */ #include #include #include using namespace std; typedef boost::chrono::system_clock sysclock; int counter = 0; void doTaskA(sysclock::time_point timePointWakeUp) { boost::this_thread::sleep_until(timePointWakeUp); while (counter < 10) ++counter; cout << "A won !!!" << endl; } void doTaskB(sysclock::time_point timePointWakeUp) { boost::this_thread::sleep_until(timePointWakeUp); while (counter > -10) --counter; cout << "B won !!!" << endl; } int main() { sysclock::time_point tpNow = sysclock::now(); sysclock::time_point tpWakeUp = tpNow + boost::chrono::seconds(1); boost::thread thA(&doTaskA, tpWakeUp); boost::thread thB(&doTaskB, tpWakeUp); thA.join(); thB.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo13a-mutex.cpp ================================================ /* MUTEXES */ #include #include #include using namespace std; boost::mutex mut; int counter = 0; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); mut.lock(); for (int i = 0; i < 1000; ++i) ++counter; mut.unlock(); } int main() { const int NUM_THREADS = 16; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-boost/demo13b01-mutex.cpp ================================================ /* MUTEXES */ #include #include #include using namespace std; boost::mutex mut; int counter = 0; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); boost::lock_guard lk(mut); for (int i = 0; i < 1000; ++i) ++counter; // Once function exits, then destructor of lk object will be called. // In destructor it unlocks the mutex. } int main() { const int NUM_THREADS = 16; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-boost/demo13b02-mutex.cpp ================================================ /* MUTEXES boost::unique_lock is more complex than boost::lock_guard: Not only does it provide for RAII-style locking, it also allows for deferring acquiring the lock until the lock() member function is called explicitly, or trying to acquire the lock in a non-blocking fashion, or with a timeout. Consequently, unlock() is only called in the destructor if the lock object has locked the Lockable object, or otherwise adopted a lock on the Lockable object. (From Boost's docs website) */ #include #include #include using namespace std; boost::mutex mut; int counter = 0; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); boost::unique_lock lk(mut); for (int i = 0; i < 1000; ++i) ++counter; } int main() { const int NUM_THREADS = 16; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-boost/demo13c-mutex-trylock.cpp ================================================ /* MUTEXES Locking with a nonblocking mutex */ #include #include #include using namespace std; boost::mutex mut; int counter = 0; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); if (false == mut.try_lock()) { return; } for (int i = 0; i < 10000; ++i) ++counter; mut.unlock(); } int main() { const int NUM_THREADS = 3; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); cout << "counter = " << counter << endl; // counter can be 10000, 20000 or 30000 return 0; } ================================================ FILE: cpp/cpp-boost/demo14-synchronized-block.cpp ================================================ /* SYNCHRONIZED BLOCKS Synchronized blocks in C++ Boost threading are not supported by default. To demonstate synchronized blocks, I use boost::unique_lock (or boost::lock_guard). Now, let's see the code: { boost::unique_lock lk(mut); // Do something in the critical section } The code block above is protected by a lock/mutex. That means it is synchronized on thread execution. This code block is called "the synchronized block". */ #include #include #include using namespace std; boost::mutex mut; int counter = 0; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); // This is the "synchronized block" { boost::unique_lock lk(mut); for (int i = 0; i < 1000; ++i) ++counter; } } int main() { const int NUM_THREADS = 16; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-boost/demo15a-deadlock.cpp ================================================ /* DEADLOCK Version A */ #include #include #include using namespace std; boost::mutex mut; void doTask(std::string name) { mut.lock(); cout << name << " acquired resource" << endl; // mut.unlock(); // Forget this statement ==> deadlock } int main() { boost::thread thFoo(&doTask, "foo"); boost::thread thBar(&doTask, "bar"); thFoo.join(); thBar.join(); cout << "You will never see this statement due to deadlock!" << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo15b-deadlock.cpp ================================================ /* DEADLOCK Version B */ #include #include #include using namespace std; boost::mutex mutResourceA; boost::mutex mutResourceB; void foo() { mutResourceA.lock(); cout << "foo acquired resource A" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); mutResourceB.lock(); cout << "foo acquired resource B" << endl; mutResourceB.unlock(); mutResourceA.unlock(); } void bar() { mutResourceB.lock(); cout << "bar acquired resource B" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); mutResourceA.lock(); cout << "bar acquired resource A" << endl; mutResourceA.unlock(); mutResourceB.unlock(); } int main() { boost::thread thFoo(&foo); boost::thread thBar(&bar); thFoo.join(); thBar.join(); cout << "You will never see this statement due to deadlock!" << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo16-monitor.cpp ================================================ /* MONITORS Implementation of a monitor for managing a counter */ #include #include #include using namespace std; class Monitor { private: boost::mutex mut; int* pCounter; public: // Should disable copy/move constructors, copy/move assignment operators void init(int* pCounter) { this->pCounter = pCounter; } void increaseCounter() { mut.lock(); (*pCounter) += 1; mut.unlock(); } }; void doTask(Monitor* monitor) { boost::this_thread::sleep_for(boost::chrono::seconds(1)); for (int i = 0; i < 1000; ++i) monitor->increaseCounter(); } int main() { int counter = 0; Monitor monitor; const int NUM_THREADS = 16; boost::thread_group lstTh; monitor.init(&counter); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask, &monitor)); } lstTh.join_all(); cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo17a-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version A: Introduction to reentrant locks */ #include #include using namespace std; boost::mutex mut; void doTask() { mut.lock(); cout << "First time acquiring the resource" << endl; mut.lock(); cout << "Second time acquiring the resource" << endl; mut.unlock(); mut.unlock(); } int main() { boost::thread th(&doTask); /* The thread th shall meet deadlock. So, you will never get output "Second time the acquiring resource". */ th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo17b-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version B: Solving the problem from version A */ #include #include using namespace std; boost::recursive_mutex mut; void doTask() { mut.lock(); cout << "First time acquiring the resource" << endl; mut.lock(); cout << "Second time acquiring the resource" << endl; mut.unlock(); mut.unlock(); } void doTaskUsingSyncBlock() { typedef boost::unique_lock uniquelk; uniquelk(mut); cout << "First time acquiring the resource" << endl; { uniquelk(mut); cout << "Second time acquiring the resource" << endl; } } int main() { boost::thread th(&doTask); th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo17c-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version C: A multithreaded app example */ #include #include #include using namespace std; boost::recursive_mutex mut; void doTask(char name) { boost::this_thread::sleep_for(boost::chrono::seconds(1)); mut.lock(); cout << "First time " << name << " acquiring the resource" << endl; mut.lock(); cout << "Second time " << name << " acquiring the resource" << endl; mut.unlock(); mut.unlock(); } void doTaskUsingSyncBlock(char name) { typedef boost::unique_lock uniquelk; boost::this_thread::sleep_for(boost::chrono::seconds(1)); { uniquelk(mut); cout << "First time " << name << " acquiring the resource" << endl; { uniquelk(mut); cout << "Second time " << name << " acquiring the resource" << endl; } } } int main() { const int NUM_THREADS = 3; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask, char(i + 'A'))); } lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo18a01-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include using namespace std; typedef boost::tuple tuplestrint; boost::barrier syncPoint(3); // participant count = 3 void processRequest(string userName, int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPoint.count_down_and_wait(); cout << "Process request for " << userName << endl; syncPoint.count_down_and_wait(); cout << "Done " << userName << endl; } int main() { const int NUM_THREADS = 3; boost::thread_group lstTh; // tuple tuplestrint lstArg[NUM_THREADS] = { tuplestrint("lorem", 1), tuplestrint("ipsum", 2), tuplestrint("dolor", 3) }; for (int i = 0; i < NUM_THREADS; ++i) { tuplestrint & arg = lstArg[i]; lstTh.add_thread(new boost::thread( &processRequest, boost::get<0>(arg), boost::get<1>(arg) )); } lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo18a02-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include using namespace std; typedef boost::tuple tuplestrint; boost::barrier syncPoint(2); // participant count = 2 void processRequest(string userName, int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPoint.count_down_and_wait(); cout << "Process request for " << userName << endl; syncPoint.count_down_and_wait(); cout << "Done " << userName << endl; } int main() { const int NUM_THREADS = 4; boost::thread_group lstTh; // tuple tuplestrint lstArg[NUM_THREADS] = { tuplestrint("lorem", 1), tuplestrint("ipsum", 3), tuplestrint("dolor", 3), tuplestrint("amet", 10), }; for (int i = 0; i < NUM_THREADS; ++i) { tuplestrint & arg = lstArg[i]; lstTh.add_thread(new boost::thread( &processRequest, boost::get<0>(arg), boost::get<1>(arg) )); } // Thread with userName = "amet" shall be FREEZED lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo18a03-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include using namespace std; typedef boost::tuple tuplestrint; boost::barrier syncPointA(2); boost::barrier syncPointB(2); void processRequest(string userName, int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPointA.count_down_and_wait(); cout << "Process request for " << userName << endl; syncPointB.count_down_and_wait(); cout << "Done " << userName << endl; } int main() { const int NUM_THREADS = 4; boost::thread_group lstTh; // tuple tuplestrint lstArg[NUM_THREADS] = { tuplestrint("lorem", 1), tuplestrint("ipsum", 3), tuplestrint("dolor", 3), tuplestrint("amet", 10), }; for (int i = 0; i < NUM_THREADS; ++i) { tuplestrint & arg = lstArg[i]; lstTh.add_thread(new boost::thread( &processRequest, boost::get<0>(arg), boost::get<1>(arg) )); } lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo18b01-latch.cpp ================================================ /* BARRIERS AND LATCHES Version B: Count-down latches */ #include #include #include #include #include #include using namespace std; typedef boost::tuple tuplestrint; boost::latch syncPoint(3); // participant count = 3 void processRequest(string userName, int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPoint.count_down(); syncPoint.wait(); // syncPoint.count_down_and_wait(); cout << "Done " << userName << endl; } int main() { const int NUM_THREADS = 3; boost::thread_group lstTh; // tuple tuplestrint lstArg[NUM_THREADS] = { tuplestrint("lorem", 1), tuplestrint("ipsum", 2), tuplestrint("dolor", 3) }; for (int i = 0; i < NUM_THREADS; ++i) { tuplestrint & arg = lstArg[i]; lstTh.add_thread(new boost::thread( &processRequest, boost::get<0>(arg), boost::get<1>(arg) )); } lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo18b02-latch.cpp ================================================ /* BARRIERS AND LATCHES Version B: Count-down latches Main thread waits for 3 child threads to get enough data to progress. */ #include #include #include #include #include #include using namespace std; typedef boost::tuple tuplestrint; const int NUM_THREADS = 3; boost::latch syncPoint(NUM_THREADS); void doTask(string message, int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); cout << message << endl; syncPoint.count_down(); boost::this_thread::sleep_for(boost::chrono::seconds(8)); cout << "Cleanup" << endl; } int main() { boost::thread_group lstTh; // tuple tuplestrint lstArg[NUM_THREADS] = { tuplestrint("Send request to egg.net to get data", 6), tuplestrint("Send request to foo.org to get data", 2), tuplestrint("Send request to bar.com to get data", 4) }; for (int i = 0; i < NUM_THREADS; ++i) { tuplestrint & arg = lstArg[i]; lstTh.add_thread(new boost::thread( &doTask, boost::get<0>(arg), boost::get<1>(arg) )); } syncPoint.wait(); cout << "\nNow we have enough data to progress to next step\n" << endl; lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo19a-read-write-lock.cpp ================================================ /* READ-WRITE LOCKS */ #include #include #include #include #include "mylib-random.hpp" using namespace std; volatile int resource = 0; boost::shared_mutex rwmut; void readFunc(int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); rwmut.lock_shared(); cout << "read: " << resource << endl; rwmut.unlock_shared(); } void writeFunc(int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); rwmut.lock(); resource = mylib::RandInt::get(100); cout << "write: " << resource << endl; rwmut.unlock(); } int main() { const int NUM_THREADS_READ = 10; const int NUM_THREADS_WRITE = 4; const int NUM_ARGS = 3; boost::thread_group lstThRead; boost::thread_group lstThWrite; int lstArg[NUM_ARGS]; // INITIALIZE for (int i = 0; i < NUM_ARGS; ++i) { lstArg[i] = i; } // CREATE THREADS for (int i = 0; i < NUM_THREADS_READ; ++i) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; lstThRead.add_thread(new boost::thread( &readFunc, arg )); } for (int i = 0; i < NUM_THREADS_WRITE; ++i) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; lstThWrite.add_thread(new boost::thread( &writeFunc, arg )); } // JOIN THREADS lstThRead.join_all(); lstThWrite.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo19b-read-write-lock.cpp ================================================ /* READ-WRITE LOCKS */ #include #include #include #include #include "mylib-random.hpp" using namespace std; volatile int resource = 0; boost::shared_mutex rwmut; void readFunc(int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); boost::shared_lock lk(rwmut); cout << "read: " << resource << endl; // lk.unlock(); } void writeFunc(int waitTime) { boost::this_thread::sleep_for(boost::chrono::seconds(waitTime)); boost::lock_guard lk(rwmut); // boost::unique_lock lk(rwmut); resource = mylib::RandInt::get(100); cout << "write: " << resource << endl; // lk.unlock(); } int main() { const int NUM_THREADS_READ = 10; const int NUM_THREADS_WRITE = 4; const int NUM_ARGS = 3; boost::thread_group lstThRead; boost::thread_group lstThWrite; int lstArg[NUM_ARGS]; // INITIALIZE for (int i = 0; i < NUM_ARGS; ++i) { lstArg[i] = i; } // CREATE THREADS for (int i = 0; i < NUM_THREADS_READ; ++i) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; lstThRead.add_thread(new boost::thread( &readFunc, arg )); } for (int i = 0; i < NUM_THREADS_WRITE; ++i) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; lstThWrite.add_thread(new boost::thread( &writeFunc, arg )); } // JOIN THREADS lstThRead.join_all(); lstThWrite.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/demo20a01-semaphore.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages Semaphores in C++ Boost threading are not supported by default. So, I use mylib::Semaphore for this demonstration. */ #include #include #include #include "mylib-semaphore.hpp" using namespace std; mylib::Semaphore semPackage(0); void makeOneSheet() { for (int i = 0; i < 4; ++i) { cout << "Make 1 sheet" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); semPackage.release(); } } void combineOnePackage() { for (int i = 0; i < 4; ++i) { semPackage.acquire(); semPackage.acquire(); cout << "Combine 2 sheets into 1 package" << endl; } } int main() { boost::thread thMakeSheetA(&makeOneSheet); boost::thread thMakeSheetB(&makeOneSheet); boost::thread thCombinePackage(&combineOnePackage); thMakeSheetA.join(); thMakeSheetB.join(); thCombinePackage.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo20a02-semaphore.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages Semaphores in C++ Boost threading are not supported by default. So, I use mylib::Semaphore for this demonstration. */ #include #include #include #include "mylib-semaphore.hpp" using namespace std; mylib::Semaphore semPackage(0); mylib::Semaphore semSheet(2); void makeOneSheet() { for (int i = 0; i < 2; ++i) { semSheet.acquire(); cout << "Make 1 sheet" << endl; semPackage.release(); } } void combineOnePackage() { for (int i = 0; i < 2; ++i) { semPackage.acquire(); semPackage.acquire(); cout << "Combine 2 sheets into 1 package" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(2)); semSheet.release(); semSheet.release(); } } int main() { boost::thread thMakeSheetA(&makeOneSheet); boost::thread thMakeSheetB(&makeOneSheet); boost::thread thCombinePackage(&combineOnePackage); thMakeSheetA.join(); thMakeSheetB.join(); thCombinePackage.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo20a03-semaphore-deadlock.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages Semaphores in C++ Boost threading are not supported by default. So, I use mylib::Semaphore for this demonstration. */ #include #include #include #include "mylib-semaphore.hpp" using namespace std; mylib::Semaphore semPackage(0); mylib::Semaphore semSheet(2); void makeOneSheet() { for (int i = 0; i < 4; ++i) { semSheet.acquire(); cout << "Make 1 sheet" << endl; semPackage.release(); } } void combineOnePackage() { for (int i = 0; i < 4; ++i) { semPackage.acquire(); semPackage.acquire(); cout << "Combine 2 sheets into 1 package" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(2)); semSheet.release(); // Missing one statement: semSheet.release() ==> deadlock } } int main() { boost::thread thMakeSheetA(&makeOneSheet); boost::thread thMakeSheetB(&makeOneSheet); boost::thread thCombinePackage(&combineOnePackage); thMakeSheetA.join(); thMakeSheetB.join(); thCombinePackage.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo20b-semaphore.cpp ================================================ /* SEMAPHORES Version B: Tires and chassis Semaphores in C++ Boost threading are not supported by default. So, I use mylib::Semaphore for this demonstration. */ #include #include #include #include "mylib-semaphore.hpp" using namespace std; mylib::Semaphore semTire(4); mylib::Semaphore semChassis(0); void makeTire() { for (int i = 0; i < 8; ++i) { semTire.acquire(); cout << "Make 1 tire" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); semChassis.release(); } } void makeChassis() { for (int i = 0; i < 4; ++i) { semChassis.acquire(); semChassis.acquire(); semChassis.acquire(); semChassis.acquire(); cout << "Make 1 chassis" << endl; boost::this_thread::sleep_for(boost::chrono::seconds(3)); semTire.release(); semTire.release(); semTire.release(); semTire.release(); } } int main() { boost::thread thTireA(&makeTire); boost::thread thTireB(&makeTire); boost::thread thChassis(&makeChassis); thTireA.join(); thTireB.join(); thChassis.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo21a01-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; boost::mutex mut; boost::condition_variable conditionVar; void foo() { cout << "foo is waiting..." << endl; boost::unique_lock mutLock(mut); conditionVar.wait(mutLock); cout << "foo resumed" << endl; } void bar() { boost::this_thread::sleep_for(boost::chrono::seconds(3)); conditionVar.notify_one(); } int main() { boost::thread thFoo(&foo); boost::thread thBar(&bar); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo21a02-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; boost::mutex mut; boost::condition_variable conditionVar; const int NUM_TH_FOO = 3; void foo() { cout << "foo is waiting..." << endl; boost::unique_lock mutLock(mut); conditionVar.wait(mutLock); cout << "foo resumed" << endl; } void bar() { for (int i = 0; i < NUM_TH_FOO; ++i) { boost::this_thread::sleep_for(boost::chrono::seconds(2)); conditionVar.notify_one(); } } int main() { boost::thread_group lstThFoo; for (int i = 0; i < NUM_TH_FOO; ++i) { lstThFoo.add_thread(new boost::thread(&foo)); } boost::thread thBar(&bar); lstThFoo.join_all(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo21a03-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; boost::mutex mut; boost::condition_variable conditionVar; const int NUM_TH_FOO = 3; void foo() { cout << "foo is waiting..." << endl; boost::unique_lock mutLock(mut); conditionVar.wait(mutLock); cout << "foo resumed" << endl; } void bar() { boost::this_thread::sleep_for(boost::chrono::seconds(3)); // Notify all waiting threads conditionVar.notify_all(); } int main() { boost::thread_group lstThFoo; for (int i = 0; i < NUM_TH_FOO; ++i) { lstThFoo.add_thread(new boost::thread(&foo)); } boost::thread thBar(&bar); lstThFoo.join_all(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo21b-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; boost::mutex mut; boost::condition_variable conditionVar; int counter = 0; const int COUNT_HALT_01 = 3; const int COUNT_HALT_02 = 6; const int COUNT_DONE = 10; // Write numbers 1-3 and 8-10 as permitted by egg() void foo() { for (;;) { // Lock mutex and then wait for signal to relase mutex boost::unique_lock lk(mut); // Wait while egg() operates on counter, // Mutex unlocked if condition variable in egg() signaled conditionVar.wait(lk); ++counter; cout << "foo counter = " << counter << endl; if (counter >= COUNT_DONE) { return; } } } // Write numbers 4-7 void egg() { for (;;) { boost::unique_lock lk(mut); if (counter < COUNT_HALT_01 || counter > COUNT_HALT_02) { // Signal to free waiting thread by freeing the mutex // Note: foo() is now permitted to modify "counter" conditionVar.notify_one(); } else { ++counter; cout << "egg counter = " << counter << endl; } if (counter >= COUNT_DONE) { return; } } } int main() { boost::thread thFoo(&foo); boost::thread thEgg(&egg); thFoo.join(); thEgg.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo22a-blocking-queue.cpp ================================================ /* BLOCKING QUEUES Version A: A slow producer and a fast consumer Blocking queues in C++ Boost threading are not supported by default. So, I use mylib::BlockingQueue for this demonstration. */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkQueue) { boost::this_thread::sleep_for(boost::chrono::seconds(2)); blkQueue->put("Alice"); boost::this_thread::sleep_for(boost::chrono::seconds(2)); blkQueue->put("likes"); boost::this_thread::sleep_for(boost::chrono::seconds(2)); blkQueue->put("singing"); } void consumer(BlockingQueue* blkQueue) { string data; for (int i = 0; i < 3; ++i) { cout << "\nWaiting for data..." << endl; data = blkQueue->take(); cout << " " << data << endl; } } int main() { BlockingQueue blkQueue; boost::thread thProducer(&producer, &blkQueue); boost::thread thConsumer(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo22b-blocking-queue.cpp ================================================ /* BLOCKING QUEUES Version B: A fast producer and a slow consumer Blocking queues in C++ Boost threading are not supported by default. So, I use mylib::BlockingQueue for this demonstration. */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkQueue) { blkQueue->put("Alice"); blkQueue->put("likes"); /* Due to reaching the maximum capacity = 2, when executing blkQueue->put("singing"), this thread is going to sleep until the queue removes an element. */ blkQueue->put("singing"); } void consumer(BlockingQueue* blkQueue) { string data; boost::this_thread::sleep_for(boost::chrono::seconds(2)); for (int i = 0; i < 3; ++i) { cout << "\nWaiting for data..." << endl; data = blkQueue->take(); cout << " " << data << endl; } } int main() { BlockingQueue blkQueue(2); // blocking queue with capacity = 2 boost::thread thProducer(&producer, &blkQueue); boost::thread thConsumer(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo23a-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Introduction The basic way to use thread-local storage */ #include #include #include #include using namespace std; boost::thread_specific_ptr value; void printLocalValue() { cout << (*value.get()) << endl; } void doTaskApple() { value.reset(new string("APPLE")); boost::this_thread::sleep_for(boost::chrono::seconds(2)); printLocalValue(); } void doTaskBanana() { value.reset(new string("BANANA")); boost::this_thread::sleep_for(boost::chrono::seconds(2)); printLocalValue(); } int main() { boost::thread thApple(&doTaskApple); boost::thread thBanana(&doTaskBanana); thApple.join(); thBanana.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo23b-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Avoiding synchronization using thread-local storage */ #include #include #include using namespace std; boost::thread_specific_ptr counter; void doTask(int t) { boost::this_thread::sleep_for(boost::chrono::seconds(1)); counter.reset(new int(0)); for (int i = 0; i < 1000; ++i) (*counter) += 1; cout << "Thread " << t << " gives counter = " << (*counter) << endl; } int main() { const int NUM_THREADS = 3; boost::thread_group lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&doTask, i)); } lstTh.join_all(); cout << endl; /* By using thread-local storage, each thread has its own counter. So, the counter in one thread is completely independent of each other. Thread-local storage helps us to AVOID SYNCHRONIZATION. */ return 0; } ================================================ FILE: cpp/cpp-boost/demo24-volatile.cpp ================================================ /* THE VOLATILE KEYWORD */ #include #include #include using namespace std; volatile bool isRunning; void doTask() { while (isRunning) { cout << "Running..." << endl; boost::this_thread::sleep_for(boost::chrono::seconds(2)); } } int main() { isRunning = true; boost::thread th(&doTask); boost::this_thread::sleep_for(boost::chrono::seconds(6)); isRunning = false; th.join(); return 0; } ================================================ FILE: cpp/cpp-boost/demo25a-atomic.cpp ================================================ /* ATOMIC ACCESS */ #include #include #include using namespace std; volatile int counter = 0; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); counter += 1; } int main() { counter = 0; boost::thread_group lstTh; for (int i = 0; i < 1000; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); // Unpredictable result cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demo25b-atomic.cpp ================================================ /* ATOMIC ACCESS */ #include #include #include using namespace std; // boost::atomic counter; boost::atomic_int32_t counter; void doTask() { boost::this_thread::sleep_for(boost::chrono::seconds(1)); counter += 1; } int main() { counter = 0; boost::thread_group lstTh; for (int i = 0; i < 1000; ++i) { lstTh.add_thread(new boost::thread(&doTask)); } lstTh.join_all(); // counter = 1000 cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-boost/demoex-async-future.cpp ================================================ /* ASYNCHRONOUS PROGRAMMING WITH THE FUTURE */ #include #include #include #include int doTaskA() { return 7; } int doTaskB() { return 8; } void doTaskC(boost::promise & prom) { prom.set_value_at_thread_exit(9); } int main() { // future from a packaged_task (C++11) boost::packaged_task task(&doTaskA); // wrap the function boost::unique_future fut1 = task.get_future(); // get a future boost::thread th(boost::move(task)); // launch on a thread // future from an async() boost::unique_future fut2 = boost::async(boost::launch::async, &doTaskB); // future from a promise boost::promise prom; boost::unique_future fut3 = prom.get_future(); boost::thread(&doTaskC, boost::ref(prom)).detach(); std::cout << "Waiting..." << std::endl; fut1.wait(); fut2.wait(); fut3.wait(); th.join(); std::cout << "Done!" << std::endl; std::cout << "Results are: " << fut1.get() << ' ' << fut2.get() << ' ' << fut3.get() << std::endl; return 0; } ================================================ FILE: cpp/cpp-boost/exer01a-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include "mylib-time.hpp" using namespace std; typedef boost::chrono::microseconds chrmicro; typedef boost::chrono::steady_clock::time_point time_point; typedef mylib::HiResClock hrclock; int main() { const int RANGE_START = 1; const int RANGE_END = 100000; int resValue = 0; int resNumDiv = 0; // number of divisors of result time_point tpStart = hrclock::now(); for (int i = RANGE_START; i <= RANGE_END; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } chrmicro timeElapsed = hrclock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << resValue << endl; cout << "The largest number of divisor is " << resNumDiv << endl; cout << "Time elapsed = " << (timeElapsed.count() / 1000000.0) << endl; return 0; } ================================================ FILE: cpp/cpp-boost/exer01b-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include #include #include #include "mylib-time.hpp" using namespace std; typedef boost::chrono::microseconds chrmicro; typedef boost::chrono::steady_clock::time_point time_point; typedef mylib::HiResClock hrclock; struct WorkerArg { int iStart; int iEnd; WorkerArg(int iStart = 0, int iEnd = 0): iStart(iStart), iEnd(iEnd) { } }; struct WorkerResult { int value; int numDiv; WorkerResult(int value = 0, int numDiv = 0): value(value), numDiv(numDiv) { } }; void workerFunc(WorkerArg* arg, WorkerResult* res) { int resValue = 0; int resNumDiv = 0; for (int i = arg->iStart; i <= arg->iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } (*res) = WorkerResult(resValue, resNumDiv); } void prepare( int rangeStart, int rangeEnd, int numThreads, vector& lstWorkerArg, vector& lstWorkerRes ) { lstWorkerArg.resize(numThreads); lstWorkerRes.resize(numThreads); int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg[i] = WorkerArg(rangeA, rangeB); } } int main() { const int RANGE_START = 1; const int RANGE_END = 100000; const int NUM_THREADS = 8; boost::thread_group lstTh; vector lstWorkerArg; vector lstWorkerRes; prepare(RANGE_START, RANGE_END, NUM_THREADS, lstWorkerArg, lstWorkerRes); time_point tpStart = hrclock::now(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&workerFunc, &lstWorkerArg[i], &lstWorkerRes[i])); } lstTh.join_all(); WorkerResult finalRes = lstWorkerRes[0]; for (int i = 1; i < lstWorkerRes.size(); ++i) { if (finalRes.numDiv < lstWorkerRes[i].numDiv) { finalRes = lstWorkerRes[i]; } } chrmicro timeElapsed = hrclock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << finalRes.value << endl; cout << "The largest number of divisor is " << finalRes.numDiv << endl; cout << "Time elapsed = " << (timeElapsed.count() / 1000000.0) << endl; return 0; } ================================================ FILE: cpp/cpp-boost/exer01c-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include #include #include #include "mylib-time.hpp" using namespace std; typedef boost::chrono::microseconds chrmicro; typedef boost::chrono::steady_clock::time_point time_point; typedef mylib::HiResClock hrclock; struct WorkerArg { int iStart; int iEnd; WorkerArg(int iStart = 0, int iEnd = 0): iStart(iStart), iEnd(iEnd) { } }; class FinalResult { public: int value; int numDiv; private: boost::mutex mut; public: FinalResult(): value(0), numDiv(0) { } void update(int value, int numDiv) { // Synchronize whole function boost::unique_lock lk(mut); if (this->numDiv < numDiv) { this->numDiv = numDiv; this->value = value; } } }; void workerFunc(WorkerArg* arg, FinalResult* res) { int resValue = 0; int resNumDiv = 0; for (int i = arg->iStart; i <= arg->iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } res->update(resValue, resNumDiv); } void prepare( int rangeStart, int rangeEnd, int numThreads, vector& lstWorkerArg ) { lstWorkerArg.resize(numThreads); int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg[i] = WorkerArg(rangeA, rangeB); } } int main() { const int RANGE_START = 1; const int RANGE_END = 100000; const int NUM_THREADS = 8; boost::thread_group lstTh; vector lstWorkerArg; FinalResult finalRes; prepare(RANGE_START, RANGE_END, NUM_THREADS, lstWorkerArg); time_point tpStart = hrclock::now(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.add_thread(new boost::thread(&workerFunc, &lstWorkerArg[i], &finalRes)); } lstTh.join_all(); chrmicro timeElapsed = hrclock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << finalRes.value << endl; cout << "The largest number of divisor is " << finalRes.numDiv << endl; cout << "Time elapsed = " << (timeElapsed.count() / 1000000.0) << endl; return 0; } ================================================ FILE: cpp/cpp-boost/exer02a01-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A01: 1 slow producer, 1 fast consumer */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq) { int i = 1; for (;; ++i) { blkq->put(i); boost::this_thread::sleep_for(boost::chrono::seconds(1)); } } void consumer(BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; } } int main() { BlockingQueue blkq; boost::thread thProducer(&producer, &blkq); boost::thread thConsumer(&consumer, &blkq); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02a02-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A02: 2 slow producers, 1 fast consumer */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq) { int i = 1; for (;; ++i) { blkq->put(i); boost::this_thread::sleep_for(boost::chrono::seconds(1)); } } void consumer(BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; } } int main() { BlockingQueue blkq; boost::thread thProducerA(&producer, &blkq); boost::thread thProducerB(&producer, &blkq); boost::thread thConsumer(&consumer, &blkq); thProducerA.join(); thProducerB.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02a03-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A03: 1 slow producer, 2 fast consumers */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq) { int i = 1; for (;; ++i) { blkq->put(i); boost::this_thread::sleep_for(boost::chrono::seconds(1)); } } void consumer(string name, BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << name << ": " << data << endl; } } int main() { BlockingQueue blkq; boost::thread thProducer(&producer, &blkq); boost::thread thConsumerFoo(&consumer, "foo", &blkq); boost::thread thConsumerBar(&consumer, "bar", &blkq); thProducer.join(); thConsumerFoo.join(); thConsumerBar.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02a04-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A04: Multiple fast producers, multiple slow consumers */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq, int startValue) { int i = 1; for (;; ++i) { blkq->put(i + startValue); } } void consumer(BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); } } int main() { BlockingQueue blkq(5); const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 2; boost::thread_group lstThProducer; boost::thread_group lstThConsumer; // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstThProducer.add_thread(new boost::thread(&producer, &blkq, i * 1000)); } for (int i = 0; i < NUM_CONSUMERS; ++i) { lstThConsumer.add_thread(new boost::thread(&consumer, &blkq)); } // JOIN THREADS lstThProducer.join_all(); lstThConsumer.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02b01-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B01: 1 slow producer, 1 fast consumer */ #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; void producer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i); boost::this_thread::sleep_for(boost::chrono::seconds(1)); semFill->release(); } } void consumer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; semEmpty->release(); } } int main() { mylib::Semaphore semFill(0); // item produced mylib::Semaphore semEmpty(1); // remaining space in queue queue q; boost::thread thProducer(&producer, &semFill, &semEmpty, &q); boost::thread thConsumer(&consumer, &semFill, &semEmpty, &q); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02b02-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B02: 2 slow producers, 1 fast consumer */ #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; void producer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q, int startValue ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i + startValue); boost::this_thread::sleep_for(boost::chrono::seconds(1)); semFill->release(); } } void consumer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; semEmpty->release(); } } int main() { mylib::Semaphore semFill(0); // item produced mylib::Semaphore semEmpty(1); // remaining space in queue queue q; boost::thread thProducerA(&producer, &semFill, &semEmpty, &q, 0); boost::thread thProducerB(&producer, &semFill, &semEmpty, &q, 1000); boost::thread thConsumer(&consumer, &semFill, &semEmpty, &q); thProducerA.join(); thProducerB.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02b03-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B03: 2 fast producers, 1 slow consumer */ #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; void producer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q, int startValue ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i + startValue); semFill->release(); } } void consumer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); semEmpty->release(); } } int main() { mylib::Semaphore semFill(0); // item produced mylib::Semaphore semEmpty(1); // remaining space in queue queue q; boost::thread thProducerA(&producer, &semFill, &semEmpty, &q, 0); boost::thread thProducerB(&producer, &semFill, &semEmpty, &q, 1000); boost::thread thConsumer(&consumer, &semFill, &semEmpty, &q); thProducerA.join(); thProducerB.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02b04-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B04: Multiple fast producers, multiple slow consumers */ #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; void producer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q, int startValue ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i + startValue); semFill->release(); } } void consumer( mylib::Semaphore* semFill, mylib::Semaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); semEmpty->release(); } } int main() { mylib::Semaphore semFill(0); // item produced mylib::Semaphore semEmpty(1); // remaining space in queue queue q; const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 2; boost::thread_group lstThProducer; boost::thread_group lstThConsumer; // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstThProducer.add_thread(new boost::thread( &producer, &semFill, &semEmpty, &q, i * 1000 )); } for (int i = 0; i < NUM_CONSUMERS; ++i) { lstThConsumer.add_thread(new boost::thread( &consumer, &semFill, &semEmpty, &q )); } // JOIN THREADS lstThProducer.join_all(); lstThConsumer.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/exer02c-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE C: USING CONDITION VARIABLES & MONITORS Multiple fast producers, multiple slow consumers */ #include #include #include #include using namespace std; template class Monitor { private: std::queue* q; int maxQueueSize; boost::condition_variable condFull; boost::condition_variable condEmpty; boost::mutex mut; public: Monitor() : q(0), maxQueueSize(0) { } private: Monitor(const Monitor& other) { } void operator=(const Monitor& other) { } public: void init(int maxQueueSize, std::queue* q) { this->q = q; this->maxQueueSize = maxQueueSize; } void add(const T& item) { boost::unique_lock mutLock(mut); while (q->size() == maxQueueSize) { condFull.wait(mutLock); } q->push(item); if (q->size() == 1) { condEmpty.notify_one(); } // mutLock.unlock(); } T remove() { boost::unique_lock mutLock(mut); while (q->size() == 0) { condEmpty.wait(mutLock); } T item = q->front(); q->pop(); if (q->size() == maxQueueSize - 1) { condFull.notify_one(); } // mutLock.unlock(); return item; } }; template void producer(Monitor* monitor, int startValue) { T i = 1; for (;; ++i) { monitor->add(i + startValue); } } template void consumer(Monitor* monitor) { T data; for (;;) { data = monitor->remove(); cout << "Consumer " << data << endl; boost::this_thread::sleep_for(boost::chrono::seconds(1)); } } int main() { Monitor monitor; queue q; const int MAX_QUEUE_SIZE = 6; const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 2; boost::thread_group lstThProducer; boost::thread_group lstThConsumer; // PREPARE ARGUMENTS monitor.init(MAX_QUEUE_SIZE, &q); // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstThProducer.add_thread(new boost::thread(&producer, &monitor, i * 1000)); } for (int i = 0; i < NUM_CONSUMERS; ++i) { lstThConsumer.add_thread(new boost::thread(&consumer, &monitor)); } // JOIN THREADS lstThProducer.join_all(); lstThConsumer.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/exer03a-readers-writers.cpp ================================================ /* THE READERS-WRITERS PROBLEM Solution for the first readers-writers problem */ #include #include #include #include "mylib-random.hpp" using namespace std; struct GlobalData { volatile int resource; int readerCount; boost::mutex mutResource; boost::mutex mutReaderCount; }; void doTaskWriter(GlobalData* g, int delayTime) { boost::this_thread::sleep_for(boost::chrono::seconds(delayTime)); g->mutResource.lock(); g->resource = mylib::RandInt::get(100); cout << "Write " << g->resource << endl; g->mutResource.unlock(); } void doTaskReader(GlobalData* g, int delayTime) { boost::this_thread::sleep_for(boost::chrono::seconds(delayTime)); // Increase reader count g->mutReaderCount.lock(); g->readerCount += 1; if (1 == g->readerCount) g->mutResource.lock(); g->mutReaderCount.unlock(); // Do the reading cout << "Read " << g->resource << endl; // Decrease reader count g->mutReaderCount.lock(); g->readerCount -= 1; if (0 == g->readerCount) g->mutResource.unlock(); g->mutReaderCount.unlock(); } int main() { GlobalData globalData; globalData.resource = 0; globalData.readerCount = 0; const int NUM_READERS = 8; const int NUM_WRITERS = 6; boost::thread_group lstThReader; boost::thread_group lstThWriter; // CREATE THREADS for (int i = 0; i < NUM_READERS; ++i) { lstThReader.add_thread(new boost::thread( &doTaskReader, &globalData, mylib::RandInt::get(3) )); } for (int i = 0; i < NUM_WRITERS; ++i) { lstThWriter.add_thread(new boost::thread( &doTaskWriter, &globalData, mylib::RandInt::get(3) )); } // JOIN THREADS lstThReader.join_all(); lstThWriter.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/exer03b-readers-writers.cpp ================================================ /* THE READERS-WRITERS PROBLEM Solution for the third readers-writers problem */ #include #include #include #include "mylib-random.hpp" using namespace std; struct GlobalData { volatile int resource; int readerCount; boost::mutex mutResource; boost::mutex mutReaderCount; boost::mutex mutServiceQueue; }; void doTaskWriter(GlobalData* g, int delayTime) { boost::this_thread::sleep_for(boost::chrono::seconds(delayTime)); g->mutServiceQueue.lock(); g->mutResource.lock(); g->mutServiceQueue.unlock(); g->resource = mylib::RandInt::get(100); cout << "Write " << g->resource << endl; g->mutResource.unlock(); } void doTaskReader(GlobalData* g, int delayTime) { boost::this_thread::sleep_for(boost::chrono::seconds(delayTime)); g->mutServiceQueue.lock(); // Increase reader count g->mutReaderCount.lock(); g->readerCount += 1; if (1 == g->readerCount) g->mutResource.lock(); g->mutReaderCount.unlock(); g->mutServiceQueue.unlock(); // Do the reading cout << "Read " << g->resource << endl; // Decrease reader count g->mutReaderCount.lock(); g->readerCount -= 1; if (0 == g->readerCount) g->mutResource.unlock(); g->mutReaderCount.unlock(); } int main() { GlobalData globalData; globalData.resource = 0; globalData.readerCount = 0; const int NUM_READERS = 8; const int NUM_WRITERS = 6; boost::thread_group lstThReader; boost::thread_group lstThWriter; // CREATE THREADS for (int i = 0; i < NUM_READERS; ++i) { lstThReader.add_thread(new boost::thread( &doTaskReader, &globalData, mylib::RandInt::get(3) )); } for (int i = 0; i < NUM_WRITERS; ++i) { lstThWriter.add_thread(new boost::thread( &doTaskWriter, &globalData, mylib::RandInt::get(3) )); } // JOIN THREADS lstThReader.join_all(); lstThWriter.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/exer04-dining-philosophers.cpp ================================================ /* THE DINING PHILOSOPHERS PROBLEM */ #include #include #include using namespace std; void doTaskPhilosopher(boost::mutex chopstick[], int numPhilo, int idPhilo) { int n = numPhilo; int i = idPhilo; boost::this_thread::sleep_for(boost::chrono::seconds(1)); chopstick[i].lock(); chopstick[(i + 1) % n].lock(); cout << "Philosopher #" << i << " is eating the rice" << endl; chopstick[(i + 1) % n].unlock(); chopstick[i].unlock(); } void doTaskPhilosopherUsingSyncBlock(boost::mutex chopstick[], int numPhilo, int idPhilo) { int n = numPhilo; int i = idPhilo; boost::this_thread::sleep_for(boost::chrono::seconds(1)); { boost::unique_lock ( chopstick[i] ); boost::unique_lock ( chopstick[(i + 1) % n] ); cout << "Philosopher #" << i << " is eating the rice" << endl; } } int main() { const int NUM_PHILOSOPHERS = 5; boost::mutex chopstick[NUM_PHILOSOPHERS]; boost::thread_group lstTh; // CREATE THREADS for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { lstTh.add_thread(new boost::thread( &doTaskPhilosopher, chopstick, NUM_PHILOSOPHERS, i )); } // JOIN THREADS lstTh.join_all(); return 0; } ================================================ FILE: cpp/cpp-boost/exer05-util.hpp ================================================ #ifndef _EXER05_UTIL_HPP_ #define _EXER05_UTIL_HPP_ void getScalarProduct(double const* u, double const* v, int sizeVector, double* res) { double sum = 0; for (int i = sizeVector - 1; i >= 0; --i) { sum += u[i] * v[i]; } (*res) = sum; } #endif // _EXER05_UTIL_HPP_ ================================================ FILE: cpp/cpp-boost/exer05a-product-matrix-vector.cpp ================================================ /* MATRIX-VECTOR MULTIPLICATION */ #include #include #include #include #include "exer05-util.hpp" using namespace std; using namespace boost::assign; typedef std::vector vectord; typedef std::vector matrix; void getProduct(const matrix& mat, const vectord& vec, vectord& result) { // Assume that size of mat and vec are both eligible int sizeRowMat = mat.size(); int sizeColMat = mat[0].size(); int sizeVec = vec.size(); result.clear(); result.resize(sizeRowMat, 0); boost::thread_group lstTh; for (int i = 0; i < sizeRowMat; ++i) { lstTh.add_thread(new boost::thread( &getScalarProduct, mat[i].data(), vec.data(), sizeVec, &result[i] )); } lstTh.join_all(); } int main() { matrix A; { vectord row1, row2, row3; row1 += 1, 2, 3; row2 += 4, 5, 6; row3 += 7, 8, 9; A += row1, row2, row3; } vectord b; b += 3, -1, 0; vectord result; getProduct(A, b, result); for (int i = 0; i < result.size(); ++i) { cout << result[i] << endl; } return 0; } ================================================ FILE: cpp/cpp-boost/exer05b-product-matrix-matrix.cpp ================================================ /* MATRIX-MATRIX MULTIPLICATION (DOT PRODUCT) */ #include #include #include #include #include "exer05-util.hpp" using namespace std; using namespace boost::assign; typedef std::vector vectord; typedef std::vector matrix; void getTransposeMatrix(const matrix& input, matrix& output) { int numRow = input.size(); int numCol = input[0].size(); output.clear(); output.assign(numCol, vectord(numRow, 0)); for (int i = 0; i < numRow; ++i) for (int j = 0; j < numCol; ++j) output[j][i] = input[i][j]; } void displayMatrix(const matrix& mat) { int numRow = mat.size(); int numCol = mat[0].size(); for (int i = 0; i < numRow; ++i) { for (int j = 0; j < numCol; ++j) cout << "\t" << mat[i][j]; cout << endl; } } void getProduct(const matrix& matA, const matrix& matB, matrix& result) { // Assume that size of matA and matB are both eligible int sizeRowA = matA.size(); int sizeColA = matA[0].size(); int sizeColB = matB[0].size(); int sizeTotal = sizeRowA * sizeColB; result.clear(); result.assign(sizeRowA, vectord(sizeColB, 0)); matrix matBT; getTransposeMatrix(matB, matBT); boost::thread_group lstTh; for (int i = 0; i < sizeRowA; ++i) { for (int j = 0; j < sizeColB; ++j) { int sizeVector = sizeColA; lstTh.add_thread(new boost::thread( &getScalarProduct, matA[i].data(), matBT[j].data(), sizeVector, &result[i][j] )); } } lstTh.join_all(); } int main() { matrix A, B; { vectord row1, row2; row1 += 1, 3, 5; row2 += 2, 4, 6; A += row1, row2; } { vectord row1, row2, row3; row1 += 1, 0, 1, 0; row2 += 0, 1, 0, 1; row3 += 1, 0, 0, -2; B += row1, row2, row3; } matrix result; getProduct(A, B, result); displayMatrix(result); return 0; } ================================================ FILE: cpp/cpp-boost/exer06a-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version A: Synchronous queues */ #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; template class SynchronousQueue { private: mylib::Semaphore semPut; mylib::Semaphore semTake; T element; public: SynchronousQueue() : semPut(1), semTake(0) { } void put(const T& value) { semPut.acquire(); element = value; semTake.release(); } T take() { semTake.acquire(); T result = element; semPut.release(); return result; } }; void producer(SynchronousQueue* syncQueue) { std::string arr[] = { "lorem", "ipsum", "dolor" }; for (int i = 0; i < 3; ++i) { std::string& data = arr[i]; cout << "Producer: " << data << endl; syncQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } } void consumer(SynchronousQueue* syncQueue) { std::string data; boost::this_thread::sleep_for(boost::chrono::seconds(5)); for (int i = 0; i < 3; ++i) { data = syncQueue->take(); cout << "\tConsumer: " << data << endl; } } int main() { SynchronousQueue syncQueue; boost::thread thProducer(&producer, &syncQueue); boost::thread thConsumer(&consumer, &syncQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer06b01-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version B01: General blocking queues Underlying mechanism: Semaphores */ #include #include #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; template class BlockingQueue { private: int capacity; mylib::Semaphore semRemain; mylib::Semaphore semFill; boost::mutex mut; std::queue q; public: BlockingQueue(int capacity) : capacity(capacity), semRemain(capacity), semFill(0) { if (this->capacity <= 0) throw std::invalid_argument("capacity must be a positive integer"); } void put(const T& value) { semRemain.acquire(); { boost::unique_lock lk(mut); q.push(value); } semFill.release(); } T take() { T result; semFill.acquire(); { boost::unique_lock lk(mut); result = q.front(); q.pop(); } semRemain.release(); return result; } }; void producer(BlockingQueue* blkQueue) { std::string arr[] = { "nice", "to", "meet", "you" }; for (int i = 0; i < 4; ++i) { std::string& data = arr[i]; cout << "Producer: " << data << endl; blkQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } } void consumer(BlockingQueue* blkQueue) { std::string data; boost::this_thread::sleep_for(boost::chrono::seconds(5)); for (int i = 0; i < 4; ++i) { data = blkQueue->take(); cout << "\tConsumer: " << data << endl; if (0 == i) boost::this_thread::sleep_for(boost::chrono::seconds(5)); } } int main() { BlockingQueue blkQueue(2); // capacity = 2 boost::thread thProducer(&producer, &blkQueue); boost::thread thConsumer(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer06b02-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version B02: General blocking queues Underlying mechanism: Condition variables */ #include #include #include #include #include #include using namespace std; template class BlockingQueue { private: boost::condition_variable condEmpty; boost::condition_variable condFull; boost::mutex mut; int capacity; std::queue q; public: BlockingQueue(int capacity) { if (capacity <= 0) throw std::invalid_argument("capacity must be a positive integer"); this->capacity = capacity; } void put(const T& value) { { boost::unique_lock lk(mut); while ((int)q.size() >= capacity) { // Queue is full, must wait for 'take' condFull.wait(lk); } q.push(value); } condEmpty.notify_one(); } T take() { T result; { boost::unique_lock lk(mut); while (q.empty()) { // Queue is empty, must wait for 'put' condEmpty.wait(lk); } result = q.front(); q.pop(); } condFull.notify_one(); return result; } }; void producer(BlockingQueue* blkQueue) { std::string arr[] = { "nice", "to", "meet", "you" }; for (int i = 0; i < 4; ++i) { std::string& data = arr[i]; cout << "Producer: " << data << endl; blkQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } } void consumer(BlockingQueue* blkQueue) { std::string data; boost::this_thread::sleep_for(boost::chrono::seconds(5)); for (int i = 0; i < 4; ++i) { data = blkQueue->take(); cout << "\tConsumer: " << data << endl; if (0 == i) boost::this_thread::sleep_for(boost::chrono::seconds(5)); } } int main() { BlockingQueue blkQueue(2); // capacity = 2 boost::thread thProducer(&producer, &blkQueue); boost::thread thConsumer(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-boost/exer07a-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version A: Solving the problem using a condition variable */ #include #include #include #include #include #include using namespace std; #define sleepsec(secs) \ do { boost::this_thread::sleep_for(boost::chrono::seconds(secs)); } while (0) struct Counter { int value; boost::mutex mut; boost::condition_variable cond; Counter(int value) : value(value) { } }; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, Counter& counter) { for (size_t i = 0; i < lstFileName.size(); ++i) { const string& fileName = lstFileName[i]; // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; { boost::unique_lock(counter.mut); --counter.value; counter.cond.notify_one(); } // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { vector lstFileName; lstFileName.push_back("foo.html"); lstFileName.push_back("bar.json"); Counter counter(lstFileName.size()); // The server checks auth user while reading files, concurrently boost::thread th(&processFiles, boost::cref(lstFileName), boost::ref(counter)); checkAuthUser(); // The server waits for completion of loading files { boost::unique_lock lk(counter.mut); while (counter.value > 0) { counter.cond.wait_for(lk, boost::chrono::seconds(10)); // timeout = 10 seconds } } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-boost/exer07b-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version B: Solving the problem using a semaphore */ #include #include #include #include #include #include #include "mylib-semaphore.hpp" using namespace std; #define sleepsec(secs) \ do { boost::this_thread::sleep_for(boost::chrono::seconds(secs)); } while (0) void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, mylib::Semaphore& sem) { for (size_t i = 0; i < lstFileName.size(); ++i) { const string& fileName = lstFileName[i]; // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; sem.release(); // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { vector lstFileName; lstFileName.push_back("foo.html"); lstFileName.push_back("bar.json"); mylib::Semaphore sem(0); // The server checks auth user while reading files, concurrently boost::thread th(&processFiles, boost::cref(lstFileName), boost::ref(sem)); checkAuthUser(); // The server waits for completion of loading files for (size_t i = lstFileName.size(); i > 0; --i) { sem.acquire(); } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-boost/exer07c-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version C: Solving the problem using a count-down latch */ #include #include #include #include #include #include #include using namespace std; #define sleepsec(secs) \ do { boost::this_thread::sleep_for(boost::chrono::seconds(secs)); } while (0) void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, boost::latch& rdLatch) { for (size_t i = 0; i < lstFileName.size(); ++i) { const string& fileName = lstFileName[i]; // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; rdLatch.count_down(); // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { vector lstFileName; lstFileName.push_back("foo.html"); lstFileName.push_back("bar.json"); boost::latch readFileLatch(lstFileName.size()); // The server checks auth user while reading files, concurrently boost::thread th(&processFiles, boost::cref(lstFileName), boost::ref(readFileLatch)); checkAuthUser(); // The server waits for completion of loading files readFileLatch.wait(); cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-boost/exer07d-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version D: Solving the problem using a blocking queue */ #include #include #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; #define sleepsec(secs) \ do { boost::this_thread::sleep_for(boost::chrono::seconds(secs)); } while (0) void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, mylib::BlockingQueue& blkq) { for (size_t i = 0; i < lstFileName.size(); ++i) { const string& fileName = lstFileName[i]; // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; blkq.put(fileName); // You may put file data here // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { vector lstFileName; lstFileName.push_back("foo.html"); lstFileName.push_back("bar.json"); mylib::BlockingQueue blkq; // The server checks auth user while reading files, concurrently boost::thread th(&processFiles, boost::cref(lstFileName), boost::ref(blkq)); checkAuthUser(); // The server waits for completion of loading files for (size_t i = lstFileName.size(); i > 0; --i) { blkq.take(); } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-boost/exer08-exec-service-itask.hpp ================================================ #ifndef _MY_EXEC_SERVICE_ITASK_HPP_ #define _MY_EXEC_SERVICE_ITASK_HPP_ // interface ITask class ITask { public: virtual ~ITask() { } virtual void run() = 0; }; #endif // _MY_EXEC_SERVICE_ITASK_HPP_ ================================================ FILE: cpp/cpp-boost/exer08-exec-service-main.cpp ================================================ /* EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION */ #include #include #include #include "exer08-exec-service-itask.hpp" #include "exer08-exec-service-v0a.hpp" #include "exer08-exec-service-v0b.hpp" #include "exer08-exec-service-v1a.hpp" #include "exer08-exec-service-v1b.hpp" #include "exer08-exec-service-v2a.hpp" #include "exer08-exec-service-v2b.hpp" class MyTask : public ITask { public: char id; public: void run() { std::cout << "Task " << id << " is starting" << std::endl; boost::this_thread::sleep_for(boost::chrono::seconds(3)); std::cout << "Task " << id << " is completed" << std::endl; } }; int main() { const int NUM_THREADS = 2; const int NUM_TASKS = 5; MyExecServiceV0A execService(NUM_THREADS); std::vector lstTask(NUM_TASKS); for (int i = 0; i < NUM_TASKS; ++i) lstTask[i].id = 'A' + i; for (int i = 0; i < NUM_TASKS; ++i) { execService.submit(&lstTask[i]); } std::cout << "All tasks are submitted" << std::endl; execService.waitTaskDone(); std::cout << "All tasks are completed" << std::endl; execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-boost/exer08-exec-service-v0a.hpp ================================================ /* MY EXECUTOR SERVICE Version 0A: The easiest executor service - It uses a blocking queue as underlying mechanism. */ #ifndef _MY_EXEC_SERVICE_V0A_HPP_ #define _MY_EXEC_SERVICE_V0A_HPP_ #include #include #include #include #include "mylib-blockingqueue.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV0A { private: int numThreads; boost::thread_group lstTh; mylib::BlockingQueue taskPending; public: MyExecServiceV0A(int numThreads) { init(numThreads); } private: MyExecServiceV0A(const MyExecServiceV0A& other) : numThreads(0) { } void operator=(const MyExecServiceV0A& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) MyExecServiceV0A(const MyExecServiceV0A&& other) : numThreads(0) { } void operator=(const MyExecServiceV0A&& other) { } #endif private: void init(int numThreads) { this->numThreads = numThreads; for (int i = 0; i < numThreads; ++i) { lstTh.add_thread(new boost::thread(&threadWorkerFunc, this)); } } public: void submit(ITask* task) { taskPending.add(task); } void waitTaskDone() { // This ExecService is too simple, // so there is no implementation for waitTaskDone() boost::this_thread::sleep_for(boost::chrono::seconds(11)); // fake behaviour } void shutdown() { // This ExecService is too simple, // so there is no implementation for shutdown() std::cout << "No implementation for shutdown()." << std::endl; std::cout << "You need to exit the app manually." << std::endl; lstTh.join_all(); } private: static void threadWorkerFunc(MyExecServiceV0A* thisPtr) { mylib::BlockingQueue & taskPending = thisPtr->taskPending; ITask* task = 0; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK task = taskPending.take(); // DO THE TASK task->run(); } } }; #endif // _MY_EXEC_SERVICE_V0A_HPP_ ================================================ FILE: cpp/cpp-boost/exer08-exec-service-v0b.hpp ================================================ /* MY EXECUTOR SERVICE Version 0B: The easiest executor service - It uses a blocking queue as underlying mechanism. - It supports waitTaskDone() and shutdown(). */ #ifndef _MY_EXEC_SERVICE_V0B_HPP_ #define _MY_EXEC_SERVICE_V0B_HPP_ #include #include #include #include "mylib-blockingqueue.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV0B { private: int numThreads; boost::thread_group lstTh; mylib::BlockingQueue taskPending; boost::atomic_int32_t counterTaskRunning; volatile bool forceThreadShutdown; const class : ITask { void run() { } } emptyTask; public: MyExecServiceV0B(int numThreads) { init(numThreads); } private: MyExecServiceV0B(const MyExecServiceV0B& other) : numThreads(0) { } void operator=(const MyExecServiceV0B& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) MyExecServiceV0B(const MyExecServiceV0B&& other) : numThreads(0) { } void operator=(const MyExecServiceV0B&& other) { } #endif private: void init(int numThreads) { this->numThreads = numThreads; counterTaskRunning = 0; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) { lstTh.add_thread(new boost::thread(&threadWorkerFunc, this)); } } public: void submit(ITask* task) { taskPending.add(task); } void waitTaskDone() { // This ExecService is too simple, // so there is no good implementation for waitTaskDone() while (false == taskPending.empty() || counterTaskRunning > 0) { boost::this_thread::sleep_for(boost::chrono::seconds(1)); // boost::this_thread::yield(); } } void shutdown() { forceThreadShutdown = true; taskPending.clear(); // Invoke blocked threads by adding "empty" tasks for (int i = 0; i < numThreads; ++i) { taskPending.put( (ITask* const) &emptyTask ); } lstTh.join_all(); numThreads = 0; } private: static void threadWorkerFunc(MyExecServiceV0B* thisPtr) { mylib::BlockingQueue & taskPending = thisPtr->taskPending; boost::atomic_int32_t & counterTaskRunning = thisPtr->counterTaskRunning; volatile bool & forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = 0; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK task = taskPending.take(); // If shutdown() was called, then exit the function if (forceThreadShutdown) { break; } // DO THE TASK ++counterTaskRunning; task->run(); --counterTaskRunning; } } }; #endif // _MY_EXEC_SERVICE_V0B_HPP_ ================================================ FILE: cpp/cpp-boost/exer08-exec-service-v1a.hpp ================================================ /* MY EXECUTOR SERVICE Version 1A: Simple executor service - Method "waitTaskDone" invokes thread sleeps in loop (which can cause performance problems). */ #ifndef _MY_EXEC_SERVICE_V1A_HPP_ #define _MY_EXEC_SERVICE_V1A_HPP_ #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV1A { private: typedef boost::unique_lock uniquelk; private: int numThreads; boost::thread_group lstTh; std::queue taskPending; boost::mutex mutTaskPending; boost::condition_variable condTaskPending; boost::atomic_int32_t counterTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV1A(int numThreads) { init(numThreads); } private: MyExecServiceV1A(const MyExecServiceV1A& other) : numThreads(0) { } void operator=(const MyExecServiceV1A& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) MyExecServiceV1A(const MyExecServiceV1A&& other) : numThreads(0) { } void operator=(const MyExecServiceV1A&& other) { } #endif private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; counterTaskRunning = 0; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) { lstTh.add_thread(new boost::thread(&threadWorkerFunc, this)); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { bool done = false; for (;;) { { uniquelk lk(mutTaskPending); if (taskPending.empty() && 0 == counterTaskRunning) { done = true; } } if (done) { break; } boost::this_thread::sleep_for(boost::chrono::seconds(1)); // boost::this_thread::yield(); } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; while (false == taskPending.empty()) taskPending.pop(); } condTaskPending.notify_all(); lstTh.join_all(); numThreads = 0; } private: static void threadWorkerFunc(MyExecServiceV1A* thisPtr) { std::queue & taskPending = thisPtr->taskPending; boost::mutex & mutTaskPending = thisPtr->mutTaskPending; boost::condition_variable & condTaskPending = thisPtr->condTaskPending; boost::atomic_int32_t & counterTaskRunning = thisPtr->counterTaskRunning; volatile bool & forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = 0; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; } // DO THE TASK task->run(); --counterTaskRunning; } } }; #endif // _MY_EXEC_SERVICE_V1A_HPP_ ================================================ FILE: cpp/cpp-boost/exer08-exec-service-v1b.hpp ================================================ /* MY EXECUTOR SERVICE Version 1B: Simple executor service - Method "waitTaskDone" uses a condition variable to synchronize. */ #ifndef _MY_EXEC_SERVICE_V1B_HPP_ #define _MY_EXEC_SERVICE_V1B_HPP_ #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV1B { private: typedef boost::unique_lock uniquelk; private: int numThreads; boost::thread_group lstTh; std::queue taskPending; boost::mutex mutTaskPending; boost::condition_variable condTaskPending; int counterTaskRunning; boost::mutex mutTaskRunning; boost::condition_variable condTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV1B(int numThreads) { init(numThreads); } private: MyExecServiceV1B(const MyExecServiceV1B& other) : numThreads(0) { } void operator=(const MyExecServiceV1B& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) MyExecServiceV1B(const MyExecServiceV1B&& other) : numThreads(0) { } void operator=(const MyExecServiceV1B&& other) { } #endif private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; counterTaskRunning = 0; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) { lstTh.add_thread(new boost::thread(&threadWorkerFunc, this)); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { uniquelk lkPending(mutTaskPending); if (taskPending.empty()) { uniquelk lkRunning(mutTaskRunning); while (counterTaskRunning > 0) condTaskRunning.wait(lkRunning); // no pending task and no running task break; } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; while (false == taskPending.empty()) taskPending.pop(); } condTaskPending.notify_all(); lstTh.join_all(); numThreads = 0; } private: static void threadWorkerFunc(MyExecServiceV1B* thisPtr) { std::queue & taskPending = thisPtr->taskPending; boost::mutex & mutTaskPending = thisPtr->mutTaskPending; boost::condition_variable & condTaskPending = thisPtr->condTaskPending; int & counterTaskRunning = thisPtr->counterTaskRunning; boost::mutex & mutTaskRunning = thisPtr->mutTaskRunning; boost::condition_variable & condTaskRunning = thisPtr->condTaskRunning; volatile bool & forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = 0; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; } // DO THE TASK task->run(); { uniquelk lkRunning(mutTaskRunning); --counterTaskRunning; if (0 == counterTaskRunning) { condTaskRunning.notify_one(); } } } } }; #endif // _MY_EXEC_SERVICE_V1B_HPP_ ================================================ FILE: cpp/cpp-boost/exer08-exec-service-v2a.hpp ================================================ /* MY EXECUTOR SERVICE Version 2A: The executor service storing running tasks - Method "waitTaskDone" uses a semaphore to synchronize. */ #ifndef _MY_EXEC_SERVICE_V2A_HPP_ #define _MY_EXEC_SERVICE_V2A_HPP_ #include #include #include #include #include "mylib-semaphore.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV2A { private: typedef boost::unique_lock uniquelk; private: int numThreads; boost::thread_group lstTh; std::queue taskPending; boost::mutex mutTaskPending; boost::condition_variable condTaskPending; std::list taskRunning; boost::mutex mutTaskRunning; mylib::Semaphore counterTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV2A(int numThreads) : counterTaskRunning(0) { init(numThreads); } private: MyExecServiceV2A(const MyExecServiceV2A& other) : numThreads(0), counterTaskRunning(0) { } void operator=(const MyExecServiceV2A& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) MyExecServiceV2A(const MyExecServiceV2A&& other) : numThreads(0), counterTaskRunning(0) { } void operator=(const MyExecServiceV2A&& other) { } #endif private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) { lstTh.add_thread(new boost::thread(&threadWorkerFunc, this)); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { counterTaskRunning.acquire(); { uniquelk lkPending(mutTaskPending); uniquelk lkRunning(mutTaskRunning); if (taskPending.empty() && taskRunning.empty()) { break; } } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; while (false == taskPending.empty()) taskPending.pop(); } condTaskPending.notify_all(); lstTh.join_all(); numThreads = 0; } private: static void threadWorkerFunc(MyExecServiceV2A* thisPtr) { std::queue & taskPending = thisPtr->taskPending; boost::mutex & mutTaskPending = thisPtr->mutTaskPending; boost::condition_variable & condTaskPending = thisPtr->condTaskPending; std::list & taskRunning = thisPtr->taskRunning; boost::mutex & mutTaskRunning = thisPtr->mutTaskRunning; mylib::Semaphore & counterTaskRunning = thisPtr->counterTaskRunning; volatile bool & forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = 0; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); // PUSH IT TO THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.push_back(task); } } // DO THE TASK task->run(); // REMOVE IT FROM THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.remove(task); counterTaskRunning.release(); } } } }; #endif // _MY_EXEC_SERVICE_V2A_HPP_ ================================================ FILE: cpp/cpp-boost/exer08-exec-service-v2b.hpp ================================================ /* MY EXECUTOR SERVICE Version 2B: The executor service storing running tasks - Method "waitTaskDone" uses a condition variable to synchronize. */ #ifndef _MY_EXEC_SERVICE_V2B_HPP_ #define _MY_EXEC_SERVICE_V2B_HPP_ #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV2B { private: typedef boost::unique_lock uniquelk; private: int numThreads; boost::thread_group lstTh; std::queue taskPending; boost::mutex mutTaskPending; boost::condition_variable condTaskPending; std::list taskRunning; boost::mutex mutTaskRunning; boost::condition_variable condTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV2B(int numThreads) { init(numThreads); } private: MyExecServiceV2B(const MyExecServiceV2B& other) : numThreads(0) { } void operator=(const MyExecServiceV2B& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) MyExecServiceV2B(const MyExecServiceV2B&& other) : numThreads(0) { } void operator=(const MyExecServiceV2B&& other) { } #endif private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) { lstTh.add_thread(new boost::thread(&threadWorkerFunc, this)); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { uniquelk lkPending(mutTaskPending); if (taskPending.empty()) { uniquelk lkRunning(mutTaskRunning); while (false == taskRunning.empty()) condTaskRunning.wait(lkRunning); // no pending task and no running task break; } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; while (false == taskPending.empty()) taskPending.pop(); } condTaskPending.notify_all(); lstTh.join_all(); numThreads = 0; } private: static void threadWorkerFunc(MyExecServiceV2B* thisPtr) { std::queue & taskPending = thisPtr->taskPending; boost::mutex & mutTaskPending = thisPtr->mutTaskPending; boost::condition_variable & condTaskPending = thisPtr->condTaskPending; std::list & taskRunning = thisPtr->taskRunning; boost::mutex & mutTaskRunning = thisPtr->mutTaskRunning; boost::condition_variable & condTaskRunning = thisPtr->condTaskRunning; volatile bool & forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = 0; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); // PUSH IT TO THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.push_back(task); } } // DO THE TASK task->run(); // REMOVE IT FROM THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.remove(task); condTaskRunning.notify_one(); } } } }; #endif // _MY_EXEC_SERVICE_V2B_HPP_ ================================================ FILE: cpp/cpp-boost/mylib-blockingqueue.hpp ================================================ /****************************************************** * * File name: mylib-blockingqueue.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The blocking queue implementation in C++98 Boost threading * ******************************************************/ #ifndef _MYLIB_BLOCKING_QUEUE_HPP_ #define _MYLIB_BLOCKING_QUEUE_HPP_ #include #include #include #include #include #include namespace mylib { template class BlockingQueue { private: typedef boost::unique_lock uniquelk; private: boost::condition_variable condEmpty; boost::condition_variable condFull; boost::mutex mut; size_t capacity; std::queue q; public: BlockingQueue() : capacity(std::numeric_limits::max()) { } BlockingQueue(size_t capacity) : capacity(capacity) { } private: BlockingQueue(const BlockingQueue& other) { } void operator=(const BlockingQueue& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) BlockingQueue(const BlockingQueue&& other) { } void operator=(const BlockingQueue&& other) { } #endif public: bool empty() const { return q.empty(); } size_t size() const { return q.size(); } // sync enqueue void put(const T& value) { uniquelk lk(mut); while (q.size() >= capacity) { condFull.wait(lk); } q.push(value); condEmpty.notify_one(); } // sync dequeue T take() { uniquelk lk(mut); while (q.empty()) { condEmpty.wait(lk); } T result = q.front(); q.pop(); condFull.notify_one(); return result; } // async enqueue void add(const T& value) { // Note: For asynchronous operations, we should use a long-live background thread // instead of using a temporary thread boost::thread(&BlockingQueue::put, this, value).detach(); } // returns false if queue is empty, otherwise returns true and assigns the result bool peek(T& result) const { uniquelk(mut); if (q.empty()) { return false; } result = q.front(); return true; } void clear() { uniquelk(mut); while (false == q.empty()) { q.pop(); } } }; // BlockingQueue } // namespace mylib #endif // _MYLIB_BLOCKING_QUEUE_HPP_ ================================================ FILE: cpp/cpp-boost/mylib-random.hpp ================================================ /****************************************************** * * File name: mylib-random.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The random utility in C++98 Boost * ******************************************************/ #ifndef _MYLIB_RANDOM_HPP_ #define _MYLIB_RANDOM_HPP_ #include #include #include #include namespace mylib { class RandInt { private: boost::random::random_device rd; boost::random::mt19937 mt; boost::random::uniform_int_distribution dist; public: RandInt() { init(0, std::numeric_limits::max()); } RandInt(int minValue, int maxValueInclusive) { init(minValue, maxValueInclusive); } void init(int minValue, int maxValueInclusive) { dist = boost::random::uniform_int_distribution(minValue, maxValueInclusive); mt.seed(rd()); } int next() { return dist(mt); } private: RandInt(const RandInt& other) { } void operator=(const RandInt& other) { } // STATIC private: static RandInt publicRandInt; public: static int get(int maxExclusive) { return publicRandInt.next() % maxExclusive; } }; // RandInt RandInt RandInt::publicRandInt; } // namespace mylib #endif // _MYLIB_RANDOM_HPP_ ================================================ FILE: cpp/cpp-boost/mylib-semaphore.hpp ================================================ /****************************************************** * * File name: mylib-semaphore.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The semaphore implementation in C++98 Boost threading * This is just a simulation based on the condition variable * ******************************************************/ #ifndef _MYLIB_SEMAPHORE_HPP_ #define _MYLIB_SEMAPHORE_HPP_ #include #include #include #include #include namespace mylib { class Semaphore { private: typedef boost::unique_lock uniquelk; private: volatile int value; boost::condition_variable condFreeState; boost::mutex mut; static int MIN_VALUE; static int MAX_VALUE; public: Semaphore(int initialValue) { this->value = initialValue; } private: Semaphore(const Semaphore& other) { } void operator=(const Semaphore& other) { } #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) Semaphore(const Semaphore&& other) { } void operator=(const Semaphore&& other) { } #endif public: void acquire() { uniquelk lk(mut); while (value <= 0) { condFreeState.wait(lk); } if (value > MIN_VALUE) { --value; } } void release() { uniquelk lk(mut); if (value < MAX_VALUE) { ++value; } if (value >= 0) { condFreeState.notify_one(); } } int getValue() const { return value; // does not block } }; // Semaphore int Semaphore::MIN_VALUE = std::numeric_limits::min(); int Semaphore::MAX_VALUE = std::numeric_limits::max(); } // namespace mylib #endif // _MYLIB_SEMAPHORE_HPP_ ================================================ FILE: cpp/cpp-boost/mylib-time.hpp ================================================ /****************************************************** * * File name: mylib-time.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The time utility in C++98 Boost * ******************************************************/ #ifndef _MYLIB_TIME_HPP_ #define _MYLIB_TIME_HPP_ #include #include namespace mylib { namespace chro = boost::chrono; typedef chro::system_clock sysclock; class HiResClock { private: typedef chro::high_resolution_clock stdhrc; public: static inline stdhrc::time_point now() { return stdhrc::now(); } template< typename duType > static inline duType getTimeSpan( const stdhrc::time_point& tp1, const stdhrc::time_point& tp2) { duType res = chro::duration_cast(tp2 - tp1); return res; } template< typename duType > static inline duType getTimeSpan(const stdhrc::time_point& tpBefore) { stdhrc::time_point tpCurrent = HiResClock::now(); duType res = HiResClock::getTimeSpan(tpBefore, tpCurrent); return res; } }; // HiResClock char* getTimePointStr(const sysclock::time_point& tp) { std::time_t timeStamp = sysclock::to_time_t(tp); return std::ctime(&timeStamp); } template class clock::time_point getTimePoint( int year, int month, int day, int hour, int minute, int second) { std::tm t; t.tm_year = year - 1900; t.tm_mon = month - 1; t.tm_mday = day; t.tm_hour = hour; t.tm_min = minute; t.tm_sec = second; return clock::from_time_t(std::mktime(&t)); } // tp += numSeconds * 2; // tp -= (x % numSeconds) template chro::time_point getTimePointFutureFloor(const chro::time_point& tp, int numSeconds) { chro::seconds duSeconds(numSeconds); chro::duration durationFromTp = chro::time_point_cast(tp).time_since_epoch(); chro::duration durationFuture = durationFromTp + (duSeconds * 2); durationFuture = durationFuture - (durationFuture % duSeconds); chro::time_point tpFuture(durationFuture); return tpFuture; } } // namespace mylib #endif // _MYLIB_TIME_HPP_ ================================================ FILE: cpp/cpp-pthread/demo00.cpp ================================================ /* INTRODUCTION TO MULTITHREADING You should try running this app several times and see results. */ #include #include using namespace std; void* doTask(void*) { for (int i = 0; i < 300; ++i) cout << "B"; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, &doTask, nullptr); for (int i = 0; i < 300; ++i) cout << "A"; ret = pthread_join(tid, nullptr); cout << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo01-hello.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING */ #include #include using namespace std; void* doTask(void*) { cout << "Hello from example thread" << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, &doTask, nullptr); /* if (ret) { cerr << "Error: Unable to create thread " << ret << endl; return 1; } */ ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo02-join.cpp ================================================ /* THREAD JOINS */ #include #include using namespace std; void* doHeavyTask(void*) { // Do a heavy task, which takes a little time for (int i = 0; i < 2000000000; ++i); cout << "Done!" << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, &doHeavyTask, nullptr); ret = pthread_join(tid, nullptr); cout << "Good bye!" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo03a01-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version A01: The problem The id in statement "hello pthread with id..." might be DUPLICATED!!! Reason: Passing the address of variable i, so that all threads use a same value of i. */ #include #include using namespace std; void* doTask(void* ptrId) { int id = *(int*)ptrId; cout << "Hello pthread with id = " << id << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t lstTid[2]; int ret = 0; for (int i = 0; i < 2; ++i) ret = pthread_create(&lstTid[i], nullptr, &doTask, &i); for (int i = 0; i < 2; ++i) ret = pthread_join(lstTid[i], nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo03a02-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version A02: Solving the problem */ #include #include using namespace std; void* doTask(void* ptrId) { int id = *(int*)ptrId; cout << "Hello pthread with id = " << id << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t lstTid[2]; int lstArg[2]; int ret = 0; for (int i = 0; i < 2; ++i) { lstArg[i] = i + 1; ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } for (int i = 0; i < 2; ++i) ret = pthread_join(lstTid[i], nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo03b01-pass-arg.cpp ================================================ /* PASSING MULTIPLE ARGUMENTS Solution 01: Creating a custom struct */ #include #include #include using namespace std; struct ThreadArg { int x; double y; string z; }; void* doTask(void* argVoid) { auto arg = (ThreadArg*) argVoid; cout << arg->x << endl; cout << arg->y << endl; cout << arg->z << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; ThreadArg arg; int ret = 0; arg.x = 10; arg.y = -2.4; arg.z = "lorem ipsum"; ret = pthread_create(&tid, nullptr, &doTask, &arg); ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo03b02-pass-arg.cpp ================================================ /* PASSING MULTIPLE ARGUMENTS Solution 02: Using std::tuple */ #include #include #include #include using namespace std; void* doTask(void* argVoid) { auto arg = * (tuple *) argVoid; cout << std::get<0>(arg) << endl; cout << std::get<1>(arg) << endl; cout << std::get<2>(arg) << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; tuple arg; int ret = 0; // arg = std::make_tuple( 10, -2.4, "lorem ipsum" ); arg = { 10, -2.4, "lorem ipsum" }; ret = pthread_create(&tid, nullptr, &doTask, &arg); ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo04-sleep.cpp ================================================ /* SLEEP */ #include #include #include using namespace std; void* doTask(void* arg) { auto name = (const char*) arg; cout << name << " is sleeping" << endl; sleep(2); cout << name << " wakes up" << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, &doTask, (void*)"foo"); ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo05-id.cpp ================================================ /* GETTING THREAD'S ID */ #include #include #include using namespace std; void* doTask(void*) { sleep(2); cout << pthread_self() << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo, tidBar; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &doTask, nullptr); ret = pthread_create(&tidBar, nullptr, &doTask, nullptr); cout << "foo's id = " << tidFoo << endl; cout << "bar's id = " << tidBar << endl; ret = pthread_join(tidFoo, nullptr); ret = pthread_join(tidBar, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo06a-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version A: Using standard arrays */ #include #include using namespace std; void* doTask(void* arg) { auto index = *(int*) arg; cout << index; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 5; pthread_t lstTid[NUM_THREADS]; int lstArg[NUM_THREADS]; int ret = 0; for (int i = 0; i < NUM_THREADS; ++i) { lstArg[i] = i; ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo06b-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version B: Using the std::vector */ #include #include #include using namespace std; void* doTask(void* arg) { auto index = *(int*) arg; cout << index; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 5; vector lstTid(NUM_THREADS); vector lstArg(NUM_THREADS); int ret = 0; for (int i = 0; i < NUM_THREADS; ++i) { lstArg[i] = i; ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo07a-terminate.cpp ================================================ /* FORCING A THREAD TO TERMINATE (i.e. killing the thread) Version A: Using the flag 'isRunning' */ #include #include #include using namespace std; volatile bool isRunning; void* doTask(void*) { while (isRunning) { cout << "Running..." << endl; sleep(2); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; isRunning = true; ret = pthread_create(&tid, nullptr, &doTask, nullptr); sleep(6); isRunning = false; ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo07b-terminate.cpp ================================================ /* FORCING A THREAD TO TERMINATE (i.e. killing the thread) Version B: Using pthread_cancel */ #include #include #include using namespace std; void* doTask(void*) { while (1) { cout << "Running..." << endl; sleep(1); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, &doTask, nullptr); sleep(3); ret = pthread_cancel(tid); ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo08a-return-value.cpp ================================================ /* GETTING RETURNED VALUES FROM THREADS Version A: Values returned via pointers passed from arguments */ #include #include using namespace std; struct ThreadArg { int value; int *res; }; void* doubleValue(void* argVoid) { auto arg = (ThreadArg*) argVoid; *(arg->res) = arg->value * 2; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int result; ThreadArg arg; int ret = 0; arg = { 80, &result }; ret = pthread_create(&tid, nullptr, &doubleValue, &arg); ret = pthread_join(tid, nullptr); cout << result << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo08b-return-value.cpp ================================================ /* GETTING RETURNED VALUES FROM THREADS Version B: Values returned via pthread_exit */ #include #include using namespace std; void* doubleValue(void* arg) { auto value = *(int*) arg; int *result = new int; *result = value * 2; pthread_exit((void*)result); return (void*)result; } int main() { pthread_t tid; int arg = 80; int *result = nullptr; int ret = 0; ret = pthread_create(&tid, nullptr, &doubleValue, &arg); ret = pthread_join(tid, (void**)&result); cout << (*result) << endl; delete result; result = nullptr; return 0; } ================================================ FILE: cpp/cpp-pthread/demo09a-detach.cpp ================================================ /* THREAD DETACHING */ #include #include #include using namespace std; void* foo(void*) { cout << "foo is starting..." << endl; sleep(2); cout << "foo is exiting..." << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); ret = pthread_detach(tidFoo); if (ret) { cout << "Error: Cannot detach tidFoo" << endl; } // ret = pthread_join(tidFoo, nullptr); // if (ret) { // cout << "Error: Cannot join tidFoo" << endl; // } // If I comment this statement, // tidFoo will be forced into terminating with main thread sleep(3); cout << "Main thread is exiting" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo09b-detach.cpp ================================================ /* THREAD DETACHING */ #include #include #include using namespace std; void* foo(void*) { int ret = 0; cout << "foo is starting..." << endl; if ( ret = pthread_detach(pthread_self()) ) { cout << "Error: Cannot detach" << endl; } sleep(2); cout << "foo is exiting..." << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); sleep(3); cout << "Main thread is exiting" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo10-yield.cpp ================================================ /* THREAD YIELDING */ #include #include #include #include #include "../cpp-std/mylib-time.hpp" using namespace std; using chrmicro = std::chrono::microseconds; using hrclock = mylib::HiResClock; void littleSleep(int us) { auto tpStart = hrclock::now(); auto tpEnd = tpStart + chrmicro(us); int ret = 0; do { // ret = pthread_yield(); ret = sched_yield(); } while (hrclock::now() < tpEnd); } int main() { auto tpStartMeasure = hrclock::now(); littleSleep(130); auto timeElapsed = hrclock::getTimeSpan(tpStartMeasure); cout << "Elapsed time: " << timeElapsed.count() << " microseonds" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo11a-exec-service.cpp ================================================ /* EXECUTOR SERVICES AND THREAD POOLS Executor services in C++ POSIX threading are not supported by default. So, I use mylib::ExecService for this demonstration. */ #include #include "mylib-execservice.hpp" using namespace std; void doTask() { cout << "Hello the Executor Service" << endl; } class MyFunctor { public: void operator()() { cout << "Hello Multithreading" << endl; } }; int main() { // INIT THE EXECUTOR SERVICE WITH 2 THREADS auto execService = mylib::ExecService(2); // SUBMIT execService.submit([] { cout << "Hello World" << endl; }); execService.submit(&doTask); execService.submit(MyFunctor()); // WAIT FOR THE COMPLETION OF ALL TASKS AND SHUTDOWN EXECUTOR SERVICE execService.waitTaskDone(); execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-pthread/demo11b-exec-service.cpp ================================================ /* EXECUTOR SERVICES AND THREAD POOLS Executor services in C++ POSIX threading are not supported by default. So, I use mylib::ExecService for this demonstration. */ #include #include #include "mylib-execservice.hpp" using namespace std; int main() { constexpr int NUM_THREADS = 2; constexpr int NUM_TASKS = 5; auto execService = mylib::ExecService(NUM_THREADS); for (int i = 0; i < NUM_TASKS; ++i) { execService.submit([=] { char id = 'A' + i; cout << "Task " << id << " is starting" << endl; sleep(3); cout << "Task " << id << " is completed" << endl; }); } cout << "All tasks are submitted" << endl; execService.waitTaskDone(); cout << "All tasks are completed" << endl; execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-pthread/demo12a-race-condition.cpp ================================================ /* RACE CONDITIONS */ #include #include #include using namespace std; void* doTask(void* arg) { int index = *(int*) arg; sleep(1); cout << index; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 4; pthread_t lstTid[NUM_THREADS]; int lstArg[NUM_THREADS]; int ret = 0; for (int i = 0; i < NUM_THREADS; ++i) { lstArg[i] = i; ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo12b01-data-race-single.cpp ================================================ /* DATA RACES Version 01: Without multithreading */ #include #include using namespace std; int *a = nullptr; int N = 0; int getResult() { a = (int*)calloc(sizeof(int), N + 1); for (int i = 1; i <= N; ++i) if (0 == i % 2 || 0 == i % 3) a[i] = 1; int result = 0; for (int i = 1; i <= N; ++i) if (a[i]) ++result; free(a); a = nullptr; return result; } int main() { N = 8; int result = getResult(); cout << "Numbers of integers that are divisible by 2 or 3 is: " << result << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo12b02-data-race-multi.cpp ================================================ /* DATA RACES Version 02: Multithreading */ #include #include #include using namespace std; int* a = nullptr; int N = 0; void* markDiv2(void*) { for (int i = 2; i <= N; i += 2) a[i] = 1; pthread_exit(nullptr); return nullptr; } void* markDiv3(void*) { for (int i = 3; i <= N; i += 3) a[i] = 1; pthread_exit(nullptr); return nullptr; } int main() { N = 8; pthread_t tidDiv2, tidDiv3; int ret = 0; a = (int*)calloc(sizeof(int), N + 1); ret = pthread_create(&tidDiv2, nullptr, &markDiv2, nullptr); ret = pthread_create(&tidDiv3, nullptr, &markDiv3, nullptr); ret = pthread_join(tidDiv2, nullptr); ret = pthread_join(tidDiv3, nullptr); int result = 0; for (int i = 1; i <= N; ++i) if (a[i]) ++result; free(a); a = nullptr; cout << "Numbers of integers that are divisible by 2 or 3 is: " << result << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo12bex-data-race-fork.cpp ================================================ /* DATA RACES */ #include #include #include using namespace std; int main() { int pid = 0; pid = fork(); if (-1 == pid) { cerr << "Cannot fork" << endl; return 1; } ofstream ofs; ofs.open("tmp-output.txt"); if (ofs.fail()) { return 1; } cout << "Writing to the file..." << endl; ofs << pid << endl; ofs.close(); return 0; } /* The content of the file is UNKNOWN. It may be: 0 or 34 or 0 34 or 34 0 Assume that 34 and 0 are process ids. */ ================================================ FILE: cpp/cpp-pthread/demo12c01-race-cond-data-race.cpp ================================================ /* RACE CONDITIONS AND DATA RACES */ #include #include #include using namespace std; int counter = 0; void* increaseCounter(void*) { sleep(1); for (int i = 0; i < 1000; ++i) { counter += 1; } pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 16; pthread_t lstTid[NUM_THREADS]; int ret = 0; for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &increaseCounter, nullptr); } for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_join(lstTid[i], nullptr); } cout << "counter = " << counter << endl; // We are NOT sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-pthread/demo12c02-race-cond-data-race.cpp ================================================ /* RACE CONDITIONS AND DATA RACES */ #include #include #include using namespace std; int counter = 0; void* doTaskA(void*) { sleep(1); while (counter < 10) ++counter; cout << "A won !!!" << endl; pthread_exit(nullptr); return nullptr; } void* doTaskB(void*) { sleep(1); while (counter > -10) --counter; cout << "B won !!!" << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidA, tidB; int ret = 0; ret = pthread_create(&tidA, nullptr, &doTaskA, nullptr); ret = pthread_create(&tidB, nullptr, &doTaskB, nullptr); ret = pthread_join(tidA, nullptr); ret = pthread_join(tidB, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo13a-mutex.cpp ================================================ /* MUTEXES */ #include #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void* doTask(void*) { sleep(1); pthread_mutex_lock(&mut); for (int i = 0; i < 1000; ++i) ++counter; pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 16; pthread_t lstTid[NUM_THREADS]; int ret = 0; for (auto&& tid : lstTid) { ret = pthread_create(&tid, nullptr, &doTask, nullptr); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << "counter = " << counter << endl; // We are sure that counter = 16000 ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo13b-mutex-trylock.cpp ================================================ /* MUTEXES Locking with a nonblocking mutex Use pthread_mutex_trylock to attempt to lock the mutex pointed to by mutex. */ #include #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void* doTask(void*) { int ret = 0; sleep(1); ret = pthread_mutex_trylock(&mut); if (ret) { /* switch (ret) { case EBUSY: The mutex could not be acquired because the mutex pointed to by mutex was already locked. case EAGAIN: The mutex could not be acquired because the maximum number of recursive locks for mutex has been exceeded. case EOWNERDEAD: The last owner of this mutex died while holding the mutex. This mutex is now owned by the caller. The caller must attempt to make the state protected by the mutex consistent. case ENOTRECOVERABLE: The mutex you are trying to acquire is protecting state left irrecoverable by the mutex's previous owner that died while holding the lock. The mutex has not been acquired. This condition can occur when the lock was previously acquired with EOWNERDEAD and the owner was unable to cleanup the state and had unlocked the mutex without making the mutex state consistent. case ENOMEM: The limit on the number of simultaneously held mutexes has been exceeded. } */ pthread_exit(nullptr); return nullptr; } for (int i = 0; i < 10000; ++i) ++counter; pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 3; pthread_t lstTid[NUM_THREADS]; int ret = 0; for (auto&& tid : lstTid) { ret = pthread_create(&tid, nullptr, &doTask, nullptr); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << "counter = " << counter << endl; // counter can be 10000, 20000 or 30000 ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo14-synchronized-block.cpp ================================================ /* SYNCHRONIZED BLOCKS Synchronized blocks in C++ POSIX threading are not supported by default. To demonstate synchronized blocks, I implement the class LockGuard. Now, let's see the code: { LockGuard lk(&mutex); // Do something in the critical section } When go to the end of the code block, lk object shall execute it's destructor and release mutex. */ #include #include #include using namespace std; class LockGuard { private: pthread_mutex_t* mut = nullptr; private: LockGuard(const LockGuard&) = delete; LockGuard(const LockGuard&&) = delete; void operator=(const LockGuard&) = delete; void operator=(const LockGuard&&) = delete; public: LockGuard(pthread_mutex_t* mut) { this->mut = mut; // Assume that mut != nullptr pthread_mutex_lock(this->mut); } ~LockGuard() { pthread_mutex_unlock(this->mut); } }; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void* doTask(void*) { sleep(1); // This is the "synchronized block" { LockGuard lk(&mut); for (int i = 0; i < 1000; ++i) ++counter; } pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 16; pthread_t lstTid[NUM_THREADS]; int ret = 0; for (auto&& tid : lstTid) { ret = pthread_create(&tid, nullptr, &doTask, nullptr); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << "counter = " << counter << endl; // We are sure that counter = 16000 ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo15a-deadlock.cpp ================================================ /* DEADLOCK Version A */ #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; void* doTask(void* arg) { auto name = (const char*) arg; pthread_mutex_lock(&mut); cout << name << " acquired resource" << endl; // pthread_mutex_unlock(&mut); // Forget this statement ==> deadlock pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo, tidBar; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &doTask, (void*)"foo"); ret = pthread_create(&tidBar, nullptr, &doTask, (void*)"bar"); ret = pthread_join(tidFoo, nullptr); ret = pthread_join(tidBar, nullptr); cout << "You will never see this statement due to deadlock!" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo15b-deadlock.cpp ================================================ /* DEADLOCK Version B */ #include #include #include using namespace std; pthread_mutex_t mutResourceA = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutResourceB = PTHREAD_MUTEX_INITIALIZER; void* foo(void*) { pthread_mutex_lock(&mutResourceA); cout << "foo acquired resource A" << endl; sleep(1); pthread_mutex_lock(&mutResourceB); cout << "foo acquired resource B" << endl; pthread_mutex_unlock(&mutResourceB); pthread_mutex_unlock(&mutResourceA); pthread_exit(nullptr); return nullptr; } void* bar(void*) { pthread_mutex_lock(&mutResourceB); cout << "bar acquired resource B" << endl; sleep(1); pthread_mutex_lock(&mutResourceA); cout << "bar acquired resource A" << endl; pthread_mutex_unlock(&mutResourceA); pthread_mutex_unlock(&mutResourceB); pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo, tidBar; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); ret = pthread_create(&tidBar, nullptr, &bar, nullptr); ret = pthread_join(tidFoo, nullptr); ret = pthread_join(tidBar, nullptr); ret = pthread_mutex_destroy(&mutResourceA); ret = pthread_mutex_destroy(&mutResourceB); cout << "You will never see this statement due to deadlock!" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo16-monitor.cpp ================================================ /* MONITORS Implementation of a monitor for managing a counter */ #include #include #include using namespace std; class Monitor { private: pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int* pCounter = nullptr; public: // Should disable copy/move constructors, copy/move assignment operators void init(int* pCounter) { destroy(); mut = PTHREAD_MUTEX_INITIALIZER; this->pCounter = pCounter; } void increaseCounter() { int ret = 0; ret = pthread_mutex_lock(&mut); (*pCounter) += 1; ret = pthread_mutex_unlock(&mut); } void destroy() { pthread_mutex_destroy(&mut); } }; void* doTask(void* arg) { auto monitor = (Monitor*) arg; sleep(1); for (int i = 0; i < 1000; ++i) monitor->increaseCounter(); pthread_exit(nullptr); return nullptr; } int main() { int counter = 0; Monitor monitor; constexpr int NUM_THREADS = 16; pthread_t lstTid[NUM_THREADS]; int ret = 0; monitor.init(&counter); for (auto&& tid : lstTid) { ret = pthread_create(&tid, nullptr, &doTask, &monitor); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } monitor.destroy(); cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo17a-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version A: Introduction to reentrant locks */ #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; void* doTask(void*) { pthread_mutex_lock(&mut); cout << "First time acquiring the resource" << endl; pthread_mutex_lock(&mut); cout << "Second time acquiring the resource" << endl; pthread_mutex_unlock(&mut); pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, &doTask, nullptr); /* The thread tid shall meet deadlock. So, you will never get output "Second time acquiring the resource". */ ret = pthread_join(tid, nullptr); ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo17b-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version B: Solving the problem from version A */ #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; void* doTask(void*) { pthread_mutex_lock(&mut); cout << "First time acquiring the resource" << endl; pthread_mutex_lock(&mut); cout << "Second time acquiring the resource" << endl; pthread_mutex_unlock(&mut); pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; pthread_mutexattr_t attr; int ret = 0; ret = pthread_mutexattr_init(&attr); ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); ret = pthread_mutex_init(&mut, &attr); ret = pthread_create(&tid, nullptr, &doTask, nullptr); ret = pthread_join(tid, nullptr); ret = pthread_mutexattr_destroy(&attr); ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo17c-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version C: A multithreaded app example */ #include #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; void* doTask(void* arg) { char name = *(char*) arg; sleep(1); pthread_mutex_lock(&mut); cout << "First time " << name << " acquiring the resource" << endl; pthread_mutex_lock(&mut); cout << "Second time " << name << " acquiring the resource" << endl; pthread_mutex_unlock(&mut); pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 3; pthread_t lstTid[NUM_THREADS]; char lstArg[NUM_THREADS]; pthread_mutexattr_t attr; int ret = 0; ret = pthread_mutexattr_init(&attr); ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); ret = pthread_mutex_init(&mut, &attr); for (int i = 0; i < NUM_THREADS; ++i) { lstArg[i] = char(i + 'A'); ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } pthread_mutexattr_destroy(&attr); pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo18a01-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include using namespace std; pthread_barrier_t syncPoint; void* processRequest(void* argVoid) { auto arg = *(tuple*) argVoid; string userName = std::get<0>(arg); int waitTime = std::get<1>(arg); sleep(waitTime); cout << "Get request from " << userName << endl; pthread_barrier_wait(&syncPoint); cout << "Process request for " << userName << endl; pthread_barrier_wait(&syncPoint); cout << "Done " << userName << endl; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 3; pthread_t lstTid[NUM_THREADS]; int ret = 0; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 2 }, { "dolor", 3 }, }; ret = pthread_barrier_init(&syncPoint, nullptr, 3); // participant count = 3 for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &processRequest, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } ret = pthread_barrier_destroy(&syncPoint); return 0; } ================================================ FILE: cpp/cpp-pthread/demo18a02-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include using namespace std; pthread_barrier_t syncPoint; void* processRequest(void* argVoid) { auto arg = *(tuple*) argVoid; string userName = std::get<0>(arg); int waitTime = std::get<1>(arg); sleep(waitTime); cout << "Get request from " << userName << endl; pthread_barrier_wait(&syncPoint); cout << "Process request for " << userName << endl; pthread_barrier_wait(&syncPoint); cout << "Done " << userName << endl; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 4; pthread_t lstTid[NUM_THREADS]; int ret = 0; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 3 }, { "dolor", 3 }, { "amet", 10 } }; ret = pthread_barrier_init(&syncPoint, nullptr, 2); // participant count = 2 for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &processRequest, &lstArg[i]); } // Thread with userName = "amet" shall be FREEZED for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } ret = pthread_barrier_destroy(&syncPoint); return 0; } ================================================ FILE: cpp/cpp-pthread/demo18a03-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include using namespace std; pthread_barrier_t syncPointA; pthread_barrier_t syncPointB; void* processRequest(void* argVoid) { auto arg = *(tuple*) argVoid; string userName = std::get<0>(arg); int waitTime = std::get<1>(arg); sleep(waitTime); cout << "Get request from " << userName << endl; pthread_barrier_wait(&syncPointA); cout << "Process request for " << userName << endl; pthread_barrier_wait(&syncPointB); cout << "Done " << userName << endl; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 4; pthread_t lstTid[NUM_THREADS]; int ret = 0; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 3 }, { "dolor", 3 }, { "amet", 10 } }; ret = pthread_barrier_init(&syncPointA, nullptr, 2); ret = pthread_barrier_init(&syncPointB, nullptr, 2); for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &processRequest, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } ret = pthread_barrier_destroy(&syncPointA); ret = pthread_barrier_destroy(&syncPointB); return 0; } ================================================ FILE: cpp/cpp-pthread/demo18b01-latch.cpp ================================================ /* BARRIERS AND LATCHES Version B: Count-down latches Count-down latches in C++ POSIX threading are not supported by default. So, I use mylib::CountDownLatch for this demonstration. */ #include #include #include #include #include #include "mylib-latch.hpp" using namespace std; mylib::CountDownLatch syncPoint(3); void* processRequest(void* argVoid) { auto arg = *(tuple*) argVoid; string userName = std::get<0>(arg); int waitTime = std::get<1>(arg); sleep(waitTime); cout << "Get request from " << userName << endl; syncPoint.countDown(); syncPoint.wait(); cout << "Done " << userName << endl; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 3; pthread_t lstTid[NUM_THREADS]; int ret = 0; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 2 }, { "dolor", 3 } }; for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &processRequest, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } return 0; } ================================================ FILE: cpp/cpp-pthread/demo18b02-latch.cpp ================================================ /* BARRIERS AND LATCHES Version B: Count-down latches Main thread waits for 3 child threads to get enough data to progress. Count-down latches in C++ POSIX threading are not supported by default. So, I use mylib::CountDownLatch for this demonstration. */ #include #include #include #include #include #include "mylib-latch.hpp" using namespace std; constexpr int NUM_THREADS = 3; mylib::CountDownLatch syncPoint(NUM_THREADS); void* doTask(void* argVoid) { auto arg = *(tuple*) argVoid; string message = std::get<0>(arg); int waitTime = std::get<1>(arg); sleep(waitTime); cout << message << endl; syncPoint.countDown(); sleep(8); cout << "Cleanup" << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t lstTid[NUM_THREADS]; int ret = 0; // tuple tuple lstArg[NUM_THREADS] = { { "Send request to egg.net to get data", 6 }, { "Send request to foo.org to get data", 2 }, { "Send request to bar.com to get data", 4 } }; for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } syncPoint.wait(); cout << "\nNow we have enough data to progress to next step\n" << endl; for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } return 0; } ================================================ FILE: cpp/cpp-pthread/demo19-read-write-lock.cpp ================================================ /* READ-WRITE LOCKS Lock for reading A thread can hold multiple concurrent read locks on the rwlock object (that is, successfully call the pthread_rwlock_rdlock subroutine n times). If so, the thread must perform matching unlocks (that is, it must call the pthread_rwlock_unlock subroutine n times). There is a function that supports non-blocking: pthread_rwlock_tryrdlock Lock for writing The pthread_rwlock_wrlock subroutine applies a write lock to the read/write lock referenced by the rwlock object. The calling thread acquires the write lock if no other thread (reader or writer) holds the read/write lock on the rwlock object. Otherwise, the thread does not return from the pthread_rwlock_wrlock call until it can acquire the lock. There is a function that supports non-blocking: pthread_rwlock_trywrlock */ #include #include #include #include "../cpp-std/mylib-random.hpp" using namespace std; volatile int resource = 0; pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; void* readFunc(void* arg) { auto waitTime = *(int*) arg; sleep(waitTime); pthread_rwlock_rdlock(&rwlock); cout << "read: " << resource << endl; pthread_rwlock_unlock(&rwlock); pthread_exit(nullptr); return nullptr; } void* writeFunc(void* arg) { auto waitTime = *(int*) arg; sleep(waitTime); pthread_rwlock_wrlock(&rwlock); resource = mylib::RandInt::get(100); cout << "write: " << resource << endl; pthread_rwlock_unlock(&rwlock); pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS_READ = 10; constexpr int NUM_THREADS_WRITE = 4; constexpr int NUM_ARGS = 3; pthread_t lstTidRead[NUM_THREADS_READ]; pthread_t lstTidWrite[NUM_THREADS_WRITE]; int lstArg[NUM_ARGS]; int ret = 0; // INITIALIZE for (int i = 0; i < NUM_ARGS; ++i) { lstArg[i] = i; } // CREATE THREADS for (auto&& tid : lstTidRead) { int argIndex = mylib::RandInt::get(NUM_ARGS); ret = pthread_create(&tid, nullptr, &readFunc, &lstArg[argIndex]); } for (auto&& tid : lstTidWrite) { int argIndex = mylib::RandInt::get(NUM_ARGS); ret = pthread_create(&tid, nullptr, &writeFunc, &lstArg[argIndex]); } // JOIN THREADS for (auto&& tid : lstTidRead) { ret = pthread_join(tid, nullptr); } for (auto&& tid : lstTidWrite) { ret = pthread_join(tid, nullptr); } pthread_rwlock_destroy(&rwlock); return 0; } ================================================ FILE: cpp/cpp-pthread/demo20a01-semaphore.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages */ #include #include #include #include using namespace std; sem_t semPackage; void* makeOneSheet(void*) { for (int i = 0; i < 4; ++i) { cout << "Make 1 sheet" << endl; sleep(1); sem_post(&semPackage); } pthread_exit(nullptr); return nullptr; } void* combineOnePackage(void*) { for (int i = 0; i < 4; ++i) { sem_wait(&semPackage); sem_wait(&semPackage); cout << "Combine 2 sheets into 1 package" << endl; } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidMakeSheetA, tidMakeSheetB, tidCombinePackage; int ret = 0; ret = sem_init(&semPackage, 0, 0); ret = pthread_create(&tidMakeSheetA, nullptr, &makeOneSheet, nullptr); ret = pthread_create(&tidMakeSheetB, nullptr, &makeOneSheet, nullptr); ret = pthread_create(&tidCombinePackage, nullptr, &combineOnePackage, nullptr); ret = pthread_join(tidMakeSheetA, nullptr); ret = pthread_join(tidMakeSheetB, nullptr); ret = pthread_join(tidCombinePackage, nullptr); ret = sem_destroy(&semPackage); return 0; } ================================================ FILE: cpp/cpp-pthread/demo20a02-semaphore.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages */ #include #include #include #include using namespace std; sem_t semPackage; sem_t semSheet; void* makeOneSheet(void*) { for (int i = 0; i < 4; ++i) { sem_wait(&semSheet); cout << "Make 1 sheet" << endl; sem_post(&semPackage); } pthread_exit(nullptr); return nullptr; } void* combineOnePackage(void*) { for (int i = 0; i < 4; ++i) { sem_wait(&semPackage); sem_wait(&semPackage); cout << "Combine 2 sheets into 1 package" << endl; sleep(2); sem_post(&semSheet); sem_post(&semSheet); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidMakeSheetA, tidMakeSheetB, tidCombinePackage; int ret = 0; ret = sem_init(&semPackage, 0, 0); ret = sem_init(&semSheet, 0, 2); ret = pthread_create(&tidMakeSheetA, nullptr, &makeOneSheet, nullptr); ret = pthread_create(&tidMakeSheetB, nullptr, &makeOneSheet, nullptr); ret = pthread_create(&tidCombinePackage, nullptr, &combineOnePackage, nullptr); ret = pthread_join(tidMakeSheetA, nullptr); ret = pthread_join(tidMakeSheetB, nullptr); ret = pthread_join(tidCombinePackage, nullptr); ret = sem_destroy(&semPackage); ret = sem_destroy(&semSheet); return 0; } ================================================ FILE: cpp/cpp-pthread/demo20a03-semaphore-deadlock.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages */ #include #include #include #include using namespace std; sem_t semPackage; sem_t semSheet; void* makeOneSheet(void*) { for (int i = 0; i < 4; ++i) { sem_wait(&semSheet); cout << "Make 1 sheet" << endl; sem_post(&semPackage); } pthread_exit(nullptr); return nullptr; } void* combineOnePackage(void*) { for (int i = 0; i < 4; ++i) { sem_wait(&semPackage); sem_wait(&semPackage); cout << "Combine 2 sheets into 1 package" << endl; sleep(2); sem_post(&semSheet); // Missing one statement: sem_post(&semSheet) ==> deadlock } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidMakeSheetA, tidMakeSheetB, tidCombinePackage; int ret = 0; ret = sem_init(&semPackage, 0, 0); ret = sem_init(&semSheet, 0, 2); ret = pthread_create(&tidMakeSheetA, nullptr, &makeOneSheet, nullptr); ret = pthread_create(&tidMakeSheetB, nullptr, &makeOneSheet, nullptr); ret = pthread_create(&tidCombinePackage, nullptr, &combineOnePackage, nullptr); ret = pthread_join(tidMakeSheetA, nullptr); ret = pthread_join(tidMakeSheetB, nullptr); ret = pthread_join(tidCombinePackage, nullptr); ret = sem_destroy(&semPackage); ret = sem_destroy(&semSheet); return 0; } ================================================ FILE: cpp/cpp-pthread/demo20b-semaphore.cpp ================================================ /* SEMAPHORES Version B: Tires and chassis */ #include #include #include #include using namespace std; sem_t semTire; sem_t semChassis; void* makeTire(void*) { int ret = 0; for (int i = 0; i < 8; ++i) { sem_wait(&semTire); cout << "Make 1 tire" << endl; sleep(1); sem_post(&semChassis); } pthread_exit(nullptr); return nullptr; } void* makeChassis(void*) { for (int i = 0; i < 4; ++i) { sem_wait(&semChassis); sem_wait(&semChassis); sem_wait(&semChassis); sem_wait(&semChassis); cout << "Make 1 chassis" << endl; sleep(3); sem_post(&semTire); sem_post(&semTire); sem_post(&semTire); sem_post(&semTire); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidTireA, tidTireB, tidChassis; int ret = 0; ret = sem_init(&semTire, 0, 4); ret = sem_init(&semChassis, 0, 0); ret = pthread_create(&tidTireA, nullptr, &makeTire, nullptr); ret = pthread_create(&tidTireB, nullptr, &makeTire, nullptr); ret = pthread_create(&tidChassis, nullptr, &makeChassis, nullptr); ret = pthread_join(tidTireA, nullptr); ret = pthread_join(tidTireB, nullptr); ret = pthread_join(tidChassis, nullptr); ret = sem_destroy(&semTire); ret = sem_destroy(&semChassis); return 0; } ================================================ FILE: cpp/cpp-pthread/demo21a01-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t conditionVar = PTHREAD_COND_INITIALIZER; void* foo(void*) { cout << "foo is waiting..." << endl; pthread_mutex_lock(&mut); pthread_cond_wait(&conditionVar, &mut); cout << "foo resumed" << endl; pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } void* bar(void*) { sleep(3); pthread_cond_signal(&conditionVar); pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo, tidBar; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); ret = pthread_create(&tidBar, nullptr, &bar, nullptr); ret = pthread_join(tidFoo, nullptr); ret = pthread_join(tidBar, nullptr); ret = pthread_cond_destroy(&conditionVar); ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo21a02-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t conditionVar = PTHREAD_COND_INITIALIZER; constexpr int NUM_TH_FOO = 3; void* foo(void*) { cout << "foo is waiting..." << endl; pthread_mutex_lock(&mut); pthread_cond_wait(&conditionVar, &mut); cout << "foo resumed" << endl; pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } void* bar(void*) { for (int i = 0; i < NUM_TH_FOO; ++i) { sleep(2); pthread_cond_signal(&conditionVar); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t lstTidFoo[NUM_TH_FOO]; pthread_t tidBar; int ret = 0; for (auto&& tidFoo : lstTidFoo) { ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); } ret = pthread_create(&tidBar, nullptr, &bar, nullptr); for (auto&& tidFoo : lstTidFoo) { ret = pthread_join(tidFoo, nullptr); } ret = pthread_join(tidBar, nullptr); ret = pthread_cond_destroy(&conditionVar); ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo21a03-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t conditionVar = PTHREAD_COND_INITIALIZER; constexpr int NUM_TH_FOO = 3; void* foo(void*) { cout << "foo is waiting..." << endl; pthread_mutex_lock(&mut); pthread_cond_wait(&conditionVar, &mut); cout << "foo resumed" << endl; pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } void* bar(void*) { sleep(3); // Notify all waiting threads pthread_cond_broadcast(&conditionVar); pthread_exit(nullptr); return nullptr; } int main() { pthread_t lstTidFoo[NUM_TH_FOO]; pthread_t tidBar; int ret = 0; for (auto&& tidFoo : lstTidFoo) { ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); } ret = pthread_create(&tidBar, nullptr, &bar, nullptr); for (auto&& tidFoo : lstTidFoo) { ret = pthread_join(tidFoo, nullptr); } ret = pthread_join(tidBar, nullptr); ret = pthread_cond_destroy(&conditionVar); ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo21b-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t conditionVar = PTHREAD_COND_INITIALIZER; int counter = 0; constexpr int COUNT_HALT_01 = 3; constexpr int COUNT_HALT_02 = 6; constexpr int COUNT_DONE = 10; // Write numbers 1-3 and 8-10 as permitted by egg() void* foo(void*) { for (;;) { // Lock mutex and then wait for signal to relase mutex pthread_mutex_lock(&mut); // Wait while egg() operates on counter, // Mutex unlocked if condition variable in egg() signaled pthread_cond_wait(&conditionVar, &mut); ++counter; cout << "foo count = " << counter << endl; if (counter >= COUNT_DONE) { pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } pthread_mutex_unlock(&mut); } pthread_exit(nullptr); return nullptr; } // Write numbers 4-7 void* egg(void*) { for (;;) { pthread_mutex_lock(&mut); if (counter < COUNT_HALT_01 || counter > COUNT_HALT_02) { // Signal to free waiting thread by freeing the mutex // Note: foo() is now permitted to modify "counter" pthread_cond_signal(&conditionVar); } else { ++counter; cout << "egg counter = " << counter << endl; } if (counter >= COUNT_DONE) { pthread_mutex_unlock(&mut); pthread_exit(nullptr); return nullptr; } pthread_mutex_unlock(&mut); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidFoo, tidEgg; int ret = 0; ret = pthread_create(&tidFoo, nullptr, &foo, nullptr); ret = pthread_create(&tidEgg, nullptr, &egg, nullptr); ret = pthread_join(tidFoo, nullptr); ret = pthread_join(tidEgg, nullptr); ret = pthread_cond_destroy(&conditionVar); ret = pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: cpp/cpp-pthread/demo22a-blocking-queue.cpp ================================================ /* BLOCKING QUEUES Version A: A slow producer and a fast consumer Blocking queues in C++ POSIX threading are not supported by default. So, I use mylib::BlockingQueue for this demonstration. */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void* producer(void* arg) { auto blkQueue = (BlockingQueue*) arg; sleep(2); blkQueue->put("Alice"); sleep(2); blkQueue->put("likes"); sleep(2); blkQueue->put("singing"); pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkQueue = (BlockingQueue*) arg; string data; for (int i = 0; i < 3; ++i) { cout << "\nWaiting for data..." << endl; data = blkQueue->take(); cout << " " << data << endl; } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidProducer, tidConsumer; auto blkQueue = BlockingQueue(); int ret = 0; ret = pthread_create(&tidProducer, nullptr, &producer, &blkQueue); ret = pthread_create(&tidConsumer, nullptr, &consumer, &blkQueue); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo22b-blocking-queue.cpp ================================================ /* BLOCKING QUEUES Version B: A fast producer and a slow consumer Blocking queues in C++ POSIX threading are not supported by default. So, I use mylib::BlockingQueue for this demonstration. */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void* producer(void* arg) { auto blkQueue = (BlockingQueue*) arg; blkQueue->put("Alice"); blkQueue->put("likes"); /* Due to reaching the maximum capacity = 2, when executing blkQueue->put("singing"), this thread is going to sleep until the queue removes an element. */ blkQueue->put("singing"); pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkQueue = (BlockingQueue*) arg; string data; sleep(2); for (int i = 0; i < 3; ++i) { cout << "\nWaiting for data..." << endl; data = blkQueue->take(); cout << " " << data << endl; } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidProducer, tidConsumer; auto blkQueue = BlockingQueue(); // blocking queue with capacity = 2 int ret = 0; ret = pthread_create(&tidProducer, nullptr, &producer, &blkQueue); ret = pthread_create(&tidConsumer, nullptr, &consumer, &blkQueue); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo23a-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Introduction The code is specific for gcc. */ #include #include using namespace std; __thread int value = 123; void* doTask(void* arg) { cout << value << endl; pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; // Main thread sets value = 999 value = 999; cout << value << endl; // Child thread gets value // Expected output: 123 ret = pthread_create(&tid, nullptr, &doTask, nullptr); ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo23b-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Avoiding synchronization using thread-local storage The code is specific for gcc. */ #include #include #include #include using namespace std; __thread int counter = 0; void* doTask(void* arg) { auto t = *(int*) arg; sleep(1); for (int i = 0; i < 1000; ++i) ++counter; cout << "Thread " << t << " gives counter = " << counter << endl; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 3; pthread_t lstTid[NUM_THREADS]; int lstArg[NUM_THREADS]; int ret = 0; for (int i = 0; i < NUM_THREADS; ++i) { lstArg[i] = i; ret = pthread_create(&lstTid[i], nullptr, &doTask, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/demo24-volatile.cpp ================================================ /* THE VOLATILE KEYWORD */ #include #include #include using namespace std; volatile bool isRunning; void* doTask(void*) { while (isRunning) { cout << "Running..." << endl; sleep(2); } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; int ret = 0; isRunning = true; ret = pthread_create(&tid, nullptr, &doTask, nullptr); sleep(6); isRunning = false; ret = pthread_join(tid, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/demo25a-atomic.c ================================================ /* ATOMIC ACCESS In this demo, I use raw C language (not C++). */ #include #include #include volatile int counter; void* doTask(void* arg) { sleep(1); counter += 1; pthread_exit(NULL); return NULL; } int main() { counter = 0; pthread_t lstTid[1000]; int ret = 0; for (int i = 0; i < 1000; ++i) { ret = pthread_create(&lstTid[i], NULL, &doTask, NULL); } for (int i = 0; i < 1000; ++i) { ret = pthread_join(lstTid[i], NULL); } // Unpredictable result printf("counter = %d \n", counter); return 0; } ================================================ FILE: cpp/cpp-pthread/demo25b-atomic.c ================================================ /* ATOMIC ACCESS In this demo, I use raw C language (not C++). */ #include #include #include // C11 atomic #include atomic_int counter; void* doTask(void* arg) { sleep(1); atomic_fetch_add(&counter, 1); pthread_exit(NULL); return NULL; } int main() { atomic_store(&counter, 0); // Assign counter = 0 pthread_t lstTid[1000]; int ret = 0; for (int i = 0; i < 1000; ++i) { ret = pthread_create(&lstTid[i], NULL, &doTask, NULL); } for (int i = 0; i < 1000; ++i) { ret = pthread_join(lstTid[i], NULL); } // counter = 1000 printf("counter = %d \n", counter); return 0; } ================================================ FILE: cpp/cpp-pthread/demoex-attribute.cpp ================================================ #include #include #include using namespace std; void* doTask(void* ptrId) { int id = *(int*)ptrId; cout << "Sleeping in thread " << id << endl; sleep(1); cout << "Thread with id " << id << " exiting..." << endl; pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_THREADS = 3; pthread_t tid[NUM_THREADS]; pthread_attr_t attr; int arg[NUM_THREADS]; int ret = 0; // Initialize and set thread joinable pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); // PTHREAD_CREATE_JOINABLE or PTHREAD_CREATE_DETACHED for (int i = 0; i < NUM_THREADS; ++i) { arg[i] = i; ret = pthread_create(&tid[i], &attr, &doTask, &arg[i]); } void* status = nullptr; for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_join(tid[i], &status); cout << "completed thread id " << i << " with status " << status << endl; } ret = pthread_attr_destroy(&attr); return 0; } ================================================ FILE: cpp/cpp-pthread/demoex-oop.cpp ================================================ #include #include using namespace std; class Task { private: pthread_t tid; public: int index; public: Task(const Task& other) = delete; Task(const Task&& other) = delete; void operator=(const Task& other) = delete; void operator=(const Task&& other) = delete; Task(int index = -1): index(index) { } int start() { int ret = pthread_create(&tid, nullptr, &work, (void*)this); return ret; } int join() { int ret = pthread_join(tid, nullptr); return ret; } private: static void* work(void* arg) { auto thisPtr = (Task*) arg; cout << thisPtr->index << endl; pthread_exit(nullptr); return nullptr; } }; int main() { constexpr int NUM_TASKS = 3; Task task[NUM_TASKS]; for (int i = 0; i < NUM_TASKS; ++i) { task[i].index = i; } for (int i = 0; i < NUM_TASKS; ++i) { task[i].start(); } for (int i = 0; i < NUM_TASKS; ++i) { task[i].join(); } return 0; } ================================================ FILE: cpp/cpp-pthread/demoex-signal.cpp ================================================ #include #include #include #include using namespace std; void signalHandler(int sig) { cout << "caught signal " << sig << endl; // signal(SIGSEGV, signalHandler); } void* func(void* arg) { cout << "foo" << endl; sleep(5); pthread_exit(nullptr); return nullptr; } int main() { pthread_t tid; signal(SIGSEGV, signalHandler); // Register signal handler before going multithread pthread_create(&tid, nullptr, &func, nullptr); sleep(1); // Leave time for initialization pthread_kill(tid, SIGSEGV); pthread_join(tid, NULL); cout << "bar" << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/exer01a-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include "../cpp-std/mylib-time.hpp" using namespace std; int main() { constexpr int RANGE_START = 1; constexpr int RANGE_END = 100000; int resValue = 0; int resNumDiv = 0; // number of divisors of result auto tpStart = mylib::HiResClock::now(); for (int i = RANGE_START; i <= RANGE_END; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } auto timeElapsed = mylib::HiResClock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << resValue << endl; cout << "The largest number of divisor is " << resNumDiv << endl; cout << "Time elapsed = " << timeElapsed.count() << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/exer01b-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include #include #include #include "../cpp-std/mylib-time.hpp" using namespace std; struct WorkerResult { int value; int numDiv; WorkerResult(int value = 0, int numDiv = 0): value(value), numDiv(numDiv) { } }; struct WorkerArg { int iStart; int iEnd; WorkerResult *res; WorkerArg(int iStart = 0, int iEnd = 0, WorkerResult* res = nullptr): iStart(iStart), iEnd(iEnd), res(res) { } }; void* workerFunc(void* argVoid) { auto arg = (WorkerArg*) argVoid; auto res = arg->res; int resValue = 0; int resNumDiv = 0; for (int i = arg->iStart; i <= arg->iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } (*res) = WorkerResult(resValue, resNumDiv); pthread_exit(nullptr); return nullptr; } void prepare( int rangeStart, int rangeEnd, int numThreads, vector& lstTid, vector& lstWorkerArg, vector& lstWorkerRes ) { lstTid.resize(numThreads); lstWorkerArg.resize(numThreads); lstWorkerRes.resize(numThreads); int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg[i] = WorkerArg(rangeA, rangeB, &lstWorkerRes[i]); } } int main() { constexpr int RANGE_START = 1; constexpr int RANGE_END = 100000; constexpr int NUM_THREADS = 8; vector lstTid; vector lstWorkerArg; vector lstWorkerRes; int ret = 0; prepare(RANGE_START, RANGE_END, NUM_THREADS, lstTid, lstWorkerArg, lstWorkerRes); auto tpStart = mylib::HiResClock::now(); for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &workerFunc, &lstWorkerArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } // for (auto&& res: lstWorkerRes) { // cout << res.value << " " << res.numDiv << endl; // } auto finalRes = *max_element(lstWorkerRes.begin(), lstWorkerRes.end(), [](const WorkerResult &lhs, const WorkerResult &rhs) -> bool { return lhs.numDiv < rhs.numDiv; } ); auto timeElapsed = mylib::HiResClock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << finalRes.value << endl; cout << "The largest number of divisor is " << finalRes.numDiv << endl; cout << "Time elapsed = " << timeElapsed.count() << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/exer01c-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include #include #include #include "../cpp-std/mylib-time.hpp" using namespace std; class FinalResult { public: int value = 0; int numDiv = 0; private: pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; public: void update(int value, int numDiv) { pthread_mutex_lock(&mut); if (this->numDiv < numDiv) { this->numDiv = numDiv; this->value = value; } pthread_mutex_unlock(&mut); } void init() { destroy(); mut = PTHREAD_MUTEX_INITIALIZER; } void destroy() { pthread_mutex_destroy(&mut); } }; struct WorkerArg { int iStart; int iEnd; FinalResult *res; WorkerArg(int iStart = 0, int iEnd = 0, FinalResult* res = nullptr): iStart(iStart), iEnd(iEnd), res(res) { } }; void* workerFunc(void* argVoid) { auto arg = (WorkerArg*) argVoid; auto res = arg->res; int resValue = 0; int resNumDiv = 0; for (int i = arg->iStart; i <= arg->iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } res->update(resValue, resNumDiv); pthread_exit(nullptr); return nullptr; } void prepare( int rangeStart, int rangeEnd, int numThreads, vector& lstTid, vector& lstWorkerArg, FinalResult* finalRes ) { lstTid.resize(numThreads); lstWorkerArg.resize(numThreads); int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg[i] = WorkerArg(rangeA, rangeB, finalRes); } } int main() { constexpr int RANGE_START = 1; constexpr int RANGE_END = 100000; constexpr int NUM_THREADS = 8; vector lstTid; vector lstWorkerArg; FinalResult finalRes; int ret = 0; finalRes.init(); prepare(RANGE_START, RANGE_END, NUM_THREADS, lstTid, lstWorkerArg, &finalRes); auto tpStart = mylib::HiResClock::now(); for (int i = 0; i < NUM_THREADS; ++i) { ret = pthread_create(&lstTid[i], nullptr, &workerFunc, &lstWorkerArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } finalRes.destroy(); auto timeElapsed = mylib::HiResClock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << finalRes.value << endl; cout << "The largest number of divisor is " << finalRes.numDiv << endl; cout << "Time elapsed = " << timeElapsed.count() << endl; return 0; } ================================================ FILE: cpp/cpp-pthread/exer02a01-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A01: 1 slow producer, 1 fast consumer */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void* producer(void* arg) { auto blkq = (BlockingQueue*) arg; int i = 1; for (;; ++i) { blkq->put(i); sleep(1); } pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkq = (BlockingQueue*) arg; int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidProducer, tidConsumer; BlockingQueue blkq; int ret = 0; ret = pthread_create(&tidProducer, nullptr, &producer, &blkq); ret = pthread_create(&tidConsumer, nullptr, &consumer, &blkq); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02a02-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A02: 2 slow producers, 1 fast consumer */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void* producer(void* arg) { auto blkq = (BlockingQueue*) arg; int i = 1; for (;; ++i) { blkq->put(i); sleep(1); } pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkq = (BlockingQueue*) arg; int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidProducerA, tidProducerB; pthread_t tidConsumer; BlockingQueue blkq; int ret = 0; ret = pthread_create(&tidProducerA, nullptr, &producer, &blkq); ret = pthread_create(&tidProducerB, nullptr, &producer, &blkq); ret = pthread_create(&tidConsumer, nullptr, &consumer, &blkq); ret = pthread_join(tidProducerA, nullptr); ret = pthread_join(tidProducerB, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02a03-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A03: 1 slow producer, 2 fast consumers */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; struct ConsumerArg { string name; BlockingQueue *blkq; }; void* producer(void* arg) { auto blkq = (BlockingQueue*) arg; int i = 1; for (;; ++i) { blkq->put(i); sleep(1); } pthread_exit(nullptr); return nullptr; } void* consumer(void* argVoid) { auto arg = (ConsumerArg*) argVoid; auto blkq = arg->blkq; int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << arg->name << ": " << data << endl; } pthread_exit(nullptr); return nullptr; } int main() { pthread_t tidProducer; pthread_t tidConsumerFoo, tidConsumerBar; BlockingQueue blkq; int ret = 0; ConsumerArg argFoo = { "foo", &blkq }; ConsumerArg argBar = { "bar", &blkq }; ret = pthread_create(&tidProducer, nullptr, &producer, &blkq); ret = pthread_create(&tidConsumerFoo, nullptr, &consumer, &argFoo); ret = pthread_create(&tidConsumerBar, nullptr, &consumer, &argBar); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumerFoo, nullptr); ret = pthread_join(tidConsumerBar, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02a04-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A04: Multiple fast producers, multiple slow consumers */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; struct ProducerArg { BlockingQueue* blkq; int startValue; }; void* producer(void* argVoid) { auto arg = (ProducerArg*) argVoid; int i = 1; for (;; ++i) { arg->blkq->put(i + arg->startValue); } pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkq = (BlockingQueue*) arg; int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } pthread_exit(nullptr); return nullptr; } int main() { auto blkq = BlockingQueue(5); constexpr int NUM_PRODUCERS = 3; constexpr int NUM_CONSUMERS = 2; pthread_t lstTidProducer[NUM_PRODUCERS]; pthread_t lstTidConsumer[NUM_CONSUMERS]; ProducerArg lstArgPro[NUM_PRODUCERS]; int ret = 0; // PREPARE ARGUMENTS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstArgPro[i] = { &blkq, i * 1000 }; } // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { ret = pthread_create(&lstTidProducer[i], nullptr, &producer, &lstArgPro[i]); } for (int i = 0; i < NUM_CONSUMERS; ++i) { ret = pthread_create(&lstTidConsumer[i], nullptr, &consumer, &blkq); } // JOIN THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { ret = pthread_join(lstTidProducer[i], nullptr); } for (int i = 0; i < NUM_CONSUMERS; ++i) { ret = pthread_join(lstTidConsumer[i], nullptr); } return 0; } ================================================ FILE: cpp/cpp-pthread/exer02b01-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B01: 1 slow producer, 1 fast consumer */ #include #include #include #include #include using namespace std; struct GlobalSemaphore { sem_t semFill; // item produced sem_t semEmpty; // remaining space in queue void init(int semFillValue, int semEmptyValue) { sem_init(&semFill, 0, semFillValue); sem_init(&semEmpty, 0, semEmptyValue); } void destroy() { sem_destroy(&semFill); sem_destroy(&semEmpty); } void waitFill() { sem_wait(&semFill); } void waitEmpty() { sem_wait(&semEmpty); } void postFill() { sem_post(&semFill); } void postEmpty() { sem_post(&semEmpty); } }; struct GlobalArg { queue* q; GlobalSemaphore* sem; }; void* producer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int i = 1; for (;; ++i) { sem->waitEmpty(); q->push(i); sleep(1); sem->postFill(); } pthread_exit(nullptr); return nullptr; } void* consumer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int data = 0; for (;;) { sem->waitFill(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; sem->postEmpty(); } pthread_exit(nullptr); return nullptr; } int main() { GlobalSemaphore sem; queue q; pthread_t tidProducer, tidConsumer; GlobalArg arg = { &q, &sem }; int ret = 0; sem.init(0, 1); ret = pthread_create(&tidProducer, nullptr, &producer, &arg); ret = pthread_create(&tidConsumer, nullptr, &consumer, &arg); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); sem.destroy(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02b02-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B02: 2 slow producers, 1 fast consumer */ #include #include #include #include #include using namespace std; struct GlobalSemaphore { sem_t semFill; // item produced sem_t semEmpty; // remaining space in queue void init(int semFillValue, int semEmptyValue) { sem_init(&semFill, 0, semFillValue); sem_init(&semEmpty, 0, semEmptyValue); } void destroy() { sem_destroy(&semFill); sem_destroy(&semEmpty); } void waitFill() { sem_wait(&semFill); } void waitEmpty() { sem_wait(&semEmpty); } void postFill() { sem_post(&semFill); } void postEmpty() { sem_post(&semEmpty); } }; struct GlobalArg { queue* q; GlobalSemaphore* sem; int startValue; }; void* producer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int i = 1; for (;; ++i) { sem->waitEmpty(); q->push(i + arg->startValue); sleep(1); sem->postFill(); } pthread_exit(nullptr); return nullptr; } void* consumer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int data = 0; for (;;) { sem->waitFill(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; sem->postEmpty(); } pthread_exit(nullptr); return nullptr; } int main() { GlobalSemaphore sem; queue q; pthread_t tidProducerA, tidProducerB, tidConsumer; GlobalArg argCon, argProA, argProB; int ret = 0; sem.init(0, 1); argCon = argProA = argProB = { &q, &sem, 0 }; argProA.startValue = 0; argProB.startValue = 1000; ret = pthread_create(&tidProducerA, nullptr, &producer, &argProA); ret = pthread_create(&tidProducerB, nullptr, &producer, &argProB); ret = pthread_create(&tidConsumer, nullptr, &consumer, &argCon); ret = pthread_join(tidProducerA, nullptr); ret = pthread_join(tidProducerB, nullptr); ret = pthread_join(tidConsumer, nullptr); sem.destroy(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02b03-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B03: 2 fast producers, 1 slow consumer */ #include #include #include #include #include using namespace std; struct GlobalSemaphore { sem_t semFill; // item produced sem_t semEmpty; // remaining space in queue void init(int semFillValue, int semEmptyValue) { sem_init(&semFill, 0, semFillValue); sem_init(&semEmpty, 0, semEmptyValue); } void destroy() { sem_destroy(&semFill); sem_destroy(&semEmpty); } void waitFill() { sem_wait(&semFill); } void waitEmpty() { sem_wait(&semEmpty); } void postFill() { sem_post(&semFill); } void postEmpty() { sem_post(&semEmpty); } }; struct GlobalArg { queue* q; GlobalSemaphore* sem; int startValue; }; void* producer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int i = 1; for (;; ++i) { sem->waitEmpty(); q->push(i + arg->startValue); sem->postFill(); } pthread_exit(nullptr); return nullptr; } void* consumer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int data = 0; for (;;) { sem->waitFill(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; sleep(1); sem->postEmpty(); } pthread_exit(nullptr); return nullptr; } int main() { GlobalSemaphore sem; queue q; pthread_t tidProducerA, tidProducerB, tidConsumer; GlobalArg argCon, argProA, argProB; int ret = 0; sem.init(0, 1); argCon = argProA = argProB = { &q, &sem, 0 }; argProA.startValue = 0; argProB.startValue = 1000; ret = pthread_create(&tidProducerA, nullptr, &producer, &argProA); ret = pthread_create(&tidProducerB, nullptr, &producer, &argProB); ret = pthread_create(&tidConsumer, nullptr, &consumer, &argCon); ret = pthread_join(tidProducerA, nullptr); ret = pthread_join(tidProducerB, nullptr); ret = pthread_join(tidConsumer, nullptr); sem.destroy(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02b04-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B04: Multiple fast producers, multiple slow consumers */ #include #include #include #include #include using namespace std; struct GlobalSemaphore { sem_t semFill; // item produced sem_t semEmpty; // remaining space in queue void init(int semFillValue, int semEmptyValue) { sem_init(&semFill, 0, semFillValue); sem_init(&semEmpty, 0, semEmptyValue); } void destroy() { sem_destroy(&semFill); sem_destroy(&semEmpty); } void waitFill() { sem_wait(&semFill); } void waitEmpty() { sem_wait(&semEmpty); } void postFill() { sem_post(&semFill); } void postEmpty() { sem_post(&semEmpty); } }; struct GlobalArg { queue* q; GlobalSemaphore* sem; int startValue; }; void* producer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int i = 1; for (;; ++i) { sem->waitEmpty(); q->push(i + arg->startValue); sem->postFill(); } pthread_exit(nullptr); return nullptr; } void* consumer(void* argVoid) { auto arg = (GlobalArg*) argVoid; auto q = arg->q; auto sem = arg->sem; int data = 0; for (;;) { sem->waitFill(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; sleep(1); sem->postEmpty(); } pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_PRODUCERS = 3; constexpr int NUM_CONSUMERS = 2; GlobalSemaphore sem; queue q; pthread_t lstTidProducer[NUM_PRODUCERS]; pthread_t lstTidConsumer[NUM_CONSUMERS]; int ret = 0; sem.init(0, 1); // PREPARE ARGUMENTS GlobalArg lstArgPro[NUM_PRODUCERS]; GlobalArg argCon; for (int i = 0; i < NUM_PRODUCERS; ++i) { lstArgPro[i].q = &q; lstArgPro[i].sem = &sem; lstArgPro[i].startValue = i * 1000; } argCon.q = &q; argCon.sem = &sem; // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { ret = pthread_create(&lstTidProducer[i], nullptr, &producer, &lstArgPro[i]); } for (int i = 0; i < NUM_CONSUMERS; ++i) { ret = pthread_create(&lstTidConsumer[i], nullptr, &consumer, &argCon); } // JOIN THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { ret = pthread_join(lstTidProducer[i], nullptr); } for (int i = 0; i < NUM_CONSUMERS; ++i) { ret = pthread_join(lstTidConsumer[i], nullptr); } sem.destroy(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer02c-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE C: USING CONDITION VARIABLES & MONITORS Multiple fast producers, multiple slow consumers */ #include #include #include #include using namespace std; template class Monitor { private: std::queue* q = nullptr; int maxQueueSize = 0; pthread_cond_t condFull = PTHREAD_COND_INITIALIZER; pthread_cond_t condEmpty = PTHREAD_COND_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; public: Monitor() = default; Monitor(const Monitor &other) = delete; Monitor(const Monitor &&other) = delete; void operator=(const Monitor &other) = delete; void operator=(const Monitor &&other) = delete; void init(int maxQueueSize, std::queue* q) { destroy(); this->q = q; this->maxQueueSize = maxQueueSize; condFull = PTHREAD_COND_INITIALIZER; condEmpty = PTHREAD_COND_INITIALIZER; mut = PTHREAD_MUTEX_INITIALIZER; } void destroy() { q = nullptr; maxQueueSize = 0; pthread_cond_destroy(&condFull); pthread_cond_destroy(&condEmpty); pthread_mutex_destroy(&mut); } void add(const T& item) { pthread_mutex_lock(&mut); while (q->size() == maxQueueSize) { pthread_cond_wait(&condFull, &mut); } q->push(item); if (q->size() == 1) { pthread_cond_signal(&condEmpty); } pthread_mutex_unlock(&mut); } T remove() { pthread_mutex_lock(&mut); while (q->size() == 0) { pthread_cond_wait(&condEmpty, &mut); } T item = q->front(); q->pop(); if (q->size() == maxQueueSize - 1) { pthread_cond_signal(&condFull); } pthread_mutex_unlock(&mut); return item; } }; template struct ProducerArg { Monitor* monitor; int startValue; }; template void* producer(void* argVoid) { auto arg = (ProducerArg*) argVoid; auto monitor = arg->monitor; auto startValue = arg->startValue; int i = 1; for (;; ++i) { monitor->add(i + startValue); } pthread_exit(nullptr); return nullptr; } template void* consumer(void* argVoid) { auto monitor = (Monitor*) argVoid; T data; for (;;) { data = monitor->remove(); cout << "Consumer " << data << endl; sleep(1); } pthread_exit(nullptr); return nullptr; } int main() { Monitor monitor; queue q; constexpr int MAX_QUEUE_SIZE = 6; constexpr int NUM_PRODUCERS = 3; constexpr int NUM_CONSUMERS = 2; pthread_t lstTidProducer[NUM_PRODUCERS]; pthread_t lstTidConsumer[NUM_CONSUMERS]; ProducerArg argPro[NUM_PRODUCERS]; int ret = 0; // PREPARE ARGUMENTS monitor.init(MAX_QUEUE_SIZE, &q); for (int i = 0; i < NUM_PRODUCERS; ++i) argPro[i] = { &monitor, i * 1000 }; // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { ret = pthread_create(&lstTidProducer[i], nullptr, &producer, &argPro[i]); } for (int i = 0; i < NUM_CONSUMERS; ++i) { ret = pthread_create(&lstTidConsumer[i], nullptr, &consumer, &monitor); } // JOIN THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { ret = pthread_join(lstTidProducer[i], nullptr); } for (int i = 0; i < NUM_CONSUMERS; ++i) { ret = pthread_join(lstTidConsumer[i], nullptr); } monitor.destroy(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer03a-readers-writers.cpp ================================================ /* THE READERS-WRITERS PROBLEM Solution for the first readers-writers problem */ #include #include #include #include "../cpp-std/mylib-random.hpp" using namespace std; struct GlobalData { volatile int resource = 0; int readerCount = 0; pthread_mutex_t mutResource = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutReaderCount = PTHREAD_MUTEX_INITIALIZER; }; struct ThreadArg { GlobalData* g; int delayTime; }; void* doTaskWriter(void* argVoid) { auto arg = (ThreadArg*) argVoid; GlobalData* g = arg->g; sleep(arg->delayTime); pthread_mutex_lock(&g->mutResource); g->resource = mylib::RandInt::get(100); cout << "Write " << g->resource << endl; pthread_mutex_unlock(&g->mutResource); pthread_exit(nullptr); return nullptr; } void* doTaskReader(void* argVoid) { auto arg = (ThreadArg*) argVoid; GlobalData* g = arg->g; sleep(arg->delayTime); // Increase reader count pthread_mutex_lock(&g->mutReaderCount); g->readerCount += 1; if (1 == g->readerCount) pthread_mutex_lock(&g->mutResource); pthread_mutex_unlock(&g->mutReaderCount); // Do the reading cout << "Read " << g->resource << endl; // Decrease reader count pthread_mutex_lock(&g->mutReaderCount); g->readerCount -= 1; if (0 == g->readerCount) pthread_mutex_unlock(&g->mutResource); pthread_mutex_unlock(&g->mutReaderCount); pthread_exit(nullptr); return nullptr; } void prepareArg(ThreadArg arg[], int numArg, GlobalData* g) { for (int i = 0; i < numArg; ++i) { arg[i].g = g; arg[i].delayTime = mylib::RandInt::get(3); } } int main() { GlobalData globalData; constexpr int NUM_READERS = 8; constexpr int NUM_WRITERS = 6; pthread_t lstTidReader[NUM_READERS]; pthread_t lstTidWriter[NUM_WRITERS]; int ret = 0; // PREPARE ARGUMENTS ThreadArg argReader[NUM_READERS]; ThreadArg argWriter[NUM_WRITERS]; prepareArg(argReader, NUM_READERS, &globalData); prepareArg(argWriter, NUM_WRITERS, &globalData); // CREATE THREADS for (int i = 0; i < NUM_READERS; ++i) { ret = pthread_create(&lstTidReader[i], nullptr, &doTaskReader, &argReader[i]); } for (int i = 0; i < NUM_WRITERS; ++i) { ret = pthread_create(&lstTidWriter[i], nullptr, &doTaskWriter, &argWriter[i]); } // JOIN THREADS for (int i = 0; i < NUM_READERS; ++i) { ret = pthread_join(lstTidReader[i], nullptr); } for (int i = 0; i < NUM_WRITERS; ++i) { ret = pthread_join(lstTidWriter[i], nullptr); } // CLEAN UP ret = pthread_mutex_destroy(&globalData.mutResource); ret = pthread_mutex_destroy(&globalData.mutReaderCount); return 0; } ================================================ FILE: cpp/cpp-pthread/exer03b-readers-writers.cpp ================================================ /* THE READERS-WRITERS PROBLEM Solution for the third readers-writers problem */ #include #include #include #include "../cpp-std/mylib-random.hpp" using namespace std; struct GlobalData { volatile int resource = 0; int readerCount = 0; pthread_mutex_t mutResource = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutReaderCount = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutServiceQueue = PTHREAD_MUTEX_INITIALIZER; }; struct ThreadArg { GlobalData* g; int delayTime; }; void* doTaskWriter(void* argVoid) { auto arg = (ThreadArg*) argVoid; GlobalData* g = arg->g; sleep(arg->delayTime); pthread_mutex_lock(&g->mutServiceQueue); pthread_mutex_lock(&g->mutResource); pthread_mutex_unlock(&g->mutServiceQueue); g->resource = mylib::RandInt::get(100); cout << "Write " << g->resource << endl; pthread_mutex_unlock(&g->mutResource); pthread_exit(nullptr); return nullptr; } void* doTaskReader(void* argVoid) { auto arg = (ThreadArg*) argVoid; GlobalData* g = arg->g; sleep(arg->delayTime); // Increase reader count pthread_mutex_lock(&g->mutServiceQueue); pthread_mutex_lock(&g->mutReaderCount); g->readerCount += 1; if (1 == g->readerCount) pthread_mutex_lock(&g->mutResource); pthread_mutex_unlock(&g->mutReaderCount); pthread_mutex_unlock(&g->mutServiceQueue); // Do the reading cout << "Read " << g->resource << endl; // Decrease reader count pthread_mutex_lock(&g->mutReaderCount); g->readerCount -= 1; if (0 == g->readerCount) pthread_mutex_unlock(&g->mutResource); pthread_mutex_unlock(&g->mutReaderCount); pthread_exit(nullptr); return nullptr; } void prepareArg(ThreadArg arg[], int numArg, GlobalData* g) { for (int i = 0; i < numArg; ++i) { arg[i].g = g; arg[i].delayTime = mylib::RandInt::get(3); } } int main() { GlobalData globalData; constexpr int NUM_READERS = 8; constexpr int NUM_WRITERS = 6; pthread_t lstTidReader[NUM_READERS]; pthread_t lstTidWriter[NUM_WRITERS]; int ret = 0; // PREPARE ARGUMENTS ThreadArg argReader[NUM_READERS]; ThreadArg argWriter[NUM_WRITERS]; prepareArg(argReader, NUM_READERS, &globalData); prepareArg(argWriter, NUM_WRITERS, &globalData); // CREATE THREADS for (int i = 0; i < NUM_READERS; ++i) { ret = pthread_create(&lstTidReader[i], nullptr, &doTaskReader, &argReader[i]); } for (int i = 0; i < NUM_WRITERS; ++i) { ret = pthread_create(&lstTidWriter[i], nullptr, &doTaskWriter, &argWriter[i]); } // JOIN THREADS for (int i = 0; i < NUM_READERS; ++i) { ret = pthread_join(lstTidReader[i], nullptr); } for (int i = 0; i < NUM_WRITERS; ++i) { ret = pthread_join(lstTidWriter[i], nullptr); } // CLEAN UP ret = pthread_mutex_destroy(&globalData.mutResource); ret = pthread_mutex_destroy(&globalData.mutReaderCount); ret = pthread_mutex_destroy(&globalData.mutServiceQueue); return 0; } ================================================ FILE: cpp/cpp-pthread/exer04-dining-philosophers.cpp ================================================ /* THE DINING PHILOSOPHERS PROBLEM */ #include #include #include using namespace std; struct TaskArg { pthread_mutex_t* chopstick; int numPhilo; int idPhiLo; }; void* doTaskPhilosopher(void *argVoid) { auto arg = (TaskArg*) argVoid; auto chopstick = arg->chopstick; int n = arg->numPhilo; int i = arg->idPhiLo; sleep(1); pthread_mutex_lock(&chopstick[i]); pthread_mutex_lock(&chopstick[(i + 1) % n]); cout << "Philosopher #" << i << " is eating the rice" << endl; pthread_mutex_unlock(&chopstick[(i + 1) % n]); pthread_mutex_unlock(&chopstick[i]); pthread_exit(nullptr); return nullptr; } int main() { constexpr int NUM_PHILOSOPHERS = 5; pthread_t tid[NUM_PHILOSOPHERS]; pthread_mutex_t chopstick[NUM_PHILOSOPHERS]; TaskArg arg[NUM_PHILOSOPHERS]; int ret = 0; // PREPARE ARGUMENTS for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { chopstick[i] = PTHREAD_MUTEX_INITIALIZER; arg[i].chopstick = chopstick; arg[i].numPhilo = NUM_PHILOSOPHERS; arg[i].idPhiLo = i; } // CREATE THREADS for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { ret = pthread_create(&tid[i], nullptr, &doTaskPhilosopher, &arg[i]); } // JOIN THREADS for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { ret = pthread_join(tid[i], nullptr); } // CLEAN UP for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { ret = pthread_mutex_destroy(&chopstick[i]); } return 0; } ================================================ FILE: cpp/cpp-pthread/exer05-util.hpp ================================================ #ifndef _EXER05_UTIL_HPP_ #define _EXER05_UTIL_HPP_ struct WorkerScProdArg { double const* u = nullptr; double const* v = nullptr; int sizeVector = 0; double* res = nullptr; }; void* workerScalarProduct(void* argVoid) { auto arg = (WorkerScProdArg*) argVoid; auto u = arg->u; auto v = arg->v; auto sizeVector = arg->sizeVector; auto res = arg->res; double sum = 0; for (int i = sizeVector - 1; i >= 0; --i) { sum += u[i] * v[i]; } (*res) = sum; return nullptr; } #endif // _EXER05_UTIL_HPP_ ================================================ FILE: cpp/cpp-pthread/exer05a-product-matrix-vector.cpp ================================================ /* MATRIX-VECTOR MULTIPLICATION */ #include #include #include #include "exer05-util.hpp" using namespace std; using vectord = std::vector; using matrix = std::vector; void getProduct(const matrix& mat, const vectord& vec, vectord& result) { // Assume that size of mat and vec are both eligible int sizeRowMat = mat.size(); int sizeColMat = mat[0].size(); int sizeVec = vec.size(); int ret = 0; result.clear(); result.resize(sizeRowMat, 0); vector lstTid(sizeRowMat); vector lstArg(sizeRowMat); for (int i = 0; i < sizeRowMat; ++i) { auto&& u = mat[i].data(); auto&& v = vec.data(); lstArg[i] = { u, v, sizeVec, &result[i] }; ret = pthread_create(&lstTid[i], nullptr, &workerScalarProduct, &lstArg[i]); } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } } int main() { matrix A = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; vectord b = { 3, -1, 0 }; vectord result; getProduct(A, b, result); for (int i = 0; i < result.size(); ++i) { cout << result[i] << endl; } return 0; } ================================================ FILE: cpp/cpp-pthread/exer05b-product-matrix-matrix.cpp ================================================ /* MATRIX-MATRIX MULTIPLICATION (DOT PRODUCT) */ #include #include #include #include "exer05-util.hpp" using namespace std; using vectord = std::vector; using matrix = std::vector; void getTransposeMatrix(const matrix& input, matrix& output) { int numRow = input.size(); int numCol = input[0].size(); output.clear(); output.assign(numCol, vectord(numRow, 0)); for (int i = 0; i < numRow; ++i) for (int j = 0; j < numCol; ++j) output[j][i] = input[i][j]; } void displayMatrix(const matrix& mat) { int numRow = mat.size(); int numCol = mat[0].size(); for (int i = 0; i < numRow; ++i) { for (int j = 0; j < numCol; ++j) cout << "\t" << mat[i][j]; cout << endl; } } void getProduct(const matrix& matA, const matrix& matB, matrix& result) { // Assume that size of matA and matB are both eligible int sizeRowA = matA.size(); int sizeColA = matA[0].size(); int sizeColB = matB[0].size(); int sizeTotal = sizeRowA * sizeColB; result.clear(); result.assign(sizeRowA, vectord(sizeColB, 0)); matrix matBT; getTransposeMatrix(matB, matBT); vector lstTid(sizeTotal); vector lstArg(sizeTotal); int iSca = 0; int ret = 0; for (int i = 0; i < sizeRowA; ++i) { for (int j = 0; j < sizeColB; ++j) { auto&& u = matA[i].data(); auto&& v = matBT[j].data(); auto&& sizeVector = sizeColA; lstArg[iSca] = { u, v, sizeVector, &result[i][j] }; ret = pthread_create(&lstTid[iSca], nullptr, &workerScalarProduct, &lstArg[iSca]); ++iSca; } } for (auto&& tid : lstTid) { ret = pthread_join(tid, nullptr); } } int main() { matrix A = { { 1, 3, 5 }, { 2, 4, 6 }, }; matrix B = { { 1, 0, 1, 0 }, { 0, 1, 0, 1 }, { 1, 0, 0, -2 } }; matrix result; getProduct(A, B, result); displayMatrix(result); return 0; } ================================================ FILE: cpp/cpp-pthread/exer06a-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version A: Synchronous queues */ #include #include #include #include #include using namespace std; template class SynchronousQueue { private: sem_t semPut; sem_t semTake; T element; public: SynchronousQueue() { sem_init(&semPut, 0, 1); sem_init(&semTake, 0, 0); } ~SynchronousQueue() { sem_destroy(&semPut); sem_destroy(&semTake); } void put(const T& value) { sem_wait(&semPut); element = value; sem_post(&semTake); } T take() { sem_wait(&semTake); T result = element; sem_post(&semPut); return result; } }; void* producer(void* arg) { auto syncQueue = (SynchronousQueue*) arg; auto arr = { "lorem", "ipsum", "foo" }; for (auto&& data : arr) { cout << "Producer: " << data << endl; syncQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto syncQueue = (SynchronousQueue*) arg; std::string data; sleep(5); for (int i = 0; i < 3; ++i) { data = syncQueue->take(); cout << "\tConsumer: " << data << endl; } pthread_exit(nullptr); return nullptr; } int main() { SynchronousQueue syncQueue; pthread_t tidProducer, tidConsumer; int ret = 0; ret = pthread_create(&tidProducer, nullptr, &producer, &syncQueue); ret = pthread_create(&tidConsumer, nullptr, &consumer, &syncQueue); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/exer06b01-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version B01: General blocking queues Underlying mechanism: Semaphores */ #include #include #include #include #include #include #include using namespace std; template class BlockingQueue { private: int capacity = 0; sem_t semRemain; sem_t semFill; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; std::queue q; public: BlockingQueue(int capacity) { if (capacity <= 0) throw std::invalid_argument("capacity must be a positive integer"); this->capacity = capacity; sem_init(&semRemain, 0, capacity); sem_init(&semFill, 0, 0); } ~BlockingQueue() { sem_destroy(&semRemain); sem_destroy(&semFill); pthread_mutex_destroy(&mut); } void put(const T& value) { int ret = 0; ret = sem_wait(&semRemain); ret = pthread_mutex_lock(&mut); q.push(value); ret = pthread_mutex_unlock(&mut); ret = sem_post(&semFill); } T take() { T result; int ret = 0; ret = sem_wait(&semFill); ret = pthread_mutex_lock(&mut); result = q.front(); q.pop(); ret = pthread_mutex_unlock(&mut); ret = sem_post(&semRemain); return result; } }; void* producer(void* arg) { auto blkQueue = (BlockingQueue*) arg; auto arr = { "nice", "to", "meet", "you" }; for (auto&& data : arr) { cout << "Producer: " << data << endl; blkQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkQueue = (BlockingQueue*) arg; std::string data; sleep(5); for (int i = 0; i < 4; ++i) { data = blkQueue->take(); cout << "\tConsumer: " << data << endl; if (0 == i) sleep(5); } pthread_exit(nullptr); return nullptr; } int main() { BlockingQueue blkQueue(2); // capacity = 2 pthread_t tidProducer, tidConsumer; int ret = 0; ret = pthread_create(&tidProducer, nullptr, &producer, &blkQueue); ret = pthread_create(&tidConsumer, nullptr, &consumer, &blkQueue); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/exer06b02-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version B02: General blocking queues Underlying mechanism: Condition variables */ #include #include #include #include #include #include using namespace std; template class BlockingQueue { private: pthread_cond_t condEmpty = PTHREAD_COND_INITIALIZER; pthread_cond_t condFull = PTHREAD_COND_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int capacity = 0; std::queue q; public: BlockingQueue(int capacity) { if (capacity <= 0) throw std::invalid_argument("capacity must be a positive integer"); this->capacity = capacity; } ~BlockingQueue() { pthread_cond_destroy(&condEmpty); pthread_cond_destroy(&condFull); pthread_mutex_destroy(&mut); } void put(const T& value) { int ret = 0; ret = pthread_mutex_lock(&mut); while ((int)q.size() >= capacity) { // Queue is full, must wait for 'take' ret = pthread_cond_wait(&condFull, &mut); } q.push(value); ret = pthread_mutex_unlock(&mut); ret = pthread_cond_signal(&condEmpty); } T take() { T result; int ret = 0; ret = pthread_mutex_lock(&mut); while (q.empty()) { // Queue is empty, must wait for 'put' ret = pthread_cond_wait(&condEmpty, &mut); } result = q.front(); q.pop(); ret = pthread_mutex_unlock(&mut); ret = pthread_cond_signal(&condFull); return result; } }; void* producer(void* arg) { auto blkQueue = (BlockingQueue*) arg; auto arr = { "nice", "to", "meet", "you" }; for (auto&& data : arr) { cout << "Producer: " << data << endl; blkQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } pthread_exit(nullptr); return nullptr; } void* consumer(void* arg) { auto blkQueue = (BlockingQueue*) arg; std::string data; sleep(5); for (int i = 0; i < 4; ++i) { data = blkQueue->take(); cout << "\tConsumer: " << data << endl; if (0 == i) sleep(5); } pthread_exit(nullptr); return nullptr; } int main() { BlockingQueue blkQueue(2); // capacity = 2 pthread_t tidProducer, tidConsumer; int ret = 0; ret = pthread_create(&tidProducer, nullptr, &producer, &blkQueue); ret = pthread_create(&tidConsumer, nullptr, &consumer, &blkQueue); ret = pthread_join(tidProducer, nullptr); ret = pthread_join(tidConsumer, nullptr); return 0; } ================================================ FILE: cpp/cpp-pthread/exer07a-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version A: Solving the problem using a condition variable */ #include #include #include #include #include using namespace std; struct Counter { int value; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; Counter(int value) : value(value) { } }; struct ProcessFilesArg { const vector * lstFileName; Counter * counter; }; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleep(20); cout << "[ Auth ] Done" << endl; } void* processFiles(void* argVoid) { auto arg = (ProcessFilesArg*) argVoid; auto&& lstFileName = *arg->lstFileName; auto&& counter = *arg->counter; for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleep(10); cout << "[ ReadFile ] Done " << fileName << endl; pthread_mutex_lock(&counter.mut); { --counter.value; pthread_cond_signal(&counter.cond); } pthread_mutex_unlock(&counter.mut); // Write log into disk sleep(5); cout << "[ WriteLog ]" << endl; } return nullptr; pthread_exit(nullptr); } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; Counter counter(lstFileName.size()); pthread_t tid; ProcessFilesArg arg = { &lstFileName, &counter }; // The server checks auth user while reading files, concurrently pthread_create(&tid, nullptr, &processFiles, &arg); checkAuthUser(); // The server waits for completion of loading files pthread_mutex_lock(&counter.mut); { while (counter.value > 0) { pthread_cond_wait(&counter.cond, &counter.mut); } } pthread_mutex_unlock(&counter.mut); cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; pthread_join(tid, nullptr); pthread_cond_destroy(&counter.cond); pthread_mutex_destroy(&counter.mut); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer07b-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version B: Solving the problem using a semaphore */ #include #include #include #include #include using namespace std; struct ProcessFilesArg { const vector * lstFileName; sem_t * sem; }; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleep(20); cout << "[ Auth ] Done" << endl; } void* processFiles(void* argVoid) { auto arg = (ProcessFilesArg*) argVoid; auto&& lstFileName = *arg->lstFileName; auto&& sem = *arg->sem; for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleep(10); cout << "[ ReadFile ] Done " << fileName << endl; sem_post(&sem); // Write log into disk sleep(5); cout << "[ WriteLog ]" << endl; } return nullptr; pthread_exit(nullptr); } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; sem_t sem; sem_init(&sem, 0, 0); pthread_t tid; ProcessFilesArg arg = { &lstFileName, &sem }; // The server checks auth user while reading files, concurrently pthread_create(&tid, nullptr, &processFiles, &arg); checkAuthUser(); // The server waits for completion of loading files for (size_t i = lstFileName.size(); i > 0; --i) { sem_wait(&sem); } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; pthread_join(tid, nullptr); sem_destroy(&sem); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer07c-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version C: Solving the problem using a count-down latch */ #include #include #include #include #include "mylib-latch.hpp" using namespace std; struct ProcessFilesArg { const vector * lstFileName; mylib::CountDownLatch * rdLatch; }; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleep(20); cout << "[ Auth ] Done" << endl; } void* processFiles(void* argVoid) { auto arg = (ProcessFilesArg*) argVoid; auto&& lstFileName = *arg->lstFileName; auto&& rdLatch = *arg->rdLatch; for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleep(10); cout << "[ ReadFile ] Done " << fileName << endl; rdLatch.countDown(); // Write log into disk sleep(5); cout << "[ WriteLog ]" << endl; } return nullptr; pthread_exit(nullptr); } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; mylib::CountDownLatch readFileLatch(lstFileName.size()); pthread_t tid; ProcessFilesArg arg = { &lstFileName, &readFileLatch }; // The server checks auth user while reading files, concurrently pthread_create(&tid, nullptr, &processFiles, &arg); checkAuthUser(); // The server waits for completion of loading files readFileLatch.wait(); cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; pthread_join(tid, nullptr); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer07d-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version D: Solving the problem using a blocking queue */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; struct ProcessFilesArg { const vector * lstFileName; mylib::BlockingQueue * blkq; }; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleep(20); cout << "[ Auth ] Done" << endl; } void* processFiles(void* argVoid) { auto arg = (ProcessFilesArg*) argVoid; auto&& lstFileName = *arg->lstFileName; auto&& blkq = *arg->blkq; for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleep(10); cout << "[ ReadFile ] Done " << fileName << endl; blkq.put(fileName); // You may put file data here // Write log into disk sleep(5); cout << "[ WriteLog ]" << endl; } return nullptr; pthread_exit(nullptr); } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; mylib::BlockingQueue blkq; pthread_t tid; ProcessFilesArg arg = { &lstFileName, &blkq }; // The server checks auth user while reading files, concurrently pthread_create(&tid, nullptr, &processFiles, &arg); checkAuthUser(); // The server waits for completion of loading files for (size_t i = lstFileName.size(); i > 0; --i) { blkq.take(); } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; pthread_join(tid, nullptr); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-itask.hpp ================================================ #ifndef _MY_EXEC_SERVICE_ITASK_HPP_ #define _MY_EXEC_SERVICE_ITASK_HPP_ // interface ITask class ITask { public: virtual ~ITask() = default; virtual void run() = 0; }; #endif // _MY_EXEC_SERVICE_ITASK_HPP_ ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-main.cpp ================================================ /* EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION */ #include #include #include "exer08-exec-service-itask.hpp" #include "exer08-exec-service-v0a.hpp" #include "exer08-exec-service-v0b.hpp" #include "exer08-exec-service-v1a.hpp" #include "exer08-exec-service-v1b.hpp" #include "exer08-exec-service-v2a.hpp" #include "exer08-exec-service-v2b.hpp" class MyTask : public ITask { public: char id; public: void run() override { std::cout << "Task " << id << " is starting" << std::endl; sleep(3); std::cout << "Task " << id << " is completed" << std::endl; } }; int main() { constexpr int NUM_THREADS = 2; constexpr int NUM_TASKS = 5; MyExecServiceV0A execService(NUM_THREADS); std::vector lstTask(NUM_TASKS); for (int i = 0; i < NUM_TASKS; ++i) lstTask[i].id = 'A' + i; for (auto&& task : lstTask) execService.submit(&task); std::cout << "All tasks are submitted" << std::endl; execService.waitTaskDone(); std::cout << "All tasks are completed" << std::endl; execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-v0a.hpp ================================================ /* MY EXECUTOR SERVICE Version 0A: The easiest executor service - It uses a blocking queue as underlying mechanism. */ #ifndef _MY_EXEC_SERVICE_V0A_HPP_ #define _MY_EXEC_SERVICE_V0A_HPP_ #include #include #include #include #include "mylib-blockingqueue.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV0A { private: int numThreads = 0; std::vector lstTh; mylib::BlockingQueue taskPending; public: MyExecServiceV0A(int numThreads) { init(numThreads); } MyExecServiceV0A(const MyExecServiceV0A& other) = delete; MyExecServiceV0A(const MyExecServiceV0A&& other) = delete; void operator=(const MyExecServiceV0A& other) = delete; void operator=(const MyExecServiceV0A&& other) = delete; private: void init(int numThreads) { this->numThreads = numThreads; lstTh.resize(numThreads); for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(ITask* task) { taskPending.add(task); } void waitTaskDone() { // This ExecService is too simple, // so there is no implementation for waitTaskDone() sleep(11); // fake behaviour } void shutdown() { // This ExecService is too simple, // so there is no implementation for shutdown() std::cout << "No implementation for shutdown()." << std::endl; std::cout << "You need to exit the app manually." << std::endl; } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (MyExecServiceV0A*) argVoid; auto&& taskPending = thisPtr->taskPending; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK auto task = taskPending.take(); // DO THE TASK task->run(); } pthread_exit(nullptr); return nullptr; } }; #endif // _MY_EXEC_SERVICE_V0A_HPP_ ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-v0b.hpp ================================================ /* MY EXECUTOR SERVICE Version 0B: The easiest executor service - It uses a blocking queue as underlying mechanism. - It supports waitTaskDone() and shutdown(). */ #ifndef _MY_EXEC_SERVICE_V0B_HPP_ #define _MY_EXEC_SERVICE_V0B_HPP_ #include #include #include #include #include "mylib-blockingqueue.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV0B { private: int numThreads = 0; std::vector lstTh; mylib::BlockingQueue taskPending; std::atomic_int32_t counterTaskRunning; volatile bool forceThreadShutdown; const class : ITask { void run() override { } } emptyTask; public: MyExecServiceV0B(int numThreads) { init(numThreads); } MyExecServiceV0B(const MyExecServiceV0B& other) = delete; MyExecServiceV0B(const MyExecServiceV0B&& other) = delete; void operator=(const MyExecServiceV0B& other) = delete; void operator=(const MyExecServiceV0B&& other) = delete; private: void init(int numThreads) { this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(ITask* task) { taskPending.add(task); } void waitTaskDone() { // This ExecService is too simple, // so there is no good implementation for waitTaskDone() while (false == taskPending.empty() || counterTaskRunning > 0) { sleep(1); // pthread_yield(); // sched_yield(); } } void shutdown() { forceThreadShutdown = true; taskPending.clear(); // Invoke blocked threads by adding "empty" tasks for (int i = 0; i < numThreads; ++i) { taskPending.put( (ITask* const) &emptyTask ); } for (auto&& th : lstTh) { pthread_join(th, nullptr); } numThreads = 0; lstTh.clear(); } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (MyExecServiceV0B*) argVoid; auto&& taskPending = thisPtr->taskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK auto task = taskPending.take(); // If shutdown() was called, then exit the function if (forceThreadShutdown) { break; } // DO THE TASK ++counterTaskRunning; task->run(); --counterTaskRunning; } pthread_exit(nullptr); return nullptr; } }; #endif // _MY_EXEC_SERVICE_V0B_HPP_ ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-v1a.hpp ================================================ /* MY EXECUTOR SERVICE Version 1A: Simple executor service - Method "waitTaskDone" invokes thread sleeps in loop (which can cause performance problems). */ #ifndef _MY_EXEC_SERVICE_V1A_HPP_ #define _MY_EXEC_SERVICE_V1A_HPP_ #include #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV1A { private: int numThreads = 0; std::vector lstTh; std::queue taskPending; pthread_mutex_t mutTaskPending = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskPending = PTHREAD_COND_INITIALIZER; std::atomic_int32_t counterTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV1A(int numThreads) { init(numThreads); } MyExecServiceV1A(const MyExecServiceV1A& other) = delete; MyExecServiceV1A(const MyExecServiceV1A&& other) = delete; void operator=(const MyExecServiceV1A& other) = delete; void operator=(const MyExecServiceV1A&& other) = delete; private: void init(int numThreads) { // shutdown(); mutTaskPending = PTHREAD_MUTEX_INITIALIZER; condTaskPending = PTHREAD_COND_INITIALIZER; this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(ITask* task) { pthread_mutex_lock(&mutTaskPending); taskPending.push(task); pthread_mutex_unlock(&mutTaskPending); pthread_cond_signal(&condTaskPending); } void waitTaskDone() { bool done = false; for (;;) { pthread_mutex_lock(&mutTaskPending); if (taskPending.empty() && 0 == counterTaskRunning) { done = true; } pthread_mutex_unlock(&mutTaskPending); if (done) { break; } sleep(1); // pthread_yield(); // sched_yield(); } } void shutdown() { pthread_mutex_lock(&mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); pthread_mutex_unlock(&mutTaskPending); pthread_cond_broadcast(&condTaskPending); for (auto&& th : lstTh) { pthread_join(th, nullptr); } numThreads = 0; lstTh.clear(); pthread_mutex_destroy(&mutTaskPending); pthread_cond_destroy(&condTaskPending); } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (MyExecServiceV1A*) argVoid; auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK pthread_mutex_lock(&mutTaskPending); while (taskPending.empty() and false == forceThreadShutdown) { pthread_cond_wait(&condTaskPending, &mutTaskPending); } if (forceThreadShutdown) { pthread_mutex_unlock(&mutTaskPending); break; } // GET THE TASK FROM THE PENDING QUEUE auto task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; pthread_mutex_unlock(&mutTaskPending); // DO THE TASK task->run(); --counterTaskRunning; } pthread_exit(nullptr); return nullptr; } }; #endif // _MY_EXEC_SERVICE_V1A_HPP_ ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-v1b.hpp ================================================ /* MY EXECUTOR SERVICE Version 1A: Simple executor service - Method "waitTaskDone" consumes CPU (due to bad synchronization). */ #ifndef _MY_EXEC_SERVICE_V1B_HPP_ #define _MY_EXEC_SERVICE_V1B_HPP_ #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV1B { private: int numThreads = 0; std::vector lstTh; std::queue taskPending; pthread_mutex_t mutTaskPending = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskPending = PTHREAD_COND_INITIALIZER; int counterTaskRunning; pthread_mutex_t mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskRunning = PTHREAD_COND_INITIALIZER; volatile bool forceThreadShutdown; public: MyExecServiceV1B(int numThreads) { init(numThreads); } MyExecServiceV1B(const MyExecServiceV1B& other) = delete; MyExecServiceV1B(const MyExecServiceV1B&& other) = delete; void operator=(const MyExecServiceV1B& other) = delete; void operator=(const MyExecServiceV1B&& other) = delete; private: void init(int numThreads) { // shutdown(); mutTaskPending = PTHREAD_MUTEX_INITIALIZER; condTaskPending = PTHREAD_COND_INITIALIZER; mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; condTaskRunning = PTHREAD_COND_INITIALIZER; this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(ITask* task) { pthread_mutex_lock(&mutTaskPending); taskPending.push(task); pthread_mutex_unlock(&mutTaskPending); pthread_cond_signal(&condTaskPending); } void waitTaskDone() { bool done = false; for (;;) { pthread_mutex_lock(&mutTaskPending); if (taskPending.empty()) { pthread_mutex_lock(&mutTaskRunning); while (counterTaskRunning > 0) pthread_cond_wait(&condTaskRunning, &mutTaskRunning); // no pending task and no running task done = true; pthread_mutex_unlock(&mutTaskRunning); } pthread_mutex_unlock(&mutTaskPending); if (done) { break; } } } void shutdown() { pthread_mutex_lock(&mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); pthread_mutex_unlock(&mutTaskPending); pthread_cond_broadcast(&condTaskPending); for (auto&& th : lstTh) { pthread_join(th, nullptr); } numThreads = 0; lstTh.clear(); pthread_mutex_destroy(&mutTaskPending); pthread_cond_destroy(&condTaskPending); pthread_mutex_destroy(&mutTaskRunning); pthread_cond_destroy(&condTaskRunning); } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (MyExecServiceV1B*) argVoid; auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& condTaskRunning = thisPtr->condTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK pthread_mutex_lock(&mutTaskPending); while (taskPending.empty() and false == forceThreadShutdown) { pthread_cond_wait(&condTaskPending, &mutTaskPending); } if (forceThreadShutdown) { pthread_mutex_unlock(&mutTaskPending); break; } // GET THE TASK FROM THE PENDING QUEUE auto task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; pthread_mutex_unlock(&mutTaskPending); // DO THE TASK task->run(); pthread_mutex_lock(&mutTaskRunning); --counterTaskRunning; if (0 == counterTaskRunning) { pthread_cond_signal(&condTaskRunning); } pthread_mutex_unlock(&mutTaskRunning); } pthread_exit(nullptr); return nullptr; } }; #endif // _MY_EXEC_SERVICE_V1B_HPP_ ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-v2a.hpp ================================================ /* MY EXECUTOR SERVICE Version 2A: The executor service storing running tasks - Method "waitTaskDone" uses a semaphore to synchronize. */ #ifndef _MY_EXEC_SERVICE_V2A_HPP_ #define _MY_EXEC_SERVICE_V2A_HPP_ #include #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV2A { private: int numThreads = 0; std::vector lstTh; std::queue taskPending; pthread_mutex_t mutTaskPending = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskPending = PTHREAD_COND_INITIALIZER; std::list taskRunning; pthread_mutex_t mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; sem_t counterTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV2A(int numThreads) { init(numThreads); } MyExecServiceV2A(const MyExecServiceV2A& other) = delete; MyExecServiceV2A(const MyExecServiceV2A&& other) = delete; void operator=(const MyExecServiceV2A& other) = delete; void operator=(const MyExecServiceV2A&& other) = delete; private: void init(int numThreads) { // shutdown(); mutTaskPending = PTHREAD_MUTEX_INITIALIZER; condTaskPending = PTHREAD_COND_INITIALIZER; mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; sem_init(&counterTaskRunning, 0, 0); this->numThreads = numThreads; lstTh.resize(numThreads); forceThreadShutdown = false; for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(ITask* task) { pthread_mutex_lock(&mutTaskPending); taskPending.push(task); pthread_mutex_unlock(&mutTaskPending); pthread_cond_signal(&condTaskPending); } void waitTaskDone() { bool done = false; for (;;) { sem_wait(&counterTaskRunning); pthread_mutex_lock(&mutTaskPending); pthread_mutex_lock(&mutTaskRunning); if (taskPending.empty() && taskRunning.empty()) { done = true; } pthread_mutex_unlock(&mutTaskRunning); pthread_mutex_unlock(&mutTaskPending); if (done) { break; } } } void shutdown() { pthread_mutex_lock(&mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); pthread_mutex_unlock(&mutTaskPending); pthread_cond_broadcast(&condTaskPending); for (auto&& th : lstTh) { pthread_join(th, nullptr); } numThreads = 0; lstTh.clear(); pthread_mutex_destroy(&mutTaskPending); pthread_cond_destroy(&condTaskPending); pthread_mutex_destroy(&mutTaskRunning); sem_destroy(&counterTaskRunning); } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (MyExecServiceV2A*) argVoid; auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& taskRunning = thisPtr->taskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK pthread_mutex_lock(&mutTaskPending); while (taskPending.empty() and false == forceThreadShutdown) { pthread_cond_wait(&condTaskPending, &mutTaskPending); } if (forceThreadShutdown) { pthread_mutex_unlock(&mutTaskPending); break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); // PUSH IT TO THE RUNNING QUEUE pthread_mutex_lock(&mutTaskRunning); taskRunning.push_back(task); pthread_mutex_unlock(&mutTaskRunning); pthread_mutex_unlock(&mutTaskPending); } // DO THE TASK task->run(); // REMOVE IT FROM THE RUNNING QUEUE pthread_mutex_lock(&mutTaskRunning); taskRunning.remove(task); pthread_mutex_unlock(&mutTaskRunning); sem_post(&counterTaskRunning); } pthread_exit(nullptr); return nullptr; } }; #endif // _MY_EXEC_SERVICE_V2A_HPP_ ================================================ FILE: cpp/cpp-pthread/exer08-exec-service-v2b.hpp ================================================ /* MY EXECUTOR SERVICE Version 2B: The executor service storing running tasks - Method "waitTaskDone" uses a condition variable to synchronize. */ #ifndef _MY_EXEC_SERVICE_V2B_HPP_ #define _MY_EXEC_SERVICE_V2B_HPP_ #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV2B { private: int numThreads = 0; std::vector lstTh; std::queue taskPending; pthread_mutex_t mutTaskPending = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskPending = PTHREAD_COND_INITIALIZER; std::list taskRunning; pthread_mutex_t mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskRunning = PTHREAD_COND_INITIALIZER; volatile bool forceThreadShutdown; public: MyExecServiceV2B(int numThreads) { init(numThreads); } MyExecServiceV2B(const MyExecServiceV2B& other) = delete; MyExecServiceV2B(const MyExecServiceV2B&& other) = delete; void operator=(const MyExecServiceV2B& other) = delete; void operator=(const MyExecServiceV2B&& other) = delete; private: void init(int numThreads) { // shutdown(); mutTaskPending = PTHREAD_MUTEX_INITIALIZER; condTaskPending = PTHREAD_COND_INITIALIZER; mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; condTaskRunning = PTHREAD_COND_INITIALIZER; this->numThreads = numThreads; lstTh.resize(numThreads); forceThreadShutdown = false; for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(ITask* task) { pthread_mutex_lock(&mutTaskPending); taskPending.push(task); pthread_mutex_unlock(&mutTaskPending); pthread_cond_signal(&condTaskPending); } void waitTaskDone() { bool done = false; for (;;) { pthread_mutex_lock(&mutTaskPending); if (taskPending.empty()) { pthread_mutex_lock(&mutTaskRunning); while (false == taskRunning.empty()) pthread_cond_wait(&condTaskRunning, &mutTaskRunning); pthread_mutex_unlock(&mutTaskRunning); done = true; } pthread_mutex_unlock(&mutTaskPending); if (done) { break; } } } void shutdown() { pthread_mutex_lock(&mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); pthread_mutex_unlock(&mutTaskPending); pthread_cond_broadcast(&condTaskPending); for (auto&& th : lstTh) { pthread_join(th, nullptr); } numThreads = 0; lstTh.clear(); pthread_mutex_destroy(&mutTaskPending); pthread_cond_destroy(&condTaskPending); pthread_mutex_destroy(&mutTaskRunning); pthread_cond_destroy(&condTaskRunning); } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (MyExecServiceV2B*) argVoid; auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& taskRunning = thisPtr->taskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& condTaskRunning = thisPtr->condTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK pthread_mutex_lock(&mutTaskPending); while (taskPending.empty() and false == forceThreadShutdown) { pthread_cond_wait(&condTaskPending, &mutTaskPending); } if (forceThreadShutdown) { pthread_mutex_unlock(&mutTaskPending); break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); // PUSH IT TO THE RUNNING QUEUE pthread_mutex_lock(&mutTaskRunning); taskRunning.push_back(task); pthread_mutex_unlock(&mutTaskRunning); pthread_mutex_unlock(&mutTaskPending); } // DO THE TASK task->run(); // REMOVE IT FROM THE RUNNING QUEUE pthread_mutex_lock(&mutTaskRunning); taskRunning.remove(task); pthread_mutex_unlock(&mutTaskRunning); pthread_cond_signal(&condTaskRunning); } pthread_exit(nullptr); return nullptr; } }; #endif // _MY_EXEC_SERVICE_V2B_HPP_ ================================================ FILE: cpp/cpp-pthread/exerex-countdown-timer-a.cpp ================================================ /* COUNTDOWN TIMER */ #include #include #include #include using namespace std; char* buffer = nullptr; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void* funcUserInput(void*) { cin.getline(buffer, 1024); pthread_cond_signal(&cond); pthread_exit(nullptr); return nullptr; } /* Return true if no timeout. Otherwise, return false. */ bool waitForTime(const int waitTime) { int ret = 0; timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += waitTime; pthread_mutex_lock(&mut); ret = pthread_cond_timedwait(&cond, &mut, &ts); pthread_mutex_unlock(&mut); if (0 != ret) { // if (ETIMEDOUT == ret) return false; return false; } return true; } int main() { constexpr int SECONDS = 5; char buff[1024] = { 0 }; buffer = buff; pthread_t tid; int ret = 0; cout << "You have " << SECONDS << " seconds to write anything you like in one line." << endl; cout << "Press enter to start." << endl; cin.getline(buffer, 1024); cout << "START!!!" << endl << endl; ret = pthread_create(&tid, nullptr, &funcUserInput, nullptr); if (waitForTime(SECONDS)) { cout << "\nYou completed before the deadline." << endl; } else { ret = pthread_cancel(tid); // Kill thread cout << "\n\nTIMEOUT!!!" << endl; } ret = pthread_join(tid, nullptr); ret = pthread_mutex_destroy(&mut); ret = pthread_cond_destroy(&cond); return 0; } ================================================ FILE: cpp/cpp-pthread/exerex-countdown-timer-b.cpp ================================================ /* COUNTDOWN TIMER */ #include #include #include #include using namespace std; char* buffer = nullptr; void* userInputFunc(void*) { cin.getline(buffer, 1024); pthread_exit(nullptr); return nullptr; } int main() { constexpr int SECONDS = 5; char buff[1024] = { 0 }; buffer = buff; pthread_t tid; timespec ts; int ret = 0; cout << "You have " << SECONDS << " seconds to write anything you like in one line." << endl; cout << "Press enter to start." << endl; cin.getline(buffer, 1024); cout << "START!!!" << endl << endl; ret = pthread_create(&tid, nullptr, &userInputFunc, nullptr); clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += SECONDS; if (ret = pthread_timedjoin_np(tid, nullptr, &ts)) { ret = pthread_cancel(tid); // Kill thread cout << "\n\nTIMEOUT!!!" << endl; } else { cout << "\nYou completed before the deadline." << endl; } return 0; } ================================================ FILE: cpp/cpp-pthread/mylib-blockingqueue.hpp ================================================ /****************************************************** * * File name: mylib-blockingqueue.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The blocking queue implementation in C++11 POSIX threading * ******************************************************/ #ifndef _MYLIB_BLOCKING_QUEUE_HPP_ #define _MYLIB_BLOCKING_QUEUE_HPP_ #include #include #include namespace mylib { template class BlockingQueue { private: pthread_cond_t condEmpty = PTHREAD_COND_INITIALIZER; pthread_cond_t condFull = PTHREAD_COND_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; size_t capacity; std::queue q; struct PendingData { BlockingQueue * thisPtr; const T data; PendingData(BlockingQueue * thisPtr, const T data) : thisPtr(thisPtr), data(data) { } }; public: BlockingQueue() : capacity(std::numeric_limits::max()) { } BlockingQueue(size_t capacity) : capacity(capacity) { } ~BlockingQueue() { pthread_cond_destroy(&condEmpty); pthread_cond_destroy(&condFull); pthread_mutex_destroy(&mut); } BlockingQueue(const BlockingQueue& other) = delete; BlockingQueue(const BlockingQueue&& other) = delete; void operator=(const BlockingQueue& other) = delete; void operator=(const BlockingQueue&& other) = delete; bool empty() const { return q.empty(); } size_t size() const { return q.size(); } // sync enqueue void put(const T& value) { int tmp = 0; tmp = pthread_mutex_lock(&mut); while (q.size() >= capacity) { tmp = pthread_cond_wait(&condFull, &mut); } q.push(value); tmp = pthread_mutex_unlock(&mut); tmp = pthread_cond_signal(&condEmpty); } // sync dequeue T take() { T result; int tmp = 0; tmp = pthread_mutex_lock(&mut); while (q.empty()) { // Queue is empty, must wait for 'put' tmp = pthread_cond_wait(&condEmpty, &mut); } result = q.front(); q.pop(); tmp = pthread_mutex_unlock(&mut); tmp = pthread_cond_signal(&condFull); return result; } // async enqueue void add(const T& value) { // Note: For asynchronous operations, we should use a long-live background thread // instead of using a temporary thread pthread_t tid; int tmp; auto arg = new PendingData(this, value); tmp = pthread_create(&tid, nullptr, &BlockingQueue::putPending, arg); tmp = pthread_detach(tid); } // returns false if queue is empty, otherwise returns true and assigns the result bool peek(T& result) const { bool ret = false; int tmp; tmp = pthread_mutex_lock(&mut); if (false == q.empty()) { result = q.front(); ret = true; } tmp = pthread_mutex_unlock(&mut); return ret; } void clear() { int tmp; tmp = pthread_mutex_lock(&mut); std::queue().swap(q); tmp = pthread_mutex_unlock(&mut); } private: static void* putPending(void* argVoid) { auto arg = (PendingData*) argVoid; arg->thisPtr->put(arg->data); delete arg; arg = nullptr; pthread_exit(nullptr); return nullptr; } }; // BlockingQueue } // namespace mylib #endif // _MYLIB_BLOCKING_QUEUE_HPP_ ================================================ FILE: cpp/cpp-pthread/mylib-execservice.hpp ================================================ /****************************************************** * * File name: mylib-execservice.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The executor service implementation in C++11 POSIX threading * ******************************************************/ /* Copy code from "MyExecServiceV1B" */ #ifndef _MYLIB_EXEC_SERVICE_HPP_ #define _MYLIB_EXEC_SERVICE_HPP_ #include #include #include #include namespace mylib { class ExecService { public: using taskFunc = std::function; private: int numThreads = 0; std::vector lstTh; std::queue taskPending; pthread_mutex_t mutTaskPending = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskPending = PTHREAD_COND_INITIALIZER; int counterTaskRunning; pthread_mutex_t mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condTaskRunning = PTHREAD_COND_INITIALIZER; volatile bool forceThreadShutdown; public: ExecService(int numThreads) { init(numThreads); } ExecService(const ExecService& other) = delete; ExecService(const ExecService&& other) = delete; void operator=(const ExecService& other) = delete; void operator=(const ExecService&& other) = delete; private: void init(int numThreads) { // shutdown(); mutTaskPending = PTHREAD_MUTEX_INITIALIZER; condTaskPending = PTHREAD_COND_INITIALIZER; mutTaskRunning = PTHREAD_MUTEX_INITIALIZER; condTaskRunning = PTHREAD_COND_INITIALIZER; this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { pthread_create(&th, nullptr, &threadWorkerFunc, this); } } public: void submit(taskFunc task) { pthread_mutex_lock(&mutTaskPending); taskPending.push(task); pthread_mutex_unlock(&mutTaskPending); pthread_cond_signal(&condTaskPending); } void waitTaskDone() { bool done = false; for (;;) { pthread_mutex_lock(&mutTaskPending); if (taskPending.empty()) { pthread_mutex_lock(&mutTaskRunning); while (counterTaskRunning > 0) pthread_cond_wait(&condTaskRunning, &mutTaskRunning); // no pending task and no running task done = true; pthread_mutex_unlock(&mutTaskRunning); } pthread_mutex_unlock(&mutTaskPending); if (done) { break; } } } void shutdown() { pthread_mutex_lock(&mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); pthread_mutex_unlock(&mutTaskPending); pthread_cond_broadcast(&condTaskPending); for (auto&& th : lstTh) { pthread_join(th, nullptr); } numThreads = 0; lstTh.clear(); pthread_mutex_destroy(&mutTaskPending); pthread_cond_destroy(&condTaskPending); pthread_mutex_destroy(&mutTaskRunning); pthread_cond_destroy(&condTaskRunning); } private: static void* threadWorkerFunc(void* argVoid) { auto thisPtr = (ExecService*) argVoid; auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& condTaskRunning = thisPtr->condTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK pthread_mutex_lock(&mutTaskPending); while (taskPending.empty() and false == forceThreadShutdown) { pthread_cond_wait(&condTaskPending, &mutTaskPending); } if (forceThreadShutdown) { pthread_mutex_unlock(&mutTaskPending); break; } // GET THE TASK FROM THE PENDING QUEUE auto task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; pthread_mutex_unlock(&mutTaskPending); // DO THE TASK task(); pthread_mutex_lock(&mutTaskRunning); --counterTaskRunning; if (0 == counterTaskRunning) { pthread_cond_signal(&condTaskRunning); } pthread_mutex_unlock(&mutTaskRunning); } pthread_exit(nullptr); return nullptr; } }; // ExecService } // namespace mylib #endif // _MYLIB_EXEC_SERVICE_HPP_ ================================================ FILE: cpp/cpp-pthread/mylib-latch.hpp ================================================ /****************************************************** * * File name: mylib-latch.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The count-down latch implementation in C++11 POSIX threading * ******************************************************/ #ifndef _MYLIB_COUNT_DOWN_LATCH_HPP_ #define _MYLIB_COUNT_DOWN_LATCH_HPP_ #include namespace mylib { class CountDownLatch { private: volatile int count; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; public: CountDownLatch(unsigned int count) { this->count = count; } ~CountDownLatch() { int ret; ret = pthread_cond_destroy(&cond); ret = pthread_mutex_destroy(&mut); } CountDownLatch(const CountDownLatch& other) = delete; CountDownLatch(const CountDownLatch&& other) = delete; void operator=(const CountDownLatch& other) = delete; void operator=(const CountDownLatch&& other) = delete; public: int getCount() const { return count; } void countDown() { pthread_mutex_lock(&mut); if (count <= 0) { return; } --count; if (count <= 0) { pthread_cond_broadcast(&cond); } pthread_mutex_unlock(&mut); } void wait() { pthread_mutex_lock(&mut); while (count > 0) { pthread_cond_wait(&cond, &mut); } pthread_mutex_unlock(&mut); } }; // CountDownLatch } // namespace mylib #endif // _MYLIB_COUNT_DOWN_LATCH_HPP_ ================================================ FILE: cpp/cpp-std/demo00.cpp ================================================ /* INTRODUCTION TO MULTITHREADING You should try running this app several times and see results. */ #include #include using namespace std; void doTask() { for (int i = 0; i < 300; ++i) cout << "B"; } int main() { std::thread th(&doTask); for (int i = 0; i < 300; ++i) cout << "A"; th.join(); cout << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo01a01-hello.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version A01: Using functions */ #include #include using namespace std; void doTask() { cout << "Hello from example thread" << endl; } int main() { std::thread th(&doTask); cout << "Hello from main thread" << endl; th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo01a02-hello.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version A02: Using functions allowing passing 2 arguments */ #include #include using namespace std; void doTask(char const* message, int number) { cout << message << " " << number << endl; } int main() { std::thread th(&doTask, "Good day", 19); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo01b-hello-class01.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using class methods */ #include #include #include using namespace std; class Example { public: void doTask(string message) { cout << message << endl; } }; int main() { Example example; std::thread th(&Example::doTask, &example, "Good day"); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo01b-hello-class02.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using class methods */ #include #include #include using namespace std; class Example { public: void run() { std::thread th(&Example::doTask, this, "Good day"); th.join(); } private: void doTask(string message) { cout << message << endl; } }; int main() { Example example; example.run(); return 0; } ================================================ FILE: cpp/cpp-std/demo01b-hello-class03.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using class methods */ #include #include #include using namespace std; class Example { public: void run() { std::thread th(&Example::doTask, "Good day"); th.join(); } private: static void doTask(string message) { cout << message << endl; } }; int main() { Example example; example.run(); return 0; } ================================================ FILE: cpp/cpp-std/demo01b-hello-functor.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using functors */ #include #include #include using namespace std; class Example { public: void operator()(string message) { cout << message << endl; } }; int main() { Example example; std::thread th(example, "Good day"); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo01c-hello-lambda.cpp ================================================ /* HELLO WORLD VERSION MULTITHREADING Version C: Using lambdas */ #include #include #include using namespace std; int main() { auto doTask = [](string message) { cout << message << endl; }; std::thread th(doTask, "Good day"); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo02-join.cpp ================================================ /* THREAD JOINS */ #include #include using namespace std; void doHeavyTask() { // Do a heavy task, which takes a little time for (int i = 0; i < 2000000000; ++i); cout << "Done!" << endl; } int main() { std::thread th(&doHeavyTask); th.join(); cout << "Good bye!" << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo03a-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version A: Passing multiple arguments with various data types */ #include #include #include #include using namespace std; struct Point { int x; int y; Point(int x, int y): x(x), y(y) { } }; void doTask(int a, double b, string c, char const* d, Point e) { char buffer[50] = { 0 }; std::sprintf(buffer, "%d %.1f %s %s (%d %d)", a, b, c.data(), d, e.x, e.y); cout << buffer << endl; } int main() { auto thFoo = std::thread(&doTask, 1, 2, "red", "red", Point(0, 0)); auto thBar = std::thread(&doTask, 3, 4, "blue", "blue", Point(9, 9)); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo03b-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version B: Passing constant references */ #include #include #include using namespace std; void doTask(const string& msg) { cout << msg << endl; } int main() { auto thFoo = std::thread(&doTask, "foo"); auto thBar = std::thread(&doTask, "bar"); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo03c-pass-arg.cpp ================================================ /* PASSING ARGUMENTS Version C: Passing normal references */ #include #include #include using namespace std; /* The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g. with std::ref or std::cref). Passing references to thread functions may cause memory violation (e.g. when object is destroyed). By wrapping reference arguments with the class template std::reference_wrapper (using the function templates std::ref and std::cref), you explicitly express your intentions. */ void doTask(string& msg) { cout << msg << endl; } int main() { string a = "lorem ipsum"; string b = "dolor amet"; // auto thFoo = std::thread(doTask, a); // error auto thFoo = std::thread(&doTask, std::ref(a)); auto thBar = std::thread(&doTask, std::ref(b)); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo04a-sleep.cpp ================================================ /* SLEEP Version A: Sleep for a specific duration */ #include #include #include #include using namespace std; void doTask(string name) { cout << name << " is sleeping" << endl; std::this_thread::sleep_for(std::chrono::seconds(3)); cout << name << " wakes up" << endl; } int main() { auto thFoo = std::thread(&doTask, "foo"); thFoo.join(); cout << "Good bye" << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo04b-sleep.cpp ================================================ /* SLEEP Version B: Sleep until a specific time point */ #include #include #include #include #include "mylib-time.hpp" using namespace std; using sysclock = std::chrono::system_clock; void doTask(string name, sysclock::time_point tpWakeUp) { std::this_thread::sleep_until(tpWakeUp); cout << name << " wakes up" << endl; } int main() { auto tpNow = sysclock::now(); auto tpWakeUpFoo = tpNow + std::chrono::seconds(7); auto tpWakeUpBar = tpNow + std::chrono::seconds(3); cout << "foo will sleep until " << mylib::getTimePointStr(tpWakeUpFoo) << endl; cout << "bar will sleep until " << mylib::getTimePointStr(tpWakeUpBar) << endl; auto thFoo = std::thread(&doTask, "foo", tpWakeUpFoo); auto thBar = std::thread(&doTask, "bar", tpWakeUpBar); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo05-id.cpp ================================================ /* GETTING THREAD'S ID */ #include #include #include using namespace std; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(2)); cout << std::this_thread::get_id() << endl; } int main() { auto thFoo = std::thread(&doTask); auto thBar = std::thread(&doTask); cout << "foo's id: " << thFoo.get_id() << endl; cout << "bar's id: " << thBar.get_id() << endl; thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo06a-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version A: Using standard arrays */ #include #include using namespace std; void doTask(int index) { cout << index; } int main() { constexpr int NUM_THREADS = 5; std::thread lstTh[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; ++i) { lstTh[i] = std::thread(&doTask, i); } for (auto&& th : lstTh) { th.join(); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo06b-list-threads.cpp ================================================ /* LIST OF MULTIPLE THREADS Version B: Using the std::vector */ #include #include #include using namespace std; void doTask(int index) { cout << index; } int main() { constexpr int NUM_THREADS = 5; vector lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.push_back(std::thread(&doTask, i)); // or... // auto th = std::thread(&doTask, i); // lstTh.push_back(std::move(th)); // Because std::thread does not have copy constructors } for (auto&& th : lstTh) { th.join(); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo07-terminate.cpp ================================================ /* FORCING A THREAD TO TERMINATE (i.e. killing the thread) */ #include #include #include using namespace std; volatile bool isRunning; void doTask() { while (isRunning) { cout << "Running..." << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); } } int main() { isRunning = true; auto th = std::thread(&doTask); std::this_thread::sleep_for(std::chrono::seconds(6)); isRunning = false; th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo08a-return-value.cpp ================================================ /* GETTING RETURNED VALUES FROM THREADS Version A: Using pointers or references (traditional way) */ #include #include using namespace std; void doubleValue(int arg, int* res) { (*res) = arg * 2; } void squareValue(int arg, int& res) { res = arg * arg; } int main() { int result[3]; auto thFoo = std::thread(&doubleValue, 5, &result[0]); auto thBar = std::thread(&doubleValue, 80, &result[1]); auto thEgg = std::thread(&squareValue, 7, std::ref(result[2])); thFoo.join(); thBar.join(); thEgg.join(); cout << result[0] << endl; cout << result[1] << endl; cout << result[2] << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo08b-return-value.cpp ================================================ /* GETTING RETURNED VALUES FROM THREADS Version B: Using std::future with the promise */ #include #include #include #include using namespace std; void doubleValue(int arg, std::promise & prom) { int result = arg * 2; std::this_thread::sleep_for(std::chrono::seconds(2)); prom.set_value(result); std::this_thread::sleep_for(std::chrono::seconds(2)); cout << "This thread is exiting" << endl; } int main() { auto prom = std::promise(); auto fut = prom.get_future(); // fut is std::future auto th = std::thread(&doubleValue, 5, std::ref(prom)); // Block until prom.set_value() executes int result = fut.get(); cout << result << endl; th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo08c-return-value.cpp ================================================ /* GETTING RETURNED VALUES FROM THREADS Version C: Using std::future with the packaged_task */ #include #include #include #include #include #include using namespace std; string doubleValue(int arg) { int result = arg * 2; std::this_thread::sleep_for(std::chrono::seconds(2)); cout << "This thread is exiting" << endl; return to_string(result); } int main() { auto ptask = std::packaged_task(doubleValue); auto fut = ptask.get_future(); // fut is std::future auto th = std::thread(std::move(ptask), 5); // Block until ptask finishes string result = fut.get(); cout << result << endl; th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo09-detach.cpp ================================================ /* THREAD DETACHING */ #include #include #include using namespace std; void foo() { cout << "foo is starting..." << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); cout << "foo is exiting..." << endl; } int main() { auto thFoo = std::thread(&foo); thFoo.detach(); // If I comment this statement, // thFoo will be forced into terminating with main thread std::this_thread::sleep_for(std::chrono::seconds(3)); cout << "Main thread is exiting" << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo10-yield.cpp ================================================ /* THREAD YIELDING */ #include #include #include #include "mylib-time.hpp" using namespace std; using chrmicro = std::chrono::microseconds; using hrclock = mylib::HiResClock; void littleSleep(int us) { auto tpStart = hrclock::now(); auto tpEnd = tpStart + chrmicro(us); do { std::this_thread::yield(); } while (hrclock::now() < tpEnd); } int main() { auto tpStartMeasure = hrclock::now(); littleSleep(130); auto timeElapsed = hrclock::getTimeSpan(tpStartMeasure); cout << "Elapsed time: " << timeElapsed.count() << " microseonds" << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo11a-exec-service.cpp ================================================ /* EXECUTOR SERVICES AND THREAD POOLS Executor services in C++ std threading are not supported by default. So, I use mylib::ExecService for this demonstration. */ #include #include "mylib-execservice.hpp" using namespace std; void doTask() { cout << "Hello the Executor Service" << endl; } class MyFunctor { public: void operator()() { cout << "Hello Multithreading" << endl; } }; int main() { // INIT THE EXECUTOR SERVICE WITH 2 THREADS auto execService = mylib::ExecService(2); // SUBMIT execService.submit([] { cout << "Hello World" << endl; }); execService.submit(&doTask); execService.submit(MyFunctor()); // WAIT FOR THE COMPLETION OF ALL TASKS AND SHUTDOWN EXECUTOR SERVICE execService.waitTaskDone(); execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-std/demo11b-exec-service.cpp ================================================ /* EXECUTOR SERVICES AND THREAD POOLS Executor services in C++ std threading are not supported by default. So, I use mylib::ExecService for this demonstration. */ #include #include #include #include "mylib-execservice.hpp" using namespace std; int main() { constexpr int NUM_THREADS = 2; constexpr int NUM_TASKS = 5; auto execService = mylib::ExecService(NUM_THREADS); for (int i = 0; i < NUM_TASKS; ++i) { execService.submit([=] { char id = 'A' + i; cout << "Task " << id << " is starting" << endl; std::this_thread::sleep_for(std::chrono::seconds(3)); cout << "Task " << id << " is completed" << endl; }); } cout << "All tasks are submitted" << endl; execService.waitTaskDone(); cout << "All tasks are completed" << endl; execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-std/demo12a-race-condition.cpp ================================================ /* RACE CONDITIONS */ #include #include #include #include using namespace std; void doTask(int index) { std::this_thread::sleep_for(std::chrono::seconds(1)); cout << index; } int main() { constexpr int NUM_THREADS = 4; vector lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.push_back( std::thread(&doTask, i) ); } for (auto&& th : lstTh) { th.join(); } cout << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo12b01-data-race-single.cpp ================================================ /* DATA RACES Version 01: Without multithreading */ #include #include #include using namespace std; int getResult(int N) { vector a; a.resize(N + 1, false); for (int i = 1; i <= N; ++i) if (0 == i % 2 || 0 == i % 3) a[i] = true; // result = sum of a (i.e. counting number of true values in a) int result = std::accumulate(a.begin(), a.end(), 0); return result; } int main() { constexpr int N = 8; int result = getResult(N); cout << "Number of integers that are divisible by 2 or 3 is: " << result << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo12b02-data-race-multi.cpp ================================================ /* DATA RACES Version 02: Multithreading */ #include #include #include #include using namespace std; void markDiv2(vector & a, int N) { for (int i = 2; i <= N; i += 2) a[i] = true; } void markDiv3(vector & a, int N) { for (int i = 3; i <= N; i += 3) a[i] = true; } int main() { constexpr int N = 8; vector a; a.resize(N + 1, false); auto thDiv2 = std::thread(&markDiv2, std::ref(a), N); auto thDiv3 = std::thread(&markDiv3, std::ref(a), N); thDiv2.join(); thDiv3.join(); // result = sum of a (i.e. counting numbers of true values in a) int result = std::accumulate(a.begin(), a.end(), 0); cout << "Number of integers that are divisible by 2 or 3 is: " << result << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo12c01-race-cond-data-race.cpp ================================================ /* RACE CONDITIONS AND DATA RACES */ #include #include #include using namespace std; int counter = 0; void increaseCounter() { std::this_thread::sleep_for(std::chrono::seconds(1)); for (int i = 0; i < 1000; ++i) { counter += 1; } } int main() { constexpr int NUM_THREADS = 16; std::thread lstTh[NUM_THREADS]; for (auto&& th : lstTh) { th = std::thread(&increaseCounter); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; // We are NOT sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-std/demo12c02-race-cond-data-race.cpp ================================================ /* RACE CONDITIONS AND DATA RACES */ #include #include #include using namespace std; using sysclock = std::chrono::system_clock; int counter = 0; void doTaskA(sysclock::time_point timePointWakeUp) { std::this_thread::sleep_until(timePointWakeUp); while (counter < 10) ++counter; cout << "A won !!!" << endl; } void doTaskB(sysclock::time_point timePointWakeUp) { std::this_thread::sleep_until(timePointWakeUp); while (counter > -10) --counter; cout << "B won !!!" << endl; } int main() { auto tpNow = sysclock::now(); auto tpWakeUp = tpNow + std::chrono::seconds(1); auto thA = std::thread(&doTaskA, tpWakeUp); auto thB = std::thread(&doTaskB, tpWakeUp); thA.join(); thB.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo13a-mutex.cpp ================================================ /* MUTEXES */ #include #include #include #include using namespace std; std::mutex mut; int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); mut.lock(); for (int i = 0; i < 1000; ++i) ++counter; mut.unlock(); } int main() { constexpr int NUM_THREADS = 16; std::thread lstTh[NUM_THREADS]; for (auto&& th : lstTh) { th = std::thread(&doTask); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-std/demo13b01-mutex.cpp ================================================ /* MUTEXES std::lock_guard is a class template, which implements the RAII for mutex. It wraps the mutex inside it’s object and locks the attached mutex in its constructor. When it’s destructor is called it releases the mutex. */ #include #include #include #include using namespace std; std::mutex mut; int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); std::lock_guard lk(mut); for (int i = 0; i < 1000; ++i) ++counter; // Once function exits, then destructor of lk object will be called. // In destructor it unlocks the mutex. } int main() { constexpr int NUM_THREADS = 16; std::thread lstTh[NUM_THREADS]; for (auto&& th : lstTh) { th = std::thread(&doTask); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-std/demo13b02-mutex.cpp ================================================ /* MUTEXES - std::lock_guard will be locked only once on construction and unlocked on destruction. - In contrast to std::lock_guard, std::unique_lock can be: + created without immediately locking, and + unlocked at any point in its existence. Furthermore, C++17 introduces a new lock class called std::scoped_lock. The std::scoped_lock is a strictly superior version of std::lock_guard that locks an arbitrary number of mutexes all at once. */ #include #include #include #include using namespace std; std::mutex mut; int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock lk(mut); // std::scoped_lock lk(mut); for (int i = 0; i < 1000; ++i) ++counter; lk.unlock(); // Do something... } int main() { constexpr int NUM_THREADS = 16; std::thread lstTh[NUM_THREADS]; for (auto&& th : lstTh) { th = std::thread(&doTask); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-std/demo13c-mutex-trylock.cpp ================================================ /* MUTEXES Locking with a nonblocking mutex */ #include #include #include #include using namespace std; std::mutex mut; int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); if (false == mut.try_lock()) { return; } for (int i = 0; i < 10000; ++i) ++counter; mut.unlock(); } int main() { constexpr int NUM_THREADS = 3; std::thread lstTh[NUM_THREADS]; for (auto&& th : lstTh) { th = std::thread(&doTask); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; // counter can be 10000, 20000 or 30000 return 0; } ================================================ FILE: cpp/cpp-std/demo14-synchronized-block.cpp ================================================ /* SYNCHRONIZED BLOCKS Synchronized blocks in C++ std threading are not supported by default. To demonstate synchronized blocks, I use std::unique_lock (or std::lock_guard, std::scoped_lock). Now, let's see the code: { std::unique_lock lk(mut); // Do something in the critical section } The code block above is protected by a lock/mutex. That means it is synchronized on thread execution. This code block is called "the synchronized block". */ #include #include #include #include using namespace std; std::mutex mut; int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); // This is the "synchronized block" { std::unique_lock lk(mut); for (int i = 0; i < 1000; ++i) ++counter; } } int main() { constexpr int NUM_THREADS = 16; std::thread lstTh[NUM_THREADS]; for (auto&& th : lstTh) { th = std::thread(&doTask); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; // We are sure that counter = 16000 return 0; } ================================================ FILE: cpp/cpp-std/demo15a-deadlock.cpp ================================================ /* DEADLOCK Version A */ #include #include #include #include using namespace std; std::mutex mut; void doTask(std::string name) { mut.lock(); cout << name << " acquired resource" << endl; // mut.unlock(); // Forget this statement ==> deadlock } int main() { auto thFoo = std::thread(&doTask, "foo"); auto thBar = std::thread(&doTask, "bar"); thFoo.join(); thBar.join(); cout << "You will never see this statement due to deadlock!" << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo15b-deadlock.cpp ================================================ /* DEADLOCK Version B */ #include #include #include #include using namespace std; std::mutex mutResourceA; std::mutex mutResourceB; void foo() { mutResourceA.lock(); cout << "foo acquired resource A" << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); mutResourceB.lock(); cout << "foo acquired resource B" << endl; mutResourceB.unlock(); mutResourceA.unlock(); } void bar() { mutResourceB.lock(); cout << "bar acquired resource B" << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); mutResourceA.lock(); cout << "bar acquired resource A" << endl; mutResourceA.unlock(); mutResourceB.unlock(); } int main() { auto thFoo = std::thread(&foo); auto thBar = std::thread(&bar); thFoo.join(); thBar.join(); cout << "You will never see this statement due to deadlock!" << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo16-monitor.cpp ================================================ /* MONITORS Implementation of a monitor for managing a counter */ #include #include #include #include using namespace std; class Monitor { private: std::mutex mut; int* pCounter = nullptr; public: // Should disable copy/move constructors, copy/move assignment operators void init(int* pCounter) { this->pCounter = pCounter; } void increaseCounter() { mut.lock(); (*pCounter) += 1; mut.unlock(); } }; void doTask(Monitor* monitor) { std::this_thread::sleep_for(std::chrono::seconds(1)); for (int i = 0; i < 1000; ++i) monitor->increaseCounter(); } int main() { int counter = 0; Monitor monitor; constexpr int NUM_THREADS = 16; std::thread lstTh[NUM_THREADS]; monitor.init(&counter); for (auto&& th : lstTh) { th = std::thread(&doTask, &monitor); } for (auto&& th : lstTh) { th.join(); } cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo17a-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version A: Introduction to reentrant locks */ #include #include #include using namespace std; std::mutex mut; void doTask() { mut.lock(); cout << "First time acquiring the resource" << endl; mut.lock(); cout << "Second time acquiring the resource" << endl; mut.unlock(); mut.unlock(); } int main() { auto th = std::thread(&doTask); /* The thread th shall meet deadlock. So, you will never get output "Second time the acquiring resource". */ th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo17b-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version B: Solving the problem from version A */ #include #include #include using namespace std; std::recursive_mutex mut; void doTask() { mut.lock(); cout << "First time acquiring the resource" << endl; mut.lock(); cout << "Second time acquiring the resource" << endl; mut.unlock(); mut.unlock(); } void doTaskUsingSyncBlock() { using uniquelk = std::unique_lock; uniquelk lk(mut); cout << "First time acquiring the resource" << endl; { uniquelk lk(mut); cout << "Second time acquiring the resource" << endl; } } int main() { auto th = std::thread(&doTask); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo17c-reentrant-lock.cpp ================================================ /* REENTRANT LOCKS (RECURSIVE MUTEXES) Version C: A multithreaded app example */ #include #include #include #include using namespace std; std::recursive_mutex mut; void doTask(char name) { std::this_thread::sleep_for(std::chrono::seconds(1)); mut.lock(); cout << "First time " << name << " acquiring the resource" << endl; mut.lock(); cout << "Second time " << name << " acquiring the resource" << endl; mut.unlock(); mut.unlock(); } void doTaskUsingSyncBlock(char name) { using uniquelk = std::unique_lock; std::this_thread::sleep_for(std::chrono::seconds(1)); { uniquelk lk(mut); cout << "First time " << name << " acquiring the resource" << endl; { uniquelk lk(mut); cout << "Second time " << name << " acquiring the resource" << endl; } } } int main() { constexpr int NUM_THREADS = 3; std::thread lstTh[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; ++i) { lstTh[i] = std::thread(&doTask, char(i + 'A')); } for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo18a01-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include #include using namespace std; auto syncPoint = std::barrier(3); // participant count = 3 void processRequest(string userName, int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPoint.arrive_and_wait(); cout << "Process request for " << userName << endl; syncPoint.arrive_and_wait(); cout << "Done " << userName << endl; } int main() { constexpr int NUM_THREADS = 3; std::thread lstTh[NUM_THREADS]; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 2 }, { "dolor", 3 } }; for (int i = 0; i < NUM_THREADS; ++i) { auto&& arg = lstArg[i]; lstTh[i] = std::thread(&processRequest, std::get<0>(arg), std::get<1>(arg)); } for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo18a02-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include #include using namespace std; auto syncPoint = std::barrier(2); // participant count = 2 void processRequest(string userName, int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPoint.arrive_and_wait(); cout << "Process request for " << userName << endl; syncPoint.arrive_and_wait(); cout << "Done " << userName << endl; } int main() { constexpr int NUM_THREADS = 4; std::thread lstTh[NUM_THREADS]; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 3 }, { "dolor", 3 }, { "amet", 10 } }; for (int i = 0; i < NUM_THREADS; ++i) { auto&& arg = lstArg[i]; lstTh[i] = std::thread(&processRequest, std::get<0>(arg), std::get<1>(arg)); } // Thread with userName = "amet" shall be FREEZED for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo18a03-barrier.cpp ================================================ /* BARRIERS AND LATCHES Version A: Cyclic barriers */ #include #include #include #include #include #include using namespace std; auto syncPointA = std::barrier(2); auto syncPointB = std::barrier(2); void processRequest(string userName, int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPointA.arrive_and_wait(); cout << "Process request for " << userName << endl; syncPointB.arrive_and_wait(); cout << "Done " << userName << endl; } int main() { constexpr int NUM_THREADS = 4; std::thread lstTh[NUM_THREADS]; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 3 }, { "dolor", 3 }, { "amet", 10 } }; for (int i = 0; i < NUM_THREADS; ++i) { auto&& arg = lstArg[i]; lstTh[i] = std::thread(&processRequest, std::get<0>(arg), std::get<1>(arg)); } for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo18b01-latch.cpp ================================================ /* BARRIERS AND LATCHES Version B: Count-down latches */ #include #include #include #include #include #include using namespace std; auto syncPoint = std::latch(3); // participant count = 3 void processRequest(string userName, int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); cout << "Get request from " << userName << endl; syncPoint.arrive_and_wait(); cout << "Done " << userName << endl; } int main() { constexpr int NUM_THREADS = 3; std::thread lstTh[NUM_THREADS]; // tuple tuple lstArg[NUM_THREADS] = { { "lorem", 1 }, { "ipsum", 2 }, { "dolor", 3 } }; for (int i = 0; i < NUM_THREADS; ++i) { auto&& arg = lstArg[i]; lstTh[i] = std::thread(&processRequest, std::get<0>(arg), std::get<1>(arg)); } for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo18b02-latch.cpp ================================================ /* BARRIERS AND LATCHES Version B: Count-down latches Main thread waits for 3 child threads to get enough data to progress. */ #include #include #include #include #include #include using namespace std; constexpr int NUM_THREADS = 3; auto syncPoint = std::latch(NUM_THREADS); void doTask(string message, int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); cout << message << endl; syncPoint.count_down(); std::this_thread::sleep_for(std::chrono::seconds(8)); cout << "Cleanup" << endl; } int main() { std::thread lstTh[NUM_THREADS]; // tuple tuple lstArg[NUM_THREADS] = { { "Send request to egg.net to get data", 6 }, { "Send request to foo.org to get data", 2 }, { "Send request to bar.com to get data", 4 } }; for (int i = 0; i < NUM_THREADS; ++i) { auto&& arg = lstArg[i]; lstTh[i] = std::thread(&doTask, std::get<0>(arg), std::get<1>(arg)); } syncPoint.wait(); cout << "\nNow we have enough data to progress to next step\n" << endl; for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo19a-read-write-lock.cpp ================================================ /* READ-WRITE LOCKS */ #include #include #include #include #include #include "mylib-random.hpp" using namespace std; volatile int resource = 0; auto rwmut = std::shared_mutex(); void readFunc(int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); rwmut.lock_shared(); cout << "read: " << resource << endl; rwmut.unlock_shared(); } void writeFunc(int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); rwmut.lock(); resource = mylib::RandInt::get(100); cout << "write: " << resource << endl; rwmut.unlock(); } int main() { constexpr int NUM_THREADS_READ = 10; constexpr int NUM_THREADS_WRITE = 4; constexpr int NUM_ARGS = 3; std::thread lstThRead[NUM_THREADS_READ]; std::thread lstThWrite[NUM_THREADS_WRITE]; int lstArg[NUM_ARGS]; // INITIALIZE // lstArg = { 0, 1, 2, ..., NUM_ARG - 1 } std::iota(lstArg, lstArg + NUM_ARGS, 0); // CREATE THREADS for (auto&& th : lstThRead) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; th = std::thread(&readFunc, arg); } for (auto&& th : lstThWrite) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; th = std::thread(&writeFunc, arg); } // JOIN THREADS for (auto&& th : lstThRead) { th.join(); } for (auto&& th : lstThWrite) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo19b-read-write-lock.cpp ================================================ /* READ-WRITE LOCKS */ #include #include #include #include #include #include "mylib-random.hpp" using namespace std; volatile int resource = 0; auto rwmut = std::shared_mutex(); void readFunc(int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); std::shared_lock lk(rwmut); cout << "read: " << resource << endl; // lk.unlock(); } void writeFunc(int waitTime) { std::this_thread::sleep_for(std::chrono::seconds(waitTime)); std::lock_guard lk(rwmut); // std::unique_lock lk(rwmut); resource = mylib::RandInt::get(100); cout << "write: " << resource << endl; // lk.unlock(); } int main() { constexpr int NUM_THREADS_READ = 10; constexpr int NUM_THREADS_WRITE = 4; constexpr int NUM_ARGS = 3; std::thread lstThRead[NUM_THREADS_READ]; std::thread lstThWrite[NUM_THREADS_WRITE]; int lstArg[NUM_ARGS]; // INITIALIZE // lstArg = { 0, 1, 2, ..., NUM_ARG - 1 } std::iota(lstArg, lstArg + NUM_ARGS, 0); // CREATE THREADS for (auto&& th : lstThRead) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; th = std::thread(&readFunc, arg); } for (auto&& th : lstThWrite) { int arg = lstArg[ mylib::RandInt::get(NUM_ARGS) ]; th = std::thread(&writeFunc, arg); } // JOIN THREADS for (auto&& th : lstThRead) { th.join(); } for (auto&& th : lstThWrite) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/demo20a01-semaphore.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages */ #include #include #include #include using namespace std; auto semPackage = std::counting_semaphore(0); void makeOneSheet() { for (int i = 0; i < 4; ++i) { cout << "Make 1 sheet" << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); semPackage.release(); } } void combineOnePackage() { for (int i = 0; i < 4; ++i) { semPackage.acquire(); semPackage.acquire(); cout << "Combine 2 sheets into 1 package" << endl; } } int main() { auto thMakeSheetA = std::thread(&makeOneSheet); auto thMakeSheetB = std::thread(&makeOneSheet); auto thCombinePackage = std::thread(&combineOnePackage); thMakeSheetA.join(); thMakeSheetB.join(); thCombinePackage.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo20a02-semaphore.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages */ #include #include #include #include using namespace std; auto semPackage = std::counting_semaphore(0); auto semSheet = std::counting_semaphore(2); void makeOneSheet() { for (int i = 0; i < 2; ++i) { semSheet.acquire(); cout << "Make 1 sheet" << endl; semPackage.release(); } } void combineOnePackage() { for (int i = 0; i < 2; ++i) { semPackage.acquire(); semPackage.acquire(); cout << "Combine 2 sheets into 1 package" << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); semSheet.release(); semSheet.release(); } } int main() { auto thMakeSheetA = std::thread(&makeOneSheet); auto thMakeSheetB = std::thread(&makeOneSheet); auto thCombinePackage = std::thread(&combineOnePackage); thMakeSheetA.join(); thMakeSheetB.join(); thCombinePackage.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo20a03-semaphore-deadlock.cpp ================================================ /* SEMAPHORES Version A: Paper sheets and packages */ #include #include #include #include using namespace std; auto semPackage = std::counting_semaphore(0); auto semSheet = std::counting_semaphore(2); void makeOneSheet() { for (int i = 0; i < 4; ++i) { semSheet.acquire(); cout << "Make 1 sheet" << endl; semPackage.release(); } } void combineOnePackage() { for (int i = 0; i < 4; ++i) { semPackage.acquire(); semPackage.acquire(); cout << "Combine 2 sheets into 1 package" << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); semSheet.release(); // Missing one statement: semSheet.release() ==> deadlock } } int main() { auto thMakeSheetA = std::thread(&makeOneSheet); auto thMakeSheetB = std::thread(&makeOneSheet); auto thCombinePackage = std::thread(&combineOnePackage); thMakeSheetA.join(); thMakeSheetB.join(); thCombinePackage.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo20b-semaphore.cpp ================================================ /* SEMAPHORES Version B: Tires and chassis */ #include #include #include #include using namespace std; auto semTire = std::counting_semaphore(4); auto semChassis = std::counting_semaphore(0); void makeTire() { for (int i = 0; i < 8; ++i) { semTire.acquire(); cout << "Make 1 tire" << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); semChassis.release(); } } void makeChassis() { for (int i = 0; i < 4; ++i) { semChassis.acquire(); semChassis.acquire(); semChassis.acquire(); semChassis.acquire(); cout << "Make 1 chassis" << endl; std::this_thread::sleep_for(std::chrono::seconds(3)); semTire.release(); semTire.release(); semTire.release(); semTire.release(); } } int main() { auto thTireA = std::thread(&makeTire); auto thTireB = std::thread(&makeTire); auto thChassis = std::thread(&makeChassis); thTireA.join(); thTireB.join(); thChassis.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo21a01-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include #include #include using namespace std; std::mutex mut; std::condition_variable conditionVar; void foo() { cout << "foo is waiting..." << endl; std::unique_lock mutLock(mut); conditionVar.wait(mutLock); cout << "foo resumed" << endl; } void bar() { std::this_thread::sleep_for(std::chrono::seconds(3)); conditionVar.notify_one(); } int main() { auto thFoo = std::thread(&foo); auto thBar = std::thread(&bar); thFoo.join(); thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo21a02-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include #include #include using namespace std; std::mutex mut; std::condition_variable conditionVar; constexpr int NUM_TH_FOO = 3; void foo() { cout << "foo is waiting..." << endl; std::unique_lock mutLock(mut); conditionVar.wait(mutLock); cout << "foo resumed" << endl; } void bar() { for (int i = 0; i < NUM_TH_FOO; ++i) { std::this_thread::sleep_for(std::chrono::seconds(2)); conditionVar.notify_one(); } } int main() { std::thread lstThFoo[NUM_TH_FOO]; for (auto&& thFoo : lstThFoo) { thFoo = std::thread(&foo); } auto thBar = std::thread(&bar); for (auto&& thFoo : lstThFoo) { thFoo.join(); } thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo21a03-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include #include #include using namespace std; std::mutex mut; std::condition_variable conditionVar; constexpr int NUM_TH_FOO = 3; void foo() { cout << "foo is waiting..." << endl; std::unique_lock mutLock(mut); conditionVar.wait(mutLock); cout << "foo resumed" << endl; } void bar() { std::this_thread::sleep_for(std::chrono::seconds(3)); // Notify all waiting threads conditionVar.notify_all(); } int main() { std::thread lstThFoo[NUM_TH_FOO]; for (auto&& thFoo : lstThFoo) { thFoo = std::thread(&foo); } auto thBar = std::thread(&bar); for (auto&& thFoo : lstThFoo) { thFoo.join(); } thBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo21b-condition-variable.cpp ================================================ /* CONDITION VARIABLES */ #include #include #include #include using namespace std; std::mutex mut; std::condition_variable conditionVar; int counter = 0; constexpr int COUNT_HALT_01 = 3; constexpr int COUNT_HALT_02 = 6; constexpr int COUNT_DONE = 10; // Write numbers 1-3 and 8-10 as permitted by egg() void foo() { for (;;) { // Lock mutex and then wait for signal to relase mutex std::unique_lock lk(mut); // Wait while egg() operates on counter, // Mutex unlocked if condition variable in egg() signaled conditionVar.wait(lk); ++counter; cout << "foo counter = " << counter << endl; if (counter >= COUNT_DONE) { return; } } } // Write numbers 4-7 void egg() { for (;;) { std::unique_lock lk(mut); if (counter < COUNT_HALT_01 || counter > COUNT_HALT_02) { // Signal to free waiting thread by freeing the mutex // Note: foo() is now permitted to modify "counter" conditionVar.notify_one(); } else { ++counter; cout << "egg counter = " << counter << endl; } if (counter >= COUNT_DONE) { return; } } } int main() { auto thFoo = std::thread(&foo); auto thEgg = std::thread(&egg); thFoo.join(); thEgg.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo22a-blocking-queue.cpp ================================================ /* BLOCKING QUEUES Version A: A slow producer and a fast consumer Blocking queues in C++ std threading are not supported by default. So, I use mylib::BlockingQueue for this demonstration. */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkQueue) { std::this_thread::sleep_for(std::chrono::seconds(2)); blkQueue->put("Alice"); std::this_thread::sleep_for(std::chrono::seconds(2)); blkQueue->put("likes"); std::this_thread::sleep_for(std::chrono::seconds(2)); blkQueue->put("singing"); } void consumer(BlockingQueue* blkQueue) { string data; for (int i = 0; i < 3; ++i) { cout << "\nWaiting for data..." << endl; data = blkQueue->take(); cout << " " << data << endl; } } int main() { auto blkQueue = BlockingQueue(); auto thProducer = std::thread(&producer, &blkQueue); auto thConsumer = std::thread(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo22b-blocking-queue.cpp ================================================ /* BLOCKING QUEUES Version B: A fast producer and a slow consumer Blocking queues in C++ std threading are not supported by default. So, I use mylib::BlockingQueue for this demonstration. */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkQueue) { blkQueue->put("Alice"); blkQueue->put("likes"); /* Due to reaching the maximum capacity = 2, when executing blkQueue->put("singing"), this thread is going to sleep until the queue removes an element. */ blkQueue->put("singing"); } void consumer(BlockingQueue* blkQueue) { string data; std::this_thread::sleep_for(std::chrono::seconds(2)); for (int i = 0; i < 3; ++i) { cout << "\nWaiting for data..." << endl; data = blkQueue->take(); cout << " " << data << endl; } } int main() { auto blkQueue = BlockingQueue(2); // blocking queue with capacity = 2 auto thProducer = std::thread(&producer, &blkQueue); auto thConsumer = std::thread(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo23a01-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Introduction The basic way to use thread-local storage */ #include #include #include using namespace std; thread_local string value = "NOT SET"; void doTask() { cout << value << endl; } int main() { // Main thread sets value = "APPLE" value = "APPLE"; cout << value << endl; // Child thread gets value // Expected output: "NOT SET" auto th = std::thread(&doTask); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo23a02-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Introduction The smart-pointer way to use thread-local storage */ #include #include #include #include using namespace std; thread_local std::shared_ptr value; string getValue() { if (nullptr == value.get()) { value.reset(new string("NOT SET")); } return *value.get(); } void doTask() { cout << getValue() << endl; } int main() { // Main thread sets value = "APPLE" value.reset(new string("APPLE")); cout << getValue() << endl; // Child thread gets value // Expected output: "NOT SET" auto th = std::thread(&doTask); th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo23b-thread-local.cpp ================================================ /* THREAD-LOCAL STORAGE Avoiding synchronization using thread-local storage */ #include #include #include #include using namespace std; thread_local int counter = 0; void doTask(int t) { std::this_thread::sleep_for(std::chrono::seconds(1)); for (int i = 0; i < 1000; ++i) ++counter; cout << "Thread " << t << " gives counter = " << counter << endl; } int main() { constexpr int NUM_THREADS = 3; vector lstTh; for (int i = 0; i < NUM_THREADS; ++i) { lstTh.push_back(std::thread(&doTask, i)); } for (auto&& th : lstTh) { th.join(); } cout << endl; /* By using thread-local storage, each thread has its own counter. So, the counter in one thread is completely independent of each other. Thread-local storage helps us to AVOID SYNCHRONIZATION. */ return 0; } ================================================ FILE: cpp/cpp-std/demo24-volatile.cpp ================================================ /* THE VOLATILE KEYWORD */ #include #include #include using namespace std; volatile bool isRunning; void doTask() { while (isRunning) { cout << "Running..." << endl; std::this_thread::sleep_for(std::chrono::seconds(2)); } } int main() { isRunning = true; auto th = std::thread(&doTask); std::this_thread::sleep_for(std::chrono::seconds(6)); isRunning = false; th.join(); return 0; } ================================================ FILE: cpp/cpp-std/demo25a-atomic.cpp ================================================ /* ATOMIC ACCESS */ #include #include #include #include using namespace std; volatile int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); counter += 1; } int main() { counter = 0; vector lstTh; for (int i = 0; i < 1000; ++i) { lstTh.push_back(std::thread(&doTask)); } for (auto&& th : lstTh) { th.join(); } // Unpredictable result cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo25b-atomic.cpp ================================================ /* ATOMIC ACCESS */ #include #include #include #include #include using namespace std; // std::atomic counter; std::atomic_int32_t counter; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); counter += 1; } int main() { counter = 0; vector lstTh; for (int i = 0; i < 1000; ++i) { lstTh.push_back(std::thread(&doTask)); } for (auto&& th : lstTh) { th.join(); } // counter = 1000 cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-std/demo25c-atomic-gcc.cpp ================================================ /* ATOMIC ACCESS gcc builtins for atomic access Some functions: type __atomic_load_n (type *ptr, int memorder) void __atomic_store (type *ptr, type *val, int memorder) type __atomic_add_fetch (type *ptr, type val, int memorder) type __atomic_sub_fetch (type *ptr, type val, int memorder) type __atomic_fetch_add (type *ptr, type val, int memorder) type __atomic_fetch_sub (type *ptr, type val, int memorder) */ #include #include #include #include using namespace std; volatile int counter = 0; void doTask() { std::this_thread::sleep_for(std::chrono::seconds(1)); __atomic_add_fetch(&counter, 1, __ATOMIC_SEQ_CST); //__sync_add_and_fetch(&counter, 1); // Before C++11 } int main() { counter = 0; vector lstTh; for (int i = 0; i < 1000; ++i) { lstTh.push_back(std::thread(&doTask)); } for (auto&& th : lstTh) { th.join(); } // counter = 1000 cout << "counter = " << counter << endl; return 0; } ================================================ FILE: cpp/cpp-std/demoex-async-future.cpp ================================================ /* ASYNCHRONOUS PROGRAMMING WITH THE FUTURE */ #include #include #include int main() { // future from a packaged_task std::packaged_task task([]{ return 7; }); // wrap the function std::future fut1 = task.get_future(); // get a future std::thread th(std::move(task)); // launch on a thread // future from an async() std::future fut2 = std::async(std::launch::async, []{ return 8; }); // future from a promise std::promise prom; std::future fut3 = prom.get_future(); std::thread( [&prom]{ prom.set_value_at_thread_exit(9); }).detach(); std::cout << "Waiting..." << std::endl; fut1.wait(); fut2.wait(); fut3.wait(); th.join(); std::cout << "Done!" << std::endl; std::cout << "Results are: " << fut1.get() << ' ' << fut2.get() << ' ' << fut3.get() << std::endl; return 0; } ================================================ FILE: cpp/cpp-std/demoex-jthread.cpp ================================================ /* JTHREAD std::jthread has the same general behavior as std::thread, except that jthread automatically rejoins on destruction, and can be cancelled/stopped in certain situations. (From https://en.cppreference.com/w/cpp/thread/jthread) */ #include #include #include void sumIntegers(int a, int b) { int t = a + b; std::cout << "Sum: " << t << std::endl; } void iterateValues(std::stop_token stopTok, int startValue, int endValue) { int i = startValue; for (; i < endValue; ++i) { if (stopTok.stop_requested()) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } std::cout << "End of function, i = " << i << std::endl; } int main() { // DEMO 01: Use std::jthread just like normal std::thread std::cout << "DEMO 01:" << std::endl; { std::jthread thSumInt(&sumIntegers, 100, -30); std::this_thread::sleep_for(std::chrono::seconds(2)); } // DEMO 02: Pass the function that takes a std::stop_token as its first argument std::cout << "\nDEMO 02:" << std::endl; { std::jthread thIter(&iterateValues, 0, 1'000'000); std::this_thread::sleep_for(std::chrono::seconds(2)); thIter.request_stop(); // or thIter.get_stop_source().request_stop(); std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "End of program" << std::endl; } // No need to join thSumInt and thIter, because they auto-join on destruction return 0; } ================================================ FILE: cpp/cpp-std/exer01a-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include "mylib-time.hpp" using namespace std; int main() { constexpr int RANGE_START = 1; constexpr int RANGE_END = 100000; int resValue = 0; int resNumDiv = 0; // number of divisors of result auto tpStart = mylib::HiResClock::now(); for (int i = RANGE_START; i <= RANGE_END; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } auto timeElapsed = mylib::HiResClock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << resValue << endl; cout << "The largest number of divisor is " << resNumDiv << endl; cout << "Time elapsed = " << timeElapsed.count() << endl; return 0; } ================================================ FILE: cpp/cpp-std/exer01b-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include #include #include #include "mylib-time.hpp" using namespace std; struct WorkerArg { int iStart; int iEnd; WorkerArg(int iStart = 0, int iEnd = 0): iStart(iStart), iEnd(iEnd) { } }; struct WorkerResult { int value; int numDiv; WorkerResult(int value = 0, int numDiv = 0): value(value), numDiv(numDiv) { } }; void workerFunc(WorkerArg* arg, WorkerResult* res) { int resValue = 0; int resNumDiv = 0; for (int i = arg->iStart; i <= arg->iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } (*res) = WorkerResult(resValue, resNumDiv); } void prepare( int rangeStart, int rangeEnd, int numThreads, vector& lstTh, vector& lstWorkerArg, vector& lstWorkerRes ) { lstTh.resize(numThreads); lstWorkerArg.resize(numThreads); lstWorkerRes.resize(numThreads); int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg[i] = WorkerArg(rangeA, rangeB); } } int main() { constexpr int RANGE_START = 1; constexpr int RANGE_END = 100000; constexpr int NUM_THREADS = 8; vector lstTh; vector lstWorkerArg; vector lstWorkerRes; prepare(RANGE_START, RANGE_END, NUM_THREADS, lstTh, lstWorkerArg, lstWorkerRes); auto tpStart = mylib::HiResClock::now(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh[i] = std::thread(&workerFunc, &lstWorkerArg[i], &lstWorkerRes[i]); } for (auto&& th : lstTh) { th.join(); } // for (auto&& res: lstWorkerRes) { // cout << res.value << " " << res.numDiv << endl; // } auto finalRes = *max_element(lstWorkerRes.begin(), lstWorkerRes.end(), [](const WorkerResult &lhs, const WorkerResult &rhs) -> bool { return lhs.numDiv < rhs.numDiv; } ); auto timeElapsed = mylib::HiResClock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << finalRes.value << endl; cout << "The largest number of divisor is " << finalRes.numDiv << endl; cout << "Time elapsed = " << timeElapsed.count() << endl; return 0; } ================================================ FILE: cpp/cpp-std/exer01c-max-div.cpp ================================================ /* MAXIMUM NUMBER OF DIVISORS */ #include #include #include #include #include #include "mylib-time.hpp" using namespace std; struct WorkerArg { int iStart; int iEnd; WorkerArg(int iStart = 0, int iEnd = 0): iStart(iStart), iEnd(iEnd) { } }; class FinalResult { public: int value = 0; int numDiv = 0; private: std::mutex mut; public: void update(int value, int numDiv) { // Synchronize whole function std::unique_lock lk(mut); if (this->numDiv < numDiv) { this->numDiv = numDiv; this->value = value; } } }; void workerFunc(WorkerArg* arg, FinalResult* res) { int resValue = 0; int resNumDiv = 0; for (int i = arg->iStart; i <= arg->iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } res->update(resValue, resNumDiv); } void prepare( int rangeStart, int rangeEnd, int numThreads, vector& lstTh, vector& lstWorkerArg ) { lstTh.resize(numThreads); lstWorkerArg.resize(numThreads); int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg[i] = WorkerArg(rangeA, rangeB); } } int main() { constexpr int RANGE_START = 1; constexpr int RANGE_END = 100000; constexpr int NUM_THREADS = 8; vector lstTh; vector lstWorkerArg; FinalResult finalRes; prepare(RANGE_START, RANGE_END, NUM_THREADS, lstTh, lstWorkerArg); auto tpStart = mylib::HiResClock::now(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh[i] = std::thread(&workerFunc, &lstWorkerArg[i], &finalRes); } for (auto&& th : lstTh) { th.join(); } auto timeElapsed = mylib::HiResClock::getTimeSpan(tpStart); cout << "The integer which has largest number of divisors is " << finalRes.value << endl; cout << "The largest number of divisor is " << finalRes.numDiv << endl; cout << "Time elapsed = " << timeElapsed.count() << endl; return 0; } ================================================ FILE: cpp/cpp-std/exer02a01-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A01: 1 slow producer, 1 fast consumer */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq) { int i = 1; for (;; ++i) { blkq->put(i); std::this_thread::sleep_for(std::chrono::seconds(1)); } } void consumer(BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; } } int main() { auto blkq = BlockingQueue(); auto thProducer = std::thread(&producer, &blkq); auto thConsumer = std::thread(&consumer, &blkq); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer02a02-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A02: 2 slow producers, 1 fast consumer */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq) { int i = 1; for (;; ++i) { blkq->put(i); std::this_thread::sleep_for(std::chrono::seconds(1)); } } void consumer(BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; } } int main() { auto blkq = BlockingQueue(); auto thProducerA = std::thread(&producer, &blkq); auto thProducerB = std::thread(&producer, &blkq); auto thConsumer = std::thread(&consumer, &blkq); thProducerA.join(); thProducerB.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer02a03-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A03: 1 slow producer, 2 fast consumers */ #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq) { int i = 1; for (;; ++i) { blkq->put(i); std::this_thread::sleep_for(std::chrono::seconds(1)); } } void consumer(string name, BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << name << ": " << data << endl; } } int main() { auto blkq = BlockingQueue(); auto thProducer = std::thread(&producer, &blkq); auto thConsumerFoo = std::thread(&consumer, "foo", &blkq); auto thConsumerBar = std::thread(&consumer, "bar", &blkq); thProducer.join(); thConsumerFoo.join(); thConsumerBar.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer02a04-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A04: Multiple fast producers, multiple slow consumers */ #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; using namespace mylib; void producer(BlockingQueue* blkq, int startValue) { int i = 1; for (;; ++i) { blkq->put(i + startValue); } } void consumer(BlockingQueue* blkq) { int data = 0; for (;;) { data = blkq->take(); cout << "Consumer " << data << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { auto blkq = BlockingQueue(5); constexpr int NUM_PRODUCERS = 3; constexpr int NUM_CONSUMERS = 2; std::thread lstThProducer[NUM_PRODUCERS]; std::thread lstThConsumer[NUM_CONSUMERS]; // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstThProducer[i] = std::thread(&producer, &blkq, i * 1000); } for (auto&& th : lstThConsumer) { th = std::thread(&consumer, &blkq); } // JOIN THREADS for (auto&& th : lstThProducer) { th.join(); } for (auto&& th : lstThConsumer) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/exer02b01-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B01: 1 slow producer, 1 fast consumer */ #include #include #include #include #include using namespace std; using cntsemaphore = std::counting_semaphore<>; void producer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i); std::this_thread::sleep_for(std::chrono::seconds(1)); semFill->release(); } } void consumer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; semEmpty->release(); } } int main() { cntsemaphore semFill(0); // item produced cntsemaphore semEmpty(1); // remaining space in queue queue q; auto thProducer = std::thread(&producer, &semFill, &semEmpty, &q); auto thConsumer = std::thread(&consumer, &semFill, &semEmpty, &q); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer02b02-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B02: 2 slow producers, 1 fast consumer */ #include #include #include #include #include using namespace std; using cntsemaphore = std::counting_semaphore<>; void producer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q, int startValue ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i + startValue); std::this_thread::sleep_for(std::chrono::seconds(1)); semFill->release(); } } void consumer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; semEmpty->release(); } } int main() { cntsemaphore semFill(0); // item produced cntsemaphore semEmpty(1); // remaining space in queue queue q; auto thProducerA = std::thread(&producer, &semFill, &semEmpty, &q, 0); auto thProducerB = std::thread(&producer, &semFill, &semEmpty, &q, 1000); auto thConsumer = std::thread(&consumer, &semFill, &semEmpty, &q); thProducerA.join(); thProducerB.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer02b03-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B03: 2 fast producers, 1 slow consumer */ #include #include #include #include #include using namespace std; using cntsemaphore = std::counting_semaphore<>; void producer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q, int startValue ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i + startValue); semFill->release(); } } void consumer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); semEmpty->release(); } } int main() { cntsemaphore semFill(0); // item produced cntsemaphore semEmpty(1); // remaining space in queue queue q; auto thProducerA = std::thread(&producer, &semFill, &semEmpty, &q, 0); auto thProducerB = std::thread(&producer, &semFill, &semEmpty, &q, 1000); auto thConsumer = std::thread(&consumer, &semFill, &semEmpty, &q); thProducerA.join(); thProducerB.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer02b04-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B04: Multiple fast producers, multiple slow consumers */ #include #include #include #include #include using namespace std; using cntsemaphore = std::counting_semaphore<>; void producer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q, int startValue ) { int i = 1; for (;; ++i) { semEmpty->acquire(); q->push(i + startValue); semFill->release(); } } void consumer( cntsemaphore* semFill, cntsemaphore* semEmpty, queue* q ) { int data = 0; for (;;) { semFill->acquire(); data = q->front(); q->pop(); cout << "Consumer " << data << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); semEmpty->release(); } } int main() { cntsemaphore semFill(0); // item produced cntsemaphore semEmpty(1); // remaining space in queue queue q; constexpr int NUM_PRODUCERS = 3; constexpr int NUM_CONSUMERS = 2; std::thread lstThProducer[NUM_PRODUCERS]; std::thread lstThConsumer[NUM_CONSUMERS]; // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstThProducer[i] = std::thread(&producer, &semFill, &semEmpty, &q, i * 1000); } for (auto&& th : lstThConsumer) { th = std::thread(&consumer, &semFill, &semEmpty, &q); } // JOIN THREADS for (auto&& th : lstThProducer) { th.join(); } for (auto&& th : lstThConsumer) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/exer02c-producer-consumer.cpp ================================================ /* THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE C: USING CONDITION VARIABLES & MONITORS Multiple fast producers, multiple slow consumers */ #include #include #include #include #include #include using namespace std; template class Monitor { private: std::queue* q = nullptr; int maxQueueSize = 0; std::condition_variable condFull; std::condition_variable condEmpty; std::mutex mut; public: Monitor() = default; Monitor(const Monitor& other) = delete; Monitor(const Monitor&& other) = delete; void operator=(const Monitor& other) = delete; void operator=(const Monitor&& other) = delete; void init(int maxQueueSize, std::queue* q) { this->q = q; this->maxQueueSize = maxQueueSize; } void add(const T& item) { std::unique_lock mutLock(mut); while (q->size() == maxQueueSize) { condFull.wait(mutLock); } q->push(item); if (q->size() == 1) { condEmpty.notify_one(); } // mutLock.unlock(); } T remove() { std::unique_lock mutLock(mut); while (q->size() == 0) { condEmpty.wait(mutLock); } T item = q->front(); q->pop(); if (q->size() == maxQueueSize - 1) { condFull.notify_one(); } // mutLock.unlock(); return item; } }; template void producer(Monitor* monitor, int startValue) { T i = 1; for (;; ++i) { monitor->add(i + startValue); } } template void consumer(Monitor* monitor) { T data; for (;;) { data = monitor->remove(); cout << "Consumer " << data << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { Monitor monitor; queue q; constexpr int MAX_QUEUE_SIZE = 6; constexpr int NUM_PRODUCERS = 3; constexpr int NUM_CONSUMERS = 2; std::thread lstThProducer[NUM_PRODUCERS]; std::thread lstThConsumer[NUM_CONSUMERS]; // PREPARE ARGUMENTS monitor.init(MAX_QUEUE_SIZE, &q); // CREATE THREADS for (int i = 0; i < NUM_PRODUCERS; ++i) { lstThProducer[i] = std::thread(&producer, &monitor, i * 1000); } for (auto&& th : lstThConsumer) { th = std::thread(&consumer, &monitor); } // JOIN THREADS for (auto&& th : lstThProducer) { th.join(); } for (auto&& th : lstThConsumer) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/exer03a-readers-writers.cpp ================================================ /* THE READERS-WRITERS PROBLEM Solution for the first readers-writers problem */ #include #include #include #include #include "mylib-random.hpp" using namespace std; struct GlobalData { volatile int resource = 0; int readerCount = 0; std::mutex mutResource; std::mutex mutReaderCount; }; void doTaskWriter(GlobalData* g, int delayTime) { std::this_thread::sleep_for(std::chrono::seconds(delayTime)); g->mutResource.lock(); g->resource = mylib::RandInt::get(100); cout << "Write " << g->resource << endl; g->mutResource.unlock(); } void doTaskReader(GlobalData* g, int delayTime) { std::this_thread::sleep_for(std::chrono::seconds(delayTime)); // Increase reader count g->mutReaderCount.lock(); g->readerCount += 1; if (1 == g->readerCount) g->mutResource.lock(); g->mutReaderCount.unlock(); // Do the reading cout << "Read " << g->resource << endl; // Decrease reader count g->mutReaderCount.lock(); g->readerCount -= 1; if (0 == g->readerCount) g->mutResource.unlock(); g->mutReaderCount.unlock(); } int main() { GlobalData globalData; constexpr int NUM_READERS = 8; constexpr int NUM_WRITERS = 6; std::thread lstThReader[NUM_READERS]; std::thread lstThWriter[NUM_WRITERS]; // CREATE THREADS for (auto&& th: lstThReader) { th = std::thread(&doTaskReader, &globalData, mylib::RandInt::get(3)); } for (auto&& th: lstThWriter) { th = std::thread(&doTaskWriter, &globalData, mylib::RandInt::get(3)); } // JOIN THREADS for (auto&& th: lstThReader) { th.join(); } for (auto&& th: lstThWriter) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/exer03b-readers-writers.cpp ================================================ /* THE READERS-WRITERS PROBLEM Solution for the third readers-writers problem */ #include #include #include #include #include "mylib-random.hpp" using namespace std; struct GlobalData { volatile int resource = 0; int readerCount = 0; std::mutex mutResource; std::mutex mutReaderCount; std::mutex mutServiceQueue; }; void doTaskWriter(GlobalData* g, int delayTime) { std::this_thread::sleep_for(std::chrono::seconds(delayTime)); g->mutServiceQueue.lock(); g->mutResource.lock(); g->mutServiceQueue.unlock(); g->resource = mylib::RandInt::get(100); cout << "Write " << g->resource << endl; g->mutResource.unlock(); } void doTaskReader(GlobalData* g, int delayTime) { std::this_thread::sleep_for(std::chrono::seconds(delayTime)); g->mutServiceQueue.lock(); // Increase reader count g->mutReaderCount.lock(); g->readerCount += 1; if (1 == g->readerCount) g->mutResource.lock(); g->mutReaderCount.unlock(); g->mutServiceQueue.unlock(); // Do the reading cout << "Read " << g->resource << endl; // Decrease reader count g->mutReaderCount.lock(); g->readerCount -= 1; if (0 == g->readerCount) g->mutResource.unlock(); g->mutReaderCount.unlock(); } int main() { GlobalData globalData; constexpr int NUM_READERS = 8; constexpr int NUM_WRITERS = 6; std::thread lstThReader[NUM_READERS]; std::thread lstThWriter[NUM_WRITERS]; // CREATE THREADS for (auto&& th: lstThReader) { th = std::thread(&doTaskReader, &globalData, mylib::RandInt::get(3)); } for (auto&& th: lstThWriter) { th = std::thread(&doTaskWriter, &globalData, mylib::RandInt::get(3)); } // JOIN THREADS for (auto&& th: lstThReader) { th.join(); } for (auto&& th: lstThWriter) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/exer04-dining-philosophers.cpp ================================================ /* THE DINING PHILOSOPHERS PROBLEM */ #include #include #include #include using namespace std; void doTaskPhilosopher(std::mutex chopstick[], int numPhilo, int idPhilo) { int n = numPhilo; int i = idPhilo; std::this_thread::sleep_for(std::chrono::seconds(1)); chopstick[i].lock(); chopstick[(i + 1) % n].lock(); cout << "Philosopher #" << i << " is eating the rice" << endl; chopstick[(i + 1) % n].unlock(); chopstick[i].unlock(); } void doTaskPhilosopherUsingSyncBlock(std::mutex chopstick[], int numPhilo, int idPhilo) { int n = numPhilo; int i = idPhilo; std::this_thread::sleep_for(std::chrono::seconds(1)); { std::unique_lock ( chopstick[i] ); std::unique_lock ( chopstick[(i + 1) % n] ); cout << "Philosopher #" << i << " is eating the rice" << endl; } } int main() { constexpr int NUM_PHILOSOPHERS = 5; std::mutex chopstick[NUM_PHILOSOPHERS]; std::thread lstTh[NUM_PHILOSOPHERS]; // CREATE THREADS for (int i = 0; i < NUM_PHILOSOPHERS; ++i) { lstTh[i] = std::thread(&doTaskPhilosopher, chopstick, NUM_PHILOSOPHERS, i); } // JOIN THREADS for (auto&& th : lstTh) { th.join(); } return 0; } ================================================ FILE: cpp/cpp-std/exer05-util.hpp ================================================ #ifndef _EXER05_UTIL_HPP_ #define _EXER05_UTIL_HPP_ void getScalarProduct(double const* u, double const* v, int sizeVector, double* res) { double sum = 0; for (int i = sizeVector - 1; i >= 0; --i) { sum += u[i] * v[i]; } (*res) = sum; } #endif // _EXER05_UTIL_HPP_ ================================================ FILE: cpp/cpp-std/exer05a-product-matrix-vector.cpp ================================================ /* MATRIX-VECTOR MULTIPLICATION */ #include #include #include #include "exer05-util.hpp" using namespace std; using vectord = std::vector; using matrix = std::vector; void getProduct(const matrix& mat, const vectord& vec, vectord& result) { // Assume that size of mat and vec are both eligible int sizeRowMat = mat.size(); int sizeColMat = mat[0].size(); int sizeVec = vec.size(); result.clear(); result.resize(sizeRowMat, 0); std::vector lstTh(sizeRowMat); for (int i = 0; i < sizeRowMat; ++i) { auto&& u = mat[i].data(); auto&& v = vec.data(); lstTh[i] = std::thread(&getScalarProduct, u, v, sizeVec, &result[i]); } for (auto&& th : lstTh) { th.join(); } } int main() { matrix A = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; vectord b = { 3, -1, 0 }; vectord result; getProduct(A, b, result); for (int i = 0; i < result.size(); ++i) { cout << result[i] << endl; } return 0; } ================================================ FILE: cpp/cpp-std/exer05b-product-matrix-matrix.cpp ================================================ /* MATRIX-MATRIX MULTIPLICATION (DOT PRODUCT) */ #include #include #include #include "exer05-util.hpp" using namespace std; using vectord = std::vector; using matrix = std::vector; void getTransposeMatrix(const matrix& input, matrix& output) { int numRow = input.size(); int numCol = input[0].size(); output.clear(); output.assign(numCol, vectord(numRow, 0)); for (int i = 0; i < numRow; ++i) for (int j = 0; j < numCol; ++j) output[j][i] = input[i][j]; } void displayMatrix(const matrix& mat) { int numRow = mat.size(); int numCol = mat[0].size(); for (int i = 0; i < numRow; ++i) { for (int j = 0; j < numCol; ++j) cout << "\t" << mat[i][j]; cout << endl; } } void getProduct(const matrix& matA, const matrix& matB, matrix& result) { // Assume that size of matA and matB are both eligible int sizeRowA = matA.size(); int sizeColA = matA[0].size(); int sizeColB = matB[0].size(); int sizeTotal = sizeRowA * sizeColB; result.clear(); result.assign(sizeRowA, vectord(sizeColB, 0)); matrix matBT; getTransposeMatrix(matB, matBT); vector lstTh(sizeTotal); int iSca = 0; for (int i = 0; i < sizeRowA; ++i) { for (int j = 0; j < sizeColB; ++j) { auto&& u = matA[i].data(); auto&& v = matBT[j].data(); auto&& sizeVector = sizeColA; lstTh[iSca] = std::thread(&getScalarProduct, u, v, sizeVector, &result[i][j]); ++iSca; } } for (auto&& th : lstTh) { th.join(); } } int main() { matrix A = { { 1, 3, 5 }, { 2, 4, 6 }, }; matrix B = { { 1, 0, 1, 0 }, { 0, 1, 0, 1 }, { 1, 0, 0, -2 } }; matrix result; getProduct(A, B, result); displayMatrix(result); return 0; } ================================================ FILE: cpp/cpp-std/exer06a-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version A: Synchronous queues */ #include #include #include #include #include using namespace std; using cntsemaphore = std::counting_semaphore<>; template class SynchronousQueue { private: cntsemaphore semPut = cntsemaphore(1); cntsemaphore semTake = cntsemaphore(0); T element; public: void put(const T& value) { semPut.acquire(); element = value; semTake.release(); } T take() { semTake.acquire(); T result = element; semPut.release(); return result; } }; void producer(SynchronousQueue* syncQueue) { auto arr = { "lorem", "ipsum", "dolor" }; for (auto&& data : arr) { cout << "Producer: " << data << endl; syncQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } } void consumer(SynchronousQueue* syncQueue) { std::string data; std::this_thread::sleep_for(std::chrono::seconds(5)); for (int i = 0; i < 3; ++i) { data = syncQueue->take(); cout << "\tConsumer: " << data << endl; } } int main() { SynchronousQueue syncQueue; auto thProducer = std::thread(&producer, &syncQueue); auto thConsumer = std::thread(&consumer, &syncQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer06b01-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version B01: General blocking queues Underlying mechanism: Semaphores */ #include #include #include #include #include #include #include #include using namespace std; using cntsemaphore = std::counting_semaphore<>; template class BlockingQueue { private: int capacity; cntsemaphore semRemain; cntsemaphore semFill; std::mutex mut; std::queue q; public: BlockingQueue(int capacity) : capacity(capacity), semRemain(capacity), semFill(0) { if (this->capacity <= 0) throw std::invalid_argument("capacity must be a positive integer"); } void put(const T& value) { semRemain.acquire(); { std::unique_lock lk(mut); q.push(value); } semFill.release(); } T take() { T result; semFill.acquire(); { std::unique_lock lk(mut); result = q.front(); q.pop(); } semRemain.release(); return result; } }; void producer(BlockingQueue* blkQueue) { auto arr = { "nice", "to", "meet", "you" }; for (auto&& data : arr) { cout << "Producer: " << data << endl; blkQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } } void consumer(BlockingQueue* blkQueue) { std::string data; std::this_thread::sleep_for(std::chrono::seconds(5)); for (int i = 0; i < 4; ++i) { data = blkQueue->take(); cout << "\tConsumer: " << data << endl; if (0 == i) std::this_thread::sleep_for(std::chrono::seconds(5)); } } int main() { BlockingQueue blkQueue(2); // capacity = 2 auto thProducer = std::thread(&producer, &blkQueue); auto thConsumer = std::thread(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer06b02-blocking-queue.cpp ================================================ /* BLOCKING QUEUE IMPLEMENTATION Version B02: General blocking queues Underlying mechanism: Condition variables */ #include #include #include #include #include #include #include #include using namespace std; template class BlockingQueue { private: std::condition_variable condEmpty; std::condition_variable condFull; std::mutex mut; int capacity = 0; std::queue q; public: BlockingQueue(int capacity) { if (capacity <= 0) throw std::invalid_argument("capacity must be a positive integer"); this->capacity = capacity; } void put(const T& value) { { std::unique_lock lk(mut); while ((int)q.size() >= capacity) { // Queue is full, must wait for 'take' condFull.wait(lk); } q.push(value); } condEmpty.notify_one(); } T take() { T result; { std::unique_lock lk(mut); while (q.empty()) { // Queue is empty, must wait for 'put' condEmpty.wait(lk); } result = q.front(); q.pop(); } condFull.notify_one(); return result; } }; void producer(BlockingQueue* blkQueue) { auto arr = { "nice", "to", "meet", "you" }; for (auto&& data : arr) { cout << "Producer: " << data << endl; blkQueue->put(data); cout << "Producer: " << data << "\t\t\t[done]" << endl; } } void consumer(BlockingQueue* blkQueue) { std::string data; std::this_thread::sleep_for(std::chrono::seconds(5)); for (int i = 0; i < 4; ++i) { data = blkQueue->take(); cout << "\tConsumer: " << data << endl; if (0 == i) std::this_thread::sleep_for(std::chrono::seconds(5)); } } int main() { BlockingQueue blkQueue(2); // capacity = 2 auto thProducer = std::thread(&producer, &blkQueue); auto thConsumer = std::thread(&consumer, &blkQueue); thProducer.join(); thConsumer.join(); return 0; } ================================================ FILE: cpp/cpp-std/exer07a-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version A: Solving the problem using a condition variable */ #include #include #include #include #include #include #include using namespace std; #define sleepsec(secs) \ do { std::this_thread::sleep_for(std::chrono::seconds(secs)); } while (0) struct Counter { int value; std::mutex mut; std::condition_variable cond; Counter(int value) : value(value) { } }; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, Counter& counter) { for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; { std::unique_lock(counter.mut); --counter.value; counter.cond.notify_one(); } // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; Counter counter(lstFileName.size()); // The server checks auth user while reading files, concurrently std::thread th(&processFiles, std::cref(lstFileName), std::ref(counter)); checkAuthUser(); // The server waits for completion of loading files { std::unique_lock lk(counter.mut); while (counter.value > 0) { counter.cond.wait_for(lk, std::chrono::seconds(10)); // timeout = 10 seconds } } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-std/exer07b-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version B: Solving the problem using a semaphore */ #include #include #include #include #include #include using namespace std; #define sleepsec(secs) \ do { std::this_thread::sleep_for(std::chrono::seconds(secs)); } while (0) using cntsemaphore = std::counting_semaphore<>; void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, cntsemaphore& sem) { for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; sem.release(); // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; cntsemaphore sem(0); // The server checks auth user while reading files, concurrently std::thread th(&processFiles, std::cref(lstFileName), std::ref(sem)); checkAuthUser(); // The server waits for completion of loading files for (size_t i = lstFileName.size(); i > 0; --i) { sem.acquire(); } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-std/exer07c-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version C: Solving the problem using a count-down latch */ #include #include #include #include #include #include using namespace std; #define sleepsec(secs) \ do { std::this_thread::sleep_for(std::chrono::seconds(secs)); } while (0) void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, std::latch& rdLatch) { for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; rdLatch.count_down(); // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; std::latch readFileLatch(lstFileName.size()); // The server checks auth user while reading files, concurrently std::thread th(&processFiles, std::cref(lstFileName), std::ref(readFileLatch)); checkAuthUser(); // The server waits for completion of loading files readFileLatch.wait(); cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-std/exer07d-data-server.cpp ================================================ /* THE DATA SERVER PROBLEM Version D: Solving the problem using a blocking queue */ #include #include #include #include #include #include "mylib-blockingqueue.hpp" using namespace std; #define sleepsec(secs) \ do { std::this_thread::sleep_for(std::chrono::seconds(secs)); } while (0) void checkAuthUser() { cout << "[ Auth ] Start" << endl; // Send request to authenticator, check permissions, encrypt, decrypt... sleepsec(20); cout << "[ Auth ] Done" << endl; } void processFiles(const vector& lstFileName, mylib::BlockingQueue& blkq) { for (auto&& fileName : lstFileName) { // Read file cout << "[ ReadFile ] Start " << fileName << endl; sleepsec(10); cout << "[ ReadFile ] Done " << fileName << endl; blkq.put(fileName); // You may put file data here // Write log into disk sleepsec(5); cout << "[ WriteLog ]" << endl; } } void processRequest() { const vector lstFileName = { "foo.html", "bar.json" }; mylib::BlockingQueue blkq; // The server checks auth user while reading files, concurrently std::thread th(&processFiles, std::cref(lstFileName), std::ref(blkq)); checkAuthUser(); // The server waits for completion of loading files for (size_t i = lstFileName.size(); i > 0; --i) { blkq.take(); } cout << "\nNow user is authorized and files are loaded" << endl; cout << "Do other tasks...\n" << endl; th.join(); } int main() { processRequest(); return 0; } ================================================ FILE: cpp/cpp-std/exer08-exec-service-itask.hpp ================================================ #ifndef _MY_EXEC_SERVICE_ITASK_HPP_ #define _MY_EXEC_SERVICE_ITASK_HPP_ // interface ITask class ITask { public: virtual ~ITask() = default; virtual void run() = 0; }; #endif // _MY_EXEC_SERVICE_ITASK_HPP_ ================================================ FILE: cpp/cpp-std/exer08-exec-service-main.cpp ================================================ /* EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION */ #include #include #include "exer08-exec-service-itask.hpp" #include "exer08-exec-service-v0a.hpp" #include "exer08-exec-service-v0b.hpp" #include "exer08-exec-service-v1a.hpp" #include "exer08-exec-service-v1b.hpp" #include "exer08-exec-service-v2a.hpp" #include "exer08-exec-service-v2b.hpp" class MyTask : public ITask { public: char id; public: void run() override { std::cout << "Task " << id << " is starting" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "Task " << id << " is completed" << std::endl; } }; int main() { constexpr int NUM_THREADS = 2; constexpr int NUM_TASKS = 5; MyExecServiceV0A execService(NUM_THREADS); auto lstTask = std::vector(NUM_TASKS); for (int i = 0; i < NUM_TASKS; ++i) lstTask[i].id = 'A' + i; for (auto&& task : lstTask) execService.submit(&task); std::cout << "All tasks are submitted" << std::endl; execService.waitTaskDone(); std::cout << "All tasks are completed" << std::endl; execService.shutdown(); return 0; } ================================================ FILE: cpp/cpp-std/exer08-exec-service-v0a.hpp ================================================ /* MY EXECUTOR SERVICE Version 0A: The easiest executor service - It uses a blocking queue as underlying mechanism. */ #ifndef _MY_EXEC_SERVICE_V0A_HPP_ #define _MY_EXEC_SERVICE_V0A_HPP_ #include #include #include #include #include "mylib-blockingqueue.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV0A { private: int numThreads = 0; std::vector lstTh; mylib::BlockingQueue taskPending; public: MyExecServiceV0A(int numThreads) { init(numThreads); } MyExecServiceV0A(const MyExecServiceV0A& other) = delete; MyExecServiceV0A(const MyExecServiceV0A&& other) = delete; void operator=(const MyExecServiceV0A& other) = delete; void operator=(const MyExecServiceV0A&& other) = delete; private: void init(int numThreads) { this->numThreads = numThreads; lstTh.resize(numThreads); for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(ITask* task) { taskPending.add(task); } void waitTaskDone() { // This ExecService is too simple, // so there is no implementation for waitTaskDone() std::this_thread::sleep_for(std::chrono::seconds(11)); // fake behaviour } void shutdown() { // This ExecService is too simple, // so there is no implementation for shutdown() std::cout << "No implementation for shutdown()." << std::endl; std::cout << "You need to exit the app manually." << std::endl; } private: static void threadWorkerFunc(MyExecServiceV0A* thisPtr) { auto&& taskPending = thisPtr->taskPending; ITask* task = nullptr; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK task = taskPending.take(); // DO THE TASK task->run(); } } }; #endif // _MY_EXEC_SERVICE_V0A_HPP_ ================================================ FILE: cpp/cpp-std/exer08-exec-service-v0b.hpp ================================================ /* MY EXECUTOR SERVICE Version 0B: The easiest executor service - It uses a blocking queue as underlying mechanism. - It supports waitTaskDone() and shutdown(). */ #ifndef _MY_EXEC_SERVICE_V0B_HPP_ #define _MY_EXEC_SERVICE_V0B_HPP_ #include #include #include #include #include #include "mylib-blockingqueue.hpp" #include "exer08-exec-service-itask.hpp" class MyExecServiceV0B { private: int numThreads = 0; std::vector lstTh; mylib::BlockingQueue taskPending; std::atomic_int32_t counterTaskRunning; volatile bool forceThreadShutdown; const class : ITask { void run() override { } } emptyTask; public: MyExecServiceV0B(int numThreads) { init(numThreads); } MyExecServiceV0B(const MyExecServiceV0B& other) = delete; MyExecServiceV0B(const MyExecServiceV0B&& other) = delete; void operator=(const MyExecServiceV0B& other) = delete; void operator=(const MyExecServiceV0B&& other) = delete; private: void init(int numThreads) { this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(ITask* task) { taskPending.add(task); } void waitTaskDone() { // This ExecService is too simple, // so there is no good implementation for waitTaskDone() while (false == taskPending.empty() || counterTaskRunning > 0) { std::this_thread::sleep_for(std::chrono::seconds(1)); // std::this_thread::yield(); } } void shutdown() { forceThreadShutdown = true; taskPending.clear(); // Invoke blocked threads by adding "empty" tasks for (int i = 0; i < numThreads; ++i) { taskPending.put( (ITask* const) &emptyTask ); } for (auto&& th : lstTh) { th.join(); } numThreads = 0; lstTh.clear(); } private: static void threadWorkerFunc(MyExecServiceV0B* thisPtr) { auto&& taskPending = thisPtr->taskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK task = taskPending.take(); // If shutdown() was called, then exit the function if (forceThreadShutdown) { break; } // DO THE TASK ++counterTaskRunning; task->run(); --counterTaskRunning; } } }; #endif // _MY_EXEC_SERVICE_V0B_HPP_ ================================================ FILE: cpp/cpp-std/exer08-exec-service-v1a.hpp ================================================ /* MY EXECUTOR SERVICE Version 1A: Simple executor service - Method "waitTaskDone" invokes thread sleeps in loop (which can cause performance problems). */ #ifndef _MY_EXEC_SERVICE_V1A_HPP_ #define _MY_EXEC_SERVICE_V1A_HPP_ #include #include #include #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV1A { private: using uniquelk = std::unique_lock; private: int numThreads = 0; std::vector lstTh; std::queue taskPending; std::mutex mutTaskPending; std::condition_variable condTaskPending; std::atomic_int32_t counterTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV1A(int numThreads) { init(numThreads); } MyExecServiceV1A(const MyExecServiceV1A& other) = delete; MyExecServiceV1A(const MyExecServiceV1A&& other) = delete; void operator=(const MyExecServiceV1A& other) = delete; void operator=(const MyExecServiceV1A&& other) = delete; private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { bool done = false; for (;;) { { uniquelk lk(mutTaskPending); if (taskPending.empty() && 0 == counterTaskRunning) { done = true; } } if (done) { break; } std::this_thread::sleep_for(std::chrono::seconds(1)); // std::this_thread::yield(); } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); } condTaskPending.notify_all(); for (auto&& th : lstTh) { th.join(); } numThreads = 0; lstTh.clear(); } private: static void threadWorkerFunc(MyExecServiceV1A* thisPtr) { auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; } // DO THE TASK task->run(); --counterTaskRunning; } } }; #endif // _MY_EXEC_SERVICE_V1A_HPP_ ================================================ FILE: cpp/cpp-std/exer08-exec-service-v1b.hpp ================================================ /* MY EXECUTOR SERVICE Version 1B: Simple executor service - Method "waitTaskDone" uses a condition variable to synchronize. */ #ifndef _MY_EXEC_SERVICE_V1B_HPP_ #define _MY_EXEC_SERVICE_V1B_HPP_ #include #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV1B { private: using uniquelk = std::unique_lock; private: int numThreads = 0; std::vector lstTh; std::queue taskPending; std::mutex mutTaskPending; std::condition_variable condTaskPending; int counterTaskRunning; std::mutex mutTaskRunning; std::condition_variable condTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV1B(int numThreads) { init(numThreads); } MyExecServiceV1B(const MyExecServiceV1B& other) = delete; MyExecServiceV1B(const MyExecServiceV1B&& other) = delete; void operator=(const MyExecServiceV1B& other) = delete; void operator=(const MyExecServiceV1B&& other) = delete; private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { uniquelk lkPending(mutTaskPending); if (taskPending.empty()) { uniquelk lkRunning(mutTaskRunning); while (counterTaskRunning > 0) condTaskRunning.wait(lkRunning); // no pending task and no running task break; } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); } condTaskPending.notify_all(); for (auto&& th : lstTh) { th.join(); } numThreads = 0; lstTh.clear(); } private: static void threadWorkerFunc(MyExecServiceV1B* thisPtr) { auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& condTaskRunning = thisPtr->condTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; } // DO THE TASK task->run(); { uniquelk lkRunning(mutTaskRunning); --counterTaskRunning; if (0 == counterTaskRunning) { condTaskRunning.notify_one(); } } } } }; #endif // _MY_EXEC_SERVICE_V1B_HPP_ ================================================ FILE: cpp/cpp-std/exer08-exec-service-v2a.hpp ================================================ /* MY EXECUTOR SERVICE Version 2A: The executor service storing running tasks - Method "waitTaskDone" uses a semaphore to synchronize. */ #ifndef _MY_EXEC_SERVICE_V2A_HPP_ #define _MY_EXEC_SERVICE_V2A_HPP_ #include #include #include #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV2A { private: using cntsemaphore = std::counting_semaphore<>; using uniquelk = std::unique_lock; private: int numThreads = 0; std::vector lstTh; std::queue taskPending; std::mutex mutTaskPending; std::condition_variable condTaskPending; std::list taskRunning; std::mutex mutTaskRunning; cntsemaphore counterTaskRunning = cntsemaphore(0); volatile bool forceThreadShutdown; public: MyExecServiceV2A(int numThreads) { init(numThreads); } MyExecServiceV2A(const MyExecServiceV2A& other) = delete; MyExecServiceV2A(const MyExecServiceV2A&& other) = delete; void operator=(const MyExecServiceV2A& other) = delete; void operator=(const MyExecServiceV2A&& other) = delete; private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; lstTh.resize(numThreads); forceThreadShutdown = false; for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { counterTaskRunning.acquire(); { uniquelk lkPending(mutTaskPending); uniquelk lkRunning(mutTaskRunning); if (taskPending.empty() && taskRunning.empty()) { break; } } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); } condTaskPending.notify_all(); for (auto&& th : lstTh) { th.join(); } numThreads = 0; lstTh.clear(); } private: static void threadWorkerFunc(MyExecServiceV2A* thisPtr) { auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& taskRunning = thisPtr->taskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); // PUSH IT TO THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.push_back(task); } } // DO THE TASK task->run(); // REMOVE IT FROM THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.remove(task); counterTaskRunning.release(); } } } }; #endif // _MY_EXEC_SERVICE_V2A_HPP_ ================================================ FILE: cpp/cpp-std/exer08-exec-service-v2b.hpp ================================================ /* MY EXECUTOR SERVICE Version 2B: The executor service storing running tasks - Method "waitTaskDone" uses a condition variable to synchronize. */ #ifndef _MY_EXEC_SERVICE_V2B_HPP_ #define _MY_EXEC_SERVICE_V2B_HPP_ #include #include #include #include #include #include #include "exer08-exec-service-itask.hpp" class MyExecServiceV2B { private: using uniquelk = std::unique_lock; private: int numThreads = 0; std::vector lstTh; std::queue taskPending; std::mutex mutTaskPending; std::condition_variable condTaskPending; std::list taskRunning; std::mutex mutTaskRunning; std::condition_variable condTaskRunning; volatile bool forceThreadShutdown; public: MyExecServiceV2B(int numThreads) { init(numThreads); } MyExecServiceV2B(const MyExecServiceV2B& other) = delete; MyExecServiceV2B(const MyExecServiceV2B&& other) = delete; void operator=(const MyExecServiceV2B& other) = delete; void operator=(const MyExecServiceV2B&& other) = delete; private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; lstTh.resize(numThreads); forceThreadShutdown = false; for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(ITask* task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { uniquelk lkPending(mutTaskPending); if (taskPending.empty()) { uniquelk lkRunning(mutTaskRunning); while (false == taskRunning.empty()) condTaskRunning.wait(lkRunning); // no pending task and no running task break; } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); } condTaskPending.notify_all(); for (auto&& th : lstTh) { th.join(); } numThreads = 0; lstTh.clear(); } private: static void threadWorkerFunc(MyExecServiceV2B* thisPtr) { auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& taskRunning = thisPtr->taskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& condTaskRunning = thisPtr->condTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; ITask* task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); while (taskPending.empty() && false == forceThreadShutdown) { condTaskPending.wait(lkPending); } if (forceThreadShutdown) { // lkPending.unlock(); // remember this statement break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); // PUSH IT TO THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.push_back(task); } } // DO THE TASK task->run(); // REMOVE IT FROM THE RUNNING QUEUE { uniquelk lkRunning(mutTaskRunning); taskRunning.remove(task); condTaskRunning.notify_one(); } } } }; #endif // _MY_EXEC_SERVICE_V2B_HPP_ ================================================ FILE: cpp/cpp-std/exerex-countdown-timer.cpp ================================================ /* COUNTDOWN TIMER */ #include #include #include #include #include using namespace std; void doUserInput(char* buffer, std::condition_variable* cv) { cin.getline(buffer, 1024); cv->notify_one(); } /* Return true if no timeout. Otherwise, return false. */ bool waitForTime(const int waitTime, std::condition_variable& cv, std::mutex& mut) { std::unique_lock lk(mut); std::cv_status status = cv.wait_for(lk, std::chrono::seconds(waitTime)); if (std::cv_status::no_timeout == status) return true; else return false; } int main() { std::condition_variable cv; std::mutex mut; constexpr int SECONDS = 5; char buffer[1024] = { 0 }; cout << "You have " << SECONDS << " seconds to write anything you like in one line." << endl; cout << "Press enter to start." << endl; cin.getline(buffer, 1024); cout << "START!!!" << endl << endl; auto th = std::thread(&doUserInput, buffer, &cv); if (waitForTime(SECONDS, cv, mut)) { cout << "\nYou completed before the deadline." << endl; } else { cout << "\n\nTIMEOUT!!!" << endl; } th.join(); return 0; } ================================================ FILE: cpp/cpp-std/mylib-blockingqueue.hpp ================================================ /****************************************************** * * File name: mylib-blockingqueue.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The blocking queue implementation in C++11 std threading * ******************************************************/ #ifndef _MYLIB_BLOCKING_QUEUE_HPP_ #define _MYLIB_BLOCKING_QUEUE_HPP_ #include #include #include #include #include namespace mylib { template class BlockingQueue { private: using uniquelk = std::unique_lock; private: std::condition_variable condEmpty; std::condition_variable condFull; std::mutex mut; size_t capacity; std::queue q; public: BlockingQueue() : capacity(std::numeric_limits::max()) { } BlockingQueue(size_t capacity) : capacity(capacity) { } BlockingQueue(const BlockingQueue& other) = delete; BlockingQueue(const BlockingQueue&& other) = delete; void operator=(const BlockingQueue& other) = delete; void operator=(const BlockingQueue&& other) = delete; bool empty() const { return q.empty(); } size_t size() const { return q.size(); } // sync enqueue void put(const T& value) { uniquelk lk(mut); condFull.wait(lk, [&] { return q.size() < capacity; }); q.push(value); condEmpty.notify_one(); } // sync dequeue T take() { uniquelk lk(mut); condEmpty.wait(lk, [&] { return !q.empty(); }); T result = q.front(); q.pop(); condFull.notify_one(); return result; } // async enqueue void add(const T& value) { // Note: For asynchronous operations, we should use a long-live background thread // instead of using a temporary thread std::thread(&BlockingQueue::put, this, value).detach(); } // returns false if queue is empty, otherwise returns true and assigns the result bool peek(T& result) const { uniquelk lk(mut); if (q.empty()) { return false; } result = q.front(); return true; } void clear() { uniquelk lk(mut); std::queue().swap(q); } }; // BlockingQueue } // namespace mylib #endif // _MYLIB_BLOCKING_QUEUE_HPP_ ================================================ FILE: cpp/cpp-std/mylib-execservice.hpp ================================================ /****************************************************** * * File name: mylib-execservice.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The executor service implementation in C++11 std threading * ******************************************************/ /* Copy code from "MyExecServiceV1B" */ #ifndef _MYLIB_EXEC_SERVICE_HPP_ #define _MYLIB_EXEC_SERVICE_HPP_ #include #include #include #include #include #include namespace mylib { class ExecService { private: using uniquelk = std::unique_lock; public: using taskFunc = std::function; private: int numThreads = 0; std::vector lstTh; std::queue taskPending; std::mutex mutTaskPending; std::condition_variable condTaskPending; int counterTaskRunning; std::mutex mutTaskRunning; std::condition_variable condTaskRunning; volatile bool forceThreadShutdown; public: ExecService(int numThreads) { init(numThreads); } ExecService(const ExecService& other) = delete; ExecService(const ExecService&& other) = delete; void operator=(const ExecService& other) = delete; void operator=(const ExecService&& other) = delete; private: void init(int numThreads) { // shutdown(); this->numThreads = numThreads; lstTh.resize(numThreads); counterTaskRunning = 0; forceThreadShutdown = false; for (auto&& th : lstTh) { th = std::thread(&threadWorkerFunc, this); } } public: void submit(taskFunc task) { { uniquelk lk(mutTaskPending); taskPending.push(task); } condTaskPending.notify_one(); } void waitTaskDone() { for (;;) { uniquelk lkPending(mutTaskPending); if (taskPending.empty()) { uniquelk lkRunning(mutTaskRunning); condTaskRunning.wait(lkRunning, [&] { return counterTaskRunning <= 0; }); // no pending task and no running task break; } } } void shutdown() { { uniquelk lk(mutTaskPending); forceThreadShutdown = true; std::queue().swap(taskPending); } condTaskPending.notify_all(); for (auto&& th : lstTh) { th.join(); } numThreads = 0; lstTh.clear(); } private: static void threadWorkerFunc(ExecService* thisPtr) { auto&& taskPending = thisPtr->taskPending; auto&& mutTaskPending = thisPtr->mutTaskPending; auto&& condTaskPending = thisPtr->condTaskPending; auto&& counterTaskRunning = thisPtr->counterTaskRunning; auto&& mutTaskRunning = thisPtr->mutTaskRunning; auto&& condTaskRunning = thisPtr->condTaskRunning; auto&& forceThreadShutdown = thisPtr->forceThreadShutdown; taskFunc task = nullptr; for (;;) { { // WAIT FOR AN AVAILABLE PENDING TASK uniquelk lkPending(mutTaskPending); condTaskPending.wait(lkPending, [&] { return forceThreadShutdown || !taskPending.empty(); }); if (forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.front(); taskPending.pop(); ++counterTaskRunning; } // DO THE TASK task(); { uniquelk lkRunning(mutTaskRunning); --counterTaskRunning; if (0 == counterTaskRunning) { condTaskRunning.notify_one(); } } } } }; // ExecService } // namespace mylib #endif // _MYLIB_EXEC_SERVICE_HPP_ ================================================ FILE: cpp/cpp-std/mylib-random.hpp ================================================ /****************************************************** * * File name: mylib-random.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The random utility in C++11 std * ******************************************************/ #ifndef _MYLIB_RANDOM_HPP_ #define _MYLIB_RANDOM_HPP_ #include #include namespace mylib { class RandInt { private: std::random_device rd; std::mt19937 mt; std::uniform_int_distribution dist; public: RandInt() { init(0, std::numeric_limits::max()); } RandInt(int minValue, int maxValueInclusive) { init(minValue, maxValueInclusive); } void init(int minValue, int maxValueInclusive) { dist = std::uniform_int_distribution(minValue, maxValueInclusive); mt.seed(rd()); } int next() { return dist(mt); } RandInt(const RandInt& other) = default; RandInt(RandInt&& other) = default; RandInt& operator=(const RandInt& other) = default; RandInt& operator=(RandInt&& other) = default; // STATIC private: static RandInt publicRandInt; public: static int get(int maxExclusive) { return publicRandInt.next() % maxExclusive; } }; // RandInt RandInt RandInt::publicRandInt; } // namespace mylib #endif // _MYLIB_RANDOM_HPP_ ================================================ FILE: cpp/cpp-std/mylib-time.hpp ================================================ /****************************************************** * * File name: mylib-time.hpp * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The time utility in C++11 std * ******************************************************/ #ifndef _MYLIB_TIME_HPP_ #define _MYLIB_TIME_HPP_ #include #include namespace mylib { namespace chro = std::chrono; using sysclock = chro::system_clock; class HiResClock { private: using stdhrc = chro::high_resolution_clock; public: static inline stdhrc::time_point now() { return stdhrc::now(); } template< typename duType=chro::duration > static inline duType getTimeSpan( const stdhrc::time_point& tp1, const stdhrc::time_point& tp2) { auto res = chro::duration_cast(tp2 - tp1); return res; } template< typename duType=chro::duration > static inline duType getTimeSpan(const stdhrc::time_point& tpBefore) { auto tpCurrent = HiResClock::now(); auto res = HiResClock::getTimeSpan(tpBefore, tpCurrent); return res; } }; // HiResClock char* getTimePointStr(const sysclock::time_point& tp) { std::time_t timeStamp = sysclock::to_time_t(tp); return std::ctime(&timeStamp); } template class clock::time_point getTimePoint( int year, int month, int day, int hour, int minute, int second) { std::tm t{}; t.tm_year = year - 1900; t.tm_mon = month - 1; t.tm_mday = day; t.tm_hour = hour; t.tm_min = minute; t.tm_sec = second; return clock::from_time_t(std::mktime(&t)); } // tp += numSeconds * 2; // tp -= (x % numSeconds) template chro::time_point getTimePointFutureFloor(const chro::time_point& tp, int numSeconds) { // auto tpFuture = tp + chro::seconds(2 * numSeconds); // auto durationFuture = tpFuture.time_since_epoch(); // durationFuture = durationFuture - (durationFuture % numSeconds); // tpFuture = chro::time_point(durationFuture); // return tpFuture; auto duSeconds = chro::seconds(numSeconds); auto durationFromTp = chro::time_point_cast(tp).time_since_epoch(); auto durationFuture = durationFromTp + (duSeconds * 2); durationFuture = durationFuture - (durationFuture % duSeconds); auto tpFuture = chro::time_point(durationFuture); return tpFuture; } } // namespace mylib #endif // _MYLIB_TIME_HPP_ ================================================ FILE: csharp/.gitignore ================================================ bin/ obj/ .vs/ ================================================ FILE: csharp/IRunnable.cs ================================================ interface IRunnable { public abstract void run(); } ================================================ FILE: csharp/Program.cs ================================================ class Program { static void Main(string[] args) { new Demo00().run(); } } ================================================ FILE: csharp/demo/demo00-intro.cs ================================================ /* * INTRODUCTION TO MULTITHREADING * You should try running this app several times and see results. */ using System; using System.Threading; class Demo00 : IRunnable { public void run() { Thread th = new Thread(doTask); th.Start(); for (int i = 0; i < 300; ++i) Console.Write("A"); } private void doTask() { for (int i = 0; i < 300; ++i) Console.Write("B"); } } ================================================ FILE: csharp/demo/demo01a-hello.cs ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version A: Using functions */ using System; using System.Threading; class Demo01A : IRunnable { public void run() { Thread th = new Thread(doTask); // or // Thread th = new Thread(new ThreadStart(doTask)); th.Start(); Console.WriteLine("Hello from main thread"); } private void doTask() { Console.WriteLine("Hello from example thread"); } } ================================================ FILE: csharp/demo/demo01b01-hello.cs ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version B01: Using lambdas with ThreadStart */ using System; using System.Threading; class Demo01B01 : IRunnable { public void run() { ThreadStart doTask = new ThreadStart(() => { Console.WriteLine("Hello from example thread"); }); Thread th1 = new Thread(doTask); Thread th2 = new Thread(doTask); th1.Start(); th2.Start(); Console.WriteLine("Hello from main thread"); } } ================================================ FILE: csharp/demo/demo01b02-hello.cs ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version B02: Using lambdas */ using System; using System.Threading; class Demo01B02 : IRunnable { public void run() { Thread th = new Thread(() => Console.WriteLine("Hello from example thread")); th.Start(); Console.WriteLine("Hello from main thread"); } } ================================================ FILE: csharp/demo/demo01ex-name.cs ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version extra: Getting thread's name */ using System; using System.Threading; class Demo01ExtraName : IRunnable { public void run() { Thread thFoo = new Thread(doTask) { Name = "foo" }; Thread thBar = new Thread(doTask); thBar.Name = "bar"; thFoo.Start(); thBar.Start(); } private void doTask() { Console.WriteLine($"My name is {Thread.CurrentThread.Name}"); } } ================================================ FILE: csharp/demo/demo02a-join.cs ================================================ /* * THREAD JOINS */ using System; using System.Threading; class Demo02A : IRunnable { public void run() { Thread th = new Thread(doHeavyTask); th.Start(); th.Join(); Console.WriteLine("Good bye!"); } private void doHeavyTask() { // Do a heavy task, which takes a little time for (int i = 0; i < 2000000000; ++i); Console.WriteLine("Done!"); } } ================================================ FILE: csharp/demo/demo02b-join.cs ================================================ /* * THREAD JOINS */ using System; using System.Threading; class Demo02B : IRunnable { public void run() { Thread thFoo = new Thread(() => Console.WriteLine("foo")); Thread thBar = new Thread(() => Console.WriteLine("bar")); thFoo.Start(); thBar.Start(); // thFoo.Join(); // thBar.Join(); /* * We do not need to call thFoo.Join() and thBar.Join(). * The reason is main thread will wait for the completion of all threads before app exits. */ } } ================================================ FILE: csharp/demo/demo03a-pass-arg.cs ================================================ /* * PASSING ARGUMENTS * Version A: Using lambdas */ using System; using System.Threading; class Demo03C : IRunnable { public void run() { Thread thFoo = new Thread(() => doTask(1, 2, "red")); Thread thBar = new Thread(() => doTask(3, 4, "blue")); thFoo.Start(); thBar.Start(); } private void doTask(int a, double b, string c) { Console.WriteLine($"{a} {b} {c}"); } } ================================================ FILE: csharp/demo/demo03b-pass-arg.cs ================================================ /* * PASSING ARGUMENTS * Version B: Traditional way with a single argument of type object */ using System; using System.Threading; class Demo03A : IRunnable { public void run() { Thread thFoo = new Thread(doTask); Thread thBar = new Thread(doTask); // or // Thread thFoo = new Thread(new ParameterizedThreadStart(doTask)); // Thread thBar = new Thread(new ParameterizedThreadStart(doTask)); thFoo.Start(new object[] { 1, 2.0, "red" }); thBar.Start(new object[] { 3, 4.0, "blue" }); } private void doTask(object arg) { object[] array = (object[]) arg; int a = (int) array[0]; double b = (double) array[1]; string c = (string) array[2]; Console.WriteLine($"{a} {b} {c}\n"); } } ================================================ FILE: csharp/demo/demo03c-pass-arg.cs ================================================ /* * PASSING ARGUMENTS * Version C: Traditional way + dynamic data type */ using System; using System.Threading; class Demo03B : IRunnable { public void run() { Thread thFoo = new Thread(doTask); Thread thBar = new Thread(doTask); thFoo.Start(new object[] { 1, 2, "red" }); thBar.Start(new object[] { 3, 4, "blue" }); } private void doTask(dynamic arg) { int a = arg[0]; double b = arg[1]; string c = arg[2]; Console.WriteLine($"{a} {b} {c}\n"); } } ================================================ FILE: csharp/demo/demo03d-pass-arg.cs ================================================ /* * PASSING ARGUMENTS * Version D: Passing arguments by capturing them */ using System; using System.Threading; class Demo03D : IRunnable { public void run() { const int COUNT = 10; new Thread(() => { for (int i = 1; i <= COUNT; ++i) Console.WriteLine("Foo " + i); }).Start(); } } ================================================ FILE: csharp/demo/demo04-sleep.cs ================================================ /* * SLEEP */ using System; using System.Threading; class Demo04 : IRunnable { public void run() { var thFoo = new Thread(() => { Console.WriteLine("foo is sleeping"); Thread.Sleep(3000); Console.WriteLine("foo wakes up"); }); thFoo.Start(); thFoo.Join(); Console.WriteLine("Good bye"); } } ================================================ FILE: csharp/demo/demo05-id.cs ================================================ /* * GETTING THREAD'S ID */ using System; using System.Threading; class Demo05 : IRunnable { public void run() { ThreadStart doTask = () => { int id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine(id); }; Thread thFoo = new Thread(doTask); Thread thBar = new Thread(doTask); Console.WriteLine("foo's id: " + thFoo.ManagedThreadId); Console.WriteLine("bar's id: " + thBar.ManagedThreadId); thFoo.Start(); thBar.Start(); } } ================================================ FILE: csharp/demo/demo06a-list-threads.cs ================================================ /* * LIST OF MULTIPLE THREADS * Version A: Using List */ using System; using System.Collections.Generic; using System.Threading; class Demo06A : IRunnable { public void run() { const int NUM_THREADS = 5; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { /* * Due to the reference mechanism, * if you remove the line "int ith = i", the result will be wrong. */ int ith = i; lstTh.Add(new Thread(() => { Thread.Sleep(500); Console.WriteLine(ith); })); } lstTh.ForEach(th => th.Start()); // foreach (var th in lstTh) // { // th.Start(); // } } } ================================================ FILE: csharp/demo/demo06b-list-threads.cs ================================================ /* * LIST OF MULTIPLE THREADS * Version B: Using Linq */ using System; using System.Linq; using System.Threading; class Demo06B : IRunnable { public void run() { var lstTh = Enumerable.Range(0, 5).Select(index => new Thread(() => { Thread.Sleep(500); Console.WriteLine(index); })).ToList(); lstTh.ForEach(th => th.Start()); } } ================================================ FILE: csharp/demo/demo07-terminate.cs ================================================ /* * FORCING A THREAD TO TERMINATE (i.e. killing the thread) * Using a flag to notify the thread * * Note: The "volatile" keyword is explained in another demo. */ using System; using System.Threading; class Demo07 : IRunnable { private volatile bool flagStop; public void run() { flagStop = false; var th = new Thread(() => { while (true) { if (flagStop) break; Console.WriteLine("Running..."); Thread.Sleep(1000); } }); th.Start(); Thread.Sleep(3000); flagStop = true; } } ================================================ FILE: csharp/demo/demo08a-return-value.cs ================================================ /* * GETTING RETURNED VALUES FROM THREADS */ using System; using System.Threading; class Demo08A : IRunnable { public void run() { int resFoo = 0, resBar = 0; var thFoo = new Thread(() => resFoo = doubleValue(5)); var thBar = new Thread(() => resBar = doubleValue(80)); thFoo.Start(); thBar.Start(); // Wait until thFoo and thBar finish thFoo.Join(); thBar.Join(); Console.WriteLine(resFoo); Console.WriteLine(resBar); } private int doubleValue(int value) { return value * 2; } } ================================================ FILE: csharp/demo/demo08b-return-value.cs ================================================ /* * GETTING RETURNED VALUES FROM THREADS * Using AutoResetEvent to notify the thread which is waiting for returned values */ using System; using System.Threading; class Demo08B : IRunnable { private AutoResetEvent re; private int result; public void run() { re = new AutoResetEvent(false); new Thread(() => doubleValue(5)).Start(); // Wait until we receive a notification from re re.WaitOne(); Console.WriteLine(result); } private void doubleValue(int value) { result = value * 2; Thread.Sleep(2000); re.Set(); // Notify the threads that are waiting for re Thread.Sleep(2000); Console.WriteLine("This thread is exiting"); } } ================================================ FILE: csharp/demo/demo09-detach.cs ================================================ /* * THREAD DETACHING */ using System; using System.Threading; class Demo09 : IRunnable { public void run() { var thFoo = new Thread(() => { Console.WriteLine("foo is starting..."); Thread.Sleep(2000); Console.WriteLine("foo is exiting..."); }); thFoo.IsBackground = true; thFoo.Start(); // If I comment this statement, // thFoo will be forced into terminating with main thread Thread.Sleep(3000); Console.WriteLine("Main thread is exiting"); } } ================================================ FILE: csharp/demo/demo10-yield.cs ================================================ /* * THREAD YIELDING */ using System; using System.Threading; class Demo10 : IRunnable { public void run() { DateTime tpStartMeasure = DateTime.Now; littleSleep(1); var timeElapsed = DateTime.Now.Subtract(tpStartMeasure).TotalMilliseconds; Console.WriteLine($"Elapsed time: {timeElapsed} miliseonds"); } private void littleSleep(double miliseconds) { DateTime tpEnd = DateTime.Now.AddMilliseconds(miliseconds); do { Thread.Yield(); } while (DateTime.Now.CompareTo(tpEnd) < 0); } } ================================================ FILE: csharp/demo/demo11a01-exec-service.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version A01: System.Threading.ThreadPool * Introduction */ using System; using System.Threading; class Demo11A01 : IRunnable { public void run() { WaitCallback wcb = (arg) => { Console.WriteLine("Hello Multithreading"); }; ThreadPool.QueueUserWorkItem(wcb); ThreadPool.QueueUserWorkItem(doTask); ThreadPool.QueueUserWorkItem(arg => Console.WriteLine("Hello World")); // Wait one second for task completion Thread.Sleep(1000); } private void doTask(object arg) { Console.WriteLine("Hello the Executor Service"); } } ================================================ FILE: csharp/demo/demo11a02-exec-service.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version A02: System.Threading.ThreadPool * Passing arguments */ using System; using System.Threading; class Demo11A02 : IRunnable { public void run() { WaitCallback wcb = (arg) => { Console.WriteLine($"Hello Multithreading {arg}"); }; ThreadPool.QueueUserWorkItem(wcb, 1); ThreadPool.QueueUserWorkItem(doTask, 2); ThreadPool.QueueUserWorkItem( arg => Console.WriteLine($"Hello World {arg}"), 3 ); // Wait one second for task completion Thread.Sleep(1000); } private void doTask(object arg) { Console.WriteLine($"Hello the Executor Service {arg}"); } } ================================================ FILE: csharp/demo/demo11a03-exec-service.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version A03: System.Threading.ThreadPool * Returning values */ using System; using System.Threading; class Demo11A03 : IRunnable { private AutoResetEvent re; public void run() { re = new AutoResetEvent(false); int[] arg = new int[2]; arg[0] = 7; // input ThreadPool.QueueUserWorkItem(getSquared, arg); // Wait until the thread completes re.WaitOne(); int result = arg[1]; Console.WriteLine(result); } private void getSquared(dynamic arg) { int i = arg[0]; arg[1] = i * i; re.Set(); } } ================================================ FILE: csharp/demo/demo11a04-exec-service.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version A04: System.Threading.ThreadPool * Waiting for task completion by AutoResetEvent */ using System; using System.Threading; class Demo11A04 : IRunnable { public void run() { // INIT const int N = 5; var lstResult = new int[N]; var lstEvent = new AutoResetEvent[N]; for (int i = 0; i < N; ++i) lstEvent[i] = new AutoResetEvent(false); // RUN for (int i = 0; i < N; ++i) { int ith = i; ThreadPool.QueueUserWorkItem(_ => { lstResult[ith] = ith * ith; lstEvent[ith].Set(); } ); } WaitHandle.WaitAll(lstEvent); // PRINT RESULTS Array.ForEach(lstResult, res => Console.WriteLine(res)); } } ================================================ FILE: csharp/demo/demo11a05-exec-service.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version A05: System.Threading.ThreadPool * Waiting for task completion by CountdownEvent */ using System; using System.Threading; class Demo11A05 : IRunnable { public void run() { // INIT const int N = 5; var lstResult = new int[N]; var cde = new CountdownEvent(N); // RUN for (int i = 0; i < N; ++i) { int ith = i; ThreadPool.QueueUserWorkItem(_ => { lstResult[ith] = ith * ith; cde.Signal(); } ); } cde.Wait(); // PRINT RESULTS Array.ForEach(lstResult, res => Console.WriteLine(res)); cde.Dispose(); } } ================================================ FILE: csharp/demo/demo11b01-exec-service-parallel.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version B01: System.Threading.Tasks.Parallel * Introduction */ using System; using System.Threading.Tasks; class Demo11B01 : IRunnable { public void run() { Action action = arg => { Console.WriteLine($"Hello Multithreading {arg}"); }; Parallel.For(0, 4, action); // Main thread shall pause until all threads are completed Parallel.For(4, 8, doTask); // Main thread shall pause until all threads are completed Parallel.For(8, 12, i => Console.WriteLine($"Hello World {i}")); // Main thread shall pause until all threads are completed } private void doTask(int arg) { Console.WriteLine($"Hello the Executor Service {arg}"); } } ================================================ FILE: csharp/demo/demo11b02-exec-service-parallel.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version B02: System.Threading.Tasks.Parallel */ using System; using System.Threading.Tasks; class Demo11B02 : IRunnable { public void run() { // EXAMPLE 1 const int N = 5; var lstResult = new int[N]; Parallel.For(0, N, i => { lstResult[i] = i * i; }); Array.ForEach(lstResult, res => Console.Write(res + "\t")); Console.WriteLine(); // EXAMPLE 2 var lstArg = new double[] { -3.14, -9.8, 0, 1, -6 }; Parallel.ForEach(lstArg, arg => { Console.Write(Math.Abs(arg) + "\t"); }); Console.WriteLine(); } } ================================================ FILE: csharp/demo/demo11c-exec-service.cs ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C: Fixed thread pools */ using System; using System.Threading; using System.Threading.Tasks; class Demo11C : IRunnable { public void run() { const int N = 5; var prlOptions = new ParallelOptions { MaxDegreeOfParallelism = 2 }; Parallel.For(0, N, prlOptions, i => { char name = (char)(i + 'A'); Console.WriteLine($"Task {name} is starting"); Thread.Sleep(3000); Console.WriteLine($"Task {name} is completed"); } ); } } ================================================ FILE: csharp/demo/demo12a-race-condition.cs ================================================ /* * RACE CONDITIONS */ using System; using System.Collections.Generic; using System.Threading; class Demo12A : IRunnable { public void run() { const int NUM_THREADS = 5; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { int ith = i; lstTh.Add(new Thread(() => { Thread.Sleep(1000); Console.WriteLine(ith); })); } lstTh.ForEach(th => th.Start()); } } ================================================ FILE: csharp/demo/demo12b01-data-race-single.cs ================================================ /* * DATA RACES * Version 01: Without multithreading */ using System; using System.Linq; class Demo12B01 : IRunnable { public void run() { const int N = 8; int result = getResult(N); Console.WriteLine("Numbers of integers that are divisible by 2 or 3 is: " + result); } private int getResult(int N) { var a = Enumerable.Repeat(false, N + 1).ToArray(); for (int i = 1; i <= N; ++i) if (i % 2 == 0 || i % 3 == 0) a[i] = true; // res = number of true values in array int res = a.Count(val => val); return res; } } ================================================ FILE: csharp/demo/demo12b02-data-race-multi.cs ================================================ /* * DATA RACES * Version 02: Multithreading */ using System; using System.Threading; using System.Linq; class Demo12B02 : IRunnable { public void run() { const int N = 8; var a = Enumerable.Repeat(false, N + 1).ToArray(); var thDiv2 = new Thread(() => { for (int i = 2; i <= N; i += 2) a[i] = true; }); var thDiv3 = new Thread(() => { for (int i = 3; i <= N; i += 3) a[i] = true; }); thDiv2.Start(); thDiv3.Start(); thDiv2.Join(); thDiv3.Join(); int result = a.Count(val => val); Console.WriteLine("Numbers of integers that are divisible by 2 or 3 is: " + result); } } ================================================ FILE: csharp/demo/demo12c01-race-cond-data-race.cs ================================================ /* * RACE CONDITIONS AND DATA RACES */ using System; using System.Collections.Generic; using System.Threading; class Demo12C01 : IRunnable { private int counter; public void run() { const int NUM_THREADS = 16; counter = 0; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.Add(new Thread(doTask)); } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); Console.WriteLine("counter = " + counter); // We are NOT sure that counter = 16000 } private void doTask() { Thread.Sleep(1000); for (int i = 0; i < 1000; ++i) ++counter; } } ================================================ FILE: csharp/demo/demo12c02-race-cond-data-race.cs ================================================ /* * RACE CONDITIONS AND DATA RACES */ using System; using System.Threading; class Demo12C02 : IRunnable { public void run() { var thA = new Thread(() => { Thread.Sleep(30); while (Global.counter < 10) ++Global.counter; Console.WriteLine("A won !!!"); }); var thB = new Thread(() => { Thread.Sleep(30); while (Global.counter > -10) --Global.counter; Console.WriteLine("B won !!!"); }); thA.Start(); thB.Start(); } class Global { public static int counter = 0; } } ================================================ FILE: csharp/demo/demo13a-mutex.cs ================================================ /* * MUTEXES */ using System; using System.Collections.Generic; using System.Threading; class Demo13A : IRunnable { private Mutex mut; private int counter; public void run() { const int NUM_THREADS = 16; mut = new Mutex(); counter = 0; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.Add(new Thread(doTask)); } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); mut.Dispose(); Console.WriteLine("counter = " + counter); // We are sure that counter = 16000 } private void doTask() { Thread.Sleep(1000); mut.WaitOne(); for (int i = 0; i < 1000; ++i) ++counter; mut.ReleaseMutex(); } } ================================================ FILE: csharp/demo/demo13b-mutex-trylock.cs ================================================ /* * MUTEXES * Locking with a nonblocking mutex */ using System; using System.Collections.Generic; using System.Threading; class Demo13B : IRunnable { private Mutex mut; private int counter; public void run() { const int NUM_THREADS = 90; mut = new Mutex(); counter = 0; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.Add(new Thread(routineCounter)); } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); mut.Dispose(); Console.WriteLine("counter = " + counter); } private void routineCounter() { Thread.Sleep(1000); if (false == mut.WaitOne(1)) { return; } for (int i = 0; i < 1000; ++i) ++counter; mut.ReleaseMutex(); } } ================================================ FILE: csharp/demo/demo14-synchronized-block.cs ================================================ /* * SYNCHRONIZED BLOCKS */ using System; using System.Collections.Generic; using System.Threading; class Demo14 : IRunnable { private object theLock = new object(); private int counter; public void run() { const int NUM_THREADS = 16; counter = 0; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.Add(new Thread(doTask)); } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); Console.WriteLine("counter = " + counter); // We are sure that counter = 16000 } private void doTask() { Thread.Sleep(1000); lock (theLock) { for (int i = 0; i < 1000; ++i) ++counter; } } } ================================================ FILE: csharp/demo/demo15a-deadlock.cs ================================================ /* * DEADLOCK * Version A */ using System; using System.Threading; class Demo15A : IRunnable { private Mutex mut; public void run() { mut = new Mutex(); var thFoo = new Thread(() => doTask("foo")); var thBar = new Thread(() => doTask("bar")); thFoo.Start(); thBar.Start(); thFoo.Join(); thBar.Join(); // The app may throw System.Threading.AbandonedMutexException Console.WriteLine("You will never see this statement due to deadlock!"); } private void doTask(string name) { mut.WaitOne(); Console.WriteLine(name + " acquired resource"); // mut.ReleaseMutex(); // Forget this statement ==> deadlock } } ================================================ FILE: csharp/demo/demo15b-deadlock.cs ================================================ /* * DEADLOCK * Version B */ using System; using System.Threading; class Demo15B : IRunnable { private object resourceA = "resourceA"; private object resourceB = "resourceB"; public void run() { var thFoo = new Thread(() => { lock (resourceA) { Console.WriteLine("foo acquired resource A"); Thread.Sleep(1000); lock (resourceB) { Console.WriteLine("foo acquired resource B"); } } }); var thBar = new Thread(() => { lock (resourceB) { Console.WriteLine("bar acquired resource B"); Thread.Sleep(1000); lock (resourceA) { Console.WriteLine("bar acquired resource A"); } } }); thFoo.Start(); thBar.Start(); thFoo.Join(); thBar.Join(); Console.WriteLine("You will never see this statement due to deadlock!"); } } ================================================ FILE: csharp/demo/demo16-monitor.cs ================================================ /* * MONITORS * Implementation of a monitor for managing a counter * * Notes: * - In C#, Monitor is already available (class System.Threading.Monitor). * - From a simple point of view in C#, * "lock" keyword is shorthand for using System.Threading.Monitor + try/finally. */ using System; using System.Collections.Generic; using System.Threading; class Demo16 : IRunnable { public void run() { var counter = new Counter(); var monitor = new MyMonitor(); monitor.init(counter); const int NUM_THREADS = 16; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { lstTh.Add(new Thread(() => { Thread.Sleep(1000); for (int j = 0; j < 1000; ++j) monitor.increaseCounter(); })); } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); Console.WriteLine("counter = " + counter.value); } class Counter { public int value = 0; } class MyMonitor { private Counter counter = null; public void init(Counter counter) { this.counter = counter; } public void increaseCounter() { lock (counter) { ++counter.value; } } } } ================================================ FILE: csharp/demo/demo17a-reentrant-lock.cs ================================================ /* * REENTRANT LOCKS (RECURSIVE MUTEXES) * * In C#, "lock" keyword and System.Threading.Monitor class allow re-entrancy. * That means they are reentrant locks by default. * * Version A: A simple example */ using System; using System.Threading; class Demo17A : IRunnable { public void run() { const string resource = "resource"; new Thread(() => { lock (resource) { Console.WriteLine("First time acquiring the resource"); lock (resource) { Console.WriteLine("Second time acquiring the resource"); } } }).Start(); } } ================================================ FILE: csharp/demo/demo17b-reentrant-lock.cs ================================================ /* * REENTRANT LOCKS (RECURSIVE MUTEXES) * Version A: A multithreaded app example */ using System; using System.Collections.Generic; using System.Threading; class Demo17B : IRunnable { public void run() { const int NUM_THREADS = 3; var lstWk = new List(); for (int i = 0; i < NUM_THREADS; ++i) { lstWk.Add(new Worker((char)(i + 'A'))); } lstWk.ForEach(wk => wk.start()); } class Worker { private static object lk = new object(); private Thread th; private char name; public Worker(char name) { this.name = name; this.th = new Thread(doTask); } private void doTask() { Thread.Sleep(1000); lock (lk) { Console.WriteLine($"First time {name} acquiring the resource"); lock (lk) { Console.WriteLine($"Second time {name} acquiring the resource"); } } } public void start() { th.Start(); } } } ================================================ FILE: csharp/demo/demo18a01-barrier.cs ================================================ /* * BARRIERS AND LATCHES * Version A: Barriers */ using System; using System.Collections.Generic; using System.Threading; class Demo18A01 : IRunnable { public void run() { var syncPoint = new Barrier(participantCount: 3); var lstArg = new List { new ThreadArg{ userName = "lorem", waitTime = 1 }, new ThreadArg{ userName = "ipsum", waitTime = 2 }, new ThreadArg{ userName = "dolor", waitTime = 3 } }; lstArg.ForEach(arg => new Thread(() => { Thread.Sleep(1000 * arg.waitTime); Console.WriteLine("Get request from " + arg.userName); syncPoint.SignalAndWait(); Console.WriteLine("Process request for " + arg.userName); syncPoint.SignalAndWait(); Console.WriteLine("Done " + arg.userName); }).Start()); } class ThreadArg { public string userName; public int waitTime; } } ================================================ FILE: csharp/demo/demo18a03-barrier.cs ================================================ /* * BARRIERS AND LATCHES * Version A: Barriers */ using System; using System.Collections.Generic; using System.Threading; class Demo18A03 : IRunnable { public void run() { var syncPointA = new Barrier(participantCount: 2); var syncPointB = new Barrier(participantCount: 2); var lstArg = new List { new ThreadArg{ userName = "lorem", waitTime = 1 }, new ThreadArg{ userName = "ipsum", waitTime = 3 } }; lstArg.ForEach(arg => new Thread(() => { Thread.Sleep(1000 * arg.waitTime); Console.WriteLine("Get request from " + arg.userName); syncPointA.SignalAndWait(); Thread.Sleep(4000); Console.WriteLine("Process request for " + arg.userName); syncPointB.SignalAndWait(); Console.WriteLine("Done " + arg.userName); }).Start()); } class ThreadArg { public string userName; public int waitTime; } } ================================================ FILE: csharp/demo/demo18b01-latch.cs ================================================ /* * BARRIERS AND LATCHES * Version B: Count-down latches */ using System; using System.Collections.Generic; using System.Threading; class Demo18B01 : IRunnable { public void run() { var syncPoint = new CountdownEvent(3); var lstArg = new List { new ThreadArg{ userName = "lorem", waitTime = 1 }, new ThreadArg{ userName = "ipsum", waitTime = 2 }, new ThreadArg{ userName = "dolor", waitTime = 3 } }; lstArg.ForEach(arg => new Thread(() => { Thread.Sleep(1000 * arg.waitTime); Console.WriteLine("Get request from " + arg.userName); syncPoint.Signal(); syncPoint.Wait(); Console.WriteLine("Done " + arg.userName); }).Start()); } class ThreadArg { public string userName; public int waitTime; } } ================================================ FILE: csharp/demo/demo18b02-latch.cs ================================================ /* * BARRIERS AND LATCHES * Version B: Count-down latches * * Main thread waits for 3 child threads to get enough data to progress. */ using System; using System.Collections.Generic; using System.Threading; class Demo18B02 : IRunnable { public void run() { var lstArg = new List { new ThreadArg{ message = "Send request to egg.net to get data", waitTime = 6 }, new ThreadArg{ message = "Send request to foo.org to get data", waitTime = 2 }, new ThreadArg{ message = "Send request to bar.com to get data", waitTime = 4 } }; var syncPoint = new CountdownEvent(lstArg.Count); lstArg.ForEach(arg => new Thread(() => { Thread.Sleep(1000 * arg.waitTime); Console.WriteLine(arg.message); syncPoint.Signal(); Thread.Sleep(8000); Console.WriteLine("Cleanup"); }).Start()); syncPoint.Wait(); Console.WriteLine("\nNow we have enough data to progress to next step\n"); } class ThreadArg { public string message; public int waitTime; } } ================================================ FILE: csharp/demo/demo19-read-write-lock.cs ================================================ /* * READ-WRITE LOCKS */ using System; using System.Collections.Generic; using System.Linq; using System.Threading; class Demo19 : IRunnable { public void run() { var rwlk = new ReaderWriterLock(); const int NUM_THREADS_READ = 10; const int NUM_THREADS_WRITE = 4; const int NUM_ARGS = 3; var arg = Enumerable.Range(0, NUM_ARGS).ToArray(); var rand = new Random(); var lstThRead = new List(); var lstThWrite = new List(); for (int i = 0; i < NUM_THREADS_READ; ++i) { lstThRead.Add(new Thread(() => { int waitTime = arg[ rand.Next(arg.Length) ]; Thread.Sleep(1000 * waitTime); // Should catch exception rwlk.AcquireReaderLock(1000); Console.WriteLine("read: " + Resource.value); rwlk.ReleaseReaderLock(); })); } for (int i = 0; i < NUM_THREADS_WRITE; ++i) { lstThWrite.Add(new Thread(() => { int waitTime = arg[ rand.Next(arg.Length) ]; Thread.Sleep(1000 * waitTime); // Should catch exception rwlk.AcquireWriterLock(1000); Resource.value = rand.Next(100); Console.WriteLine("write: " + Resource.value); rwlk.ReleaseWriterLock(); })); } lstThRead.ForEach(th => th.Start()); lstThWrite.ForEach(th => th.Start()); } class Resource { public static volatile int value; } } ================================================ FILE: csharp/demo/demo20a01-semaphore.cs ================================================ /* * SEMAPHORES * Version A: Paper sheets and packages */ using System; using System.Threading; class Demo20A01 : IRunnable { public void run() { var semPackage = new Semaphore(0, int.MaxValue); // initialCount = 0, maximumCount = int.MaxValue ThreadStart makeOneSheet = () => { for (int i = 0; i < 4; ++i) { Console.WriteLine("Make 1 sheet"); Thread.Sleep(1000); semPackage.Release(); } }; ThreadStart combineOnePackage = () => { for (int i = 0; i < 4; ++i) { semPackage.WaitOne(); semPackage.WaitOne(); Console.WriteLine("Combine 2 sheets into 1 package"); } }; new Thread(makeOneSheet).Start(); new Thread(makeOneSheet).Start(); new Thread(combineOnePackage).Start(); } } ================================================ FILE: csharp/demo/demo20a02-semaphore.cs ================================================ /* * SEMAPHORES * Version A: Paper sheets and packages */ using System; using System.Threading; class Demo20A02 : IRunnable { public void run() { var semPackage = new Semaphore(0, int.MaxValue); var semSheet = new Semaphore(2, int.MaxValue); ThreadStart makeOneSheet = () => { for (int i = 0; i < 4; ++i) { semSheet.WaitOne(); Console.WriteLine("Make 1 sheet"); semPackage.Release(); } }; ThreadStart combineOnePackage = () => { for (int i = 0; i < 4; ++i) { semPackage.WaitOne(); semPackage.WaitOne(); Console.WriteLine("Combine 2 sheets into 1 package"); Thread.Sleep(1000); semSheet.Release(); semSheet.Release(); } }; new Thread(makeOneSheet).Start(); new Thread(makeOneSheet).Start(); new Thread(combineOnePackage).Start(); } } ================================================ FILE: csharp/demo/demo20a03-semaphore-deadlock.cs ================================================ /* * SEMAPHORES * Version A: Paper sheets and packages */ using System; using System.Threading; class Demo20A03 : IRunnable { public void run() { var semPackage = new Semaphore(0, int.MaxValue); var semSheet = new Semaphore(2, int.MaxValue); ThreadStart makeOneSheet = () => { for (int i = 0; i < 4; ++i) { semSheet.WaitOne(); Console.WriteLine("Make 1 sheet"); semPackage.Release(); } }; ThreadStart combineOnePackage = () => { for (int i = 0; i < 4; ++i) { semPackage.WaitOne(); semPackage.WaitOne(); Console.WriteLine("Combine 2 sheets into 1 package"); Thread.Sleep(1000); semSheet.Release(); // Missing one statement: semSheet.Release() ==> deadlock } }; new Thread(makeOneSheet).Start(); new Thread(makeOneSheet).Start(); new Thread(combineOnePackage).Start(); } } ================================================ FILE: csharp/demo/demo20b-semaphore.cs ================================================ /* * SEMAPHORES * Version B: Tires and chassis */ using System; using System.Threading; class Demo20B : IRunnable { public void run() { var semTire = new Semaphore(4, int.MaxValue); var semChassis = new Semaphore(0, int.MaxValue); ThreadStart makeTire = () => { for (int i = 0; i < 8; ++i) { semTire.WaitOne(); Console.WriteLine("Make 1 tire"); Thread.Sleep(1000); semChassis.Release(); } }; ThreadStart makeChassis = () => { for (int i = 0; i < 4; ++i) { semChassis.WaitOne(); semChassis.WaitOne(); semChassis.WaitOne(); semChassis.WaitOne(); Console.WriteLine("Make 1 chassis"); Thread.Sleep(3000); semTire.Release(); semTire.Release(); semTire.Release(); semTire.Release(); } }; new Thread(makeTire).Start(); new Thread(makeTire).Start(); new Thread(makeChassis).Start(); } } ================================================ FILE: csharp/demo/demo21a01-condition-variable.cs ================================================ /* * CONDITION VARIABLES * * In my opinion, the best mechanism to demonstrate the term "Condition Variable" * in C# is System.Threading.Monitor. * * Monitor.Wait(conditionVariable) ==> Wait * Monitor.Pulse(conditionVariable) ==> Notify * Monitor.PulseAll(conditionVariable) ==> Notify All */ using System; using System.Threading; class Demo21A01 : IRunnable { public void run() { var conditionVar = new object(); ThreadStart foo = () => { Console.WriteLine("foo is waiting..."); lock (conditionVar) { // Waiting for a notification from conditionVar Monitor.Wait(conditionVar); } Console.WriteLine("foo resumed"); }; ThreadStart bar = () => { Thread.Sleep(3000); lock (conditionVar) { // Notify a thread which is waiting for the notification from conditionVar Monitor.Pulse(conditionVar); } }; new Thread(foo).Start(); new Thread(bar).Start(); } } ================================================ FILE: csharp/demo/demo21a02-condition-variable.cs ================================================ /* * CONDITION VARIABLES */ using System; using System.Threading; class Demo21A02 : IRunnable { public void run() { var conditionVar = new object(); ThreadStart foo = () => { Console.WriteLine("foo is waiting..."); lock (conditionVar) { Monitor.Wait(conditionVar); } Console.WriteLine("foo resumed"); }; ThreadStart bar = () => { for (int i = 0; i < 3; ++i) { Thread.Sleep(2000); lock (conditionVar) { Monitor.Pulse(conditionVar); } } }; for (int i = 0; i < 3; ++i) new Thread(foo).Start(); new Thread(bar).Start(); } } ================================================ FILE: csharp/demo/demo21a03-condition-variable.cs ================================================ /* * CONDITION VARIABLES */ using System; using System.Threading; class Demo21A03 : IRunnable { public void run() { var conditionVar = new object(); ThreadStart foo = () => { Console.WriteLine("foo is waiting..."); lock (conditionVar) { Monitor.Wait(conditionVar); } Console.WriteLine("foo resumed"); }; ThreadStart bar = () => { Thread.Sleep(3000); lock (conditionVar) { // Notify all waiting threads Monitor.PulseAll(conditionVar); } }; for (int i = 0; i < 3; ++i) new Thread(foo).Start(); new Thread(bar).Start(); } } ================================================ FILE: csharp/demo/demo21b-condition-variable.cs ================================================ /* * CONDITION VARIABLES */ using System; using System.Threading; class Demo21B : IRunnable { public void run() { new Thread(foo).Start(); new Thread(egg).Start(); } private void foo() { for (; ; ) { lock (Global.conditionVar) { Monitor.Wait(Global.conditionVar); Global.counter += 1; Console.WriteLine("foo counter = " + Global.counter); if (Global.counter >= Global.COUNT_DONE) { return; } } } } private void egg() { for (; ; ) { lock (Global.conditionVar) { if (Global.counter < Global.COUNT_HALT_01 || Global.counter > Global.COUNT_HALT_02) { Monitor.Pulse(Global.conditionVar); } else { Global.counter += 1; Console.WriteLine("egg counter = " + Global.counter); } if (Global.counter >= Global.COUNT_DONE) { return; } } } } private class Global { public static object conditionVar = new object(); public static int counter = 0; public const int COUNT_HALT_01 = 3; public const int COUNT_HALT_02 = 6; public const int COUNT_DONE = 10; } } ================================================ FILE: csharp/demo/demo22a-blocking-queue.cs ================================================ /* * BLOCKING QUEUES * Version A: A slow producer and a fast consumer */ using System; using System.Collections.Concurrent; using System.Threading; class Demo22A : IRunnable { public void run() { // blocking queue with capacity = 2 var queue = new BlockingCollection(2); new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); // Should call queue.Dispose(); } private void producer(BlockingCollection queue) { Thread.Sleep(2000); queue.Add("Alice"); Thread.Sleep(2000); queue.Add("likes"); Thread.Sleep(2000); queue.Add("singing"); } private void consumer(BlockingCollection queue) { string data; for (int i = 0; i < 3; ++i) { Console.WriteLine("\nWaiting for data..."); data = queue.Take(); Console.WriteLine(" " + data); } } } ================================================ FILE: csharp/demo/demo22b-blocking-queue.cs ================================================ /* * BLOCKING QUEUES * Version B: A fast producer and a slow consumer */ using System; using System.Collections.Concurrent; using System.Threading; class Demo22B : IRunnable { public void run() { // blocking queue with capacity = 2 var queue = new BlockingCollection(2); new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); // Should call queue.Dispose(); } private void producer(BlockingCollection queue) { queue.Add("Alice"); queue.Add("likes"); /* * Due to reaching the maximum of capacity = 2, when executing queue.put("singing"), * this thread is going to sleep until the queue removes an element. */ queue.Add("singing"); } private void consumer(BlockingCollection queue) { string data; Thread.Sleep(2000); for (int i = 0; i < 3; ++i) { Console.WriteLine("\nWaiting for data..."); data = queue.Take(); Console.WriteLine(" " + data); } } } ================================================ FILE: csharp/demo/demo23a01-thread-local.cs ================================================ /* * THREAD-LOCAL STORAGE * Introduction */ using System; using System.Threading; class Demo23A01 : IRunnable { public void run() { // Main thread sets value = "APPLE" MyTask.set("APPLE"); Console.WriteLine(MyTask.get()); // Child thread gets value // Expected output: "NOT SET" new Thread(() => { Console.WriteLine(MyTask.get()); }).Start(); } class MyTask { private static ThreadLocal data = new ThreadLocal(); public static string get() { if (data.Value is null) { data.Value = "NOT SET"; } return data.Value; } public static void set(string value) { data.Value = value; } } } ================================================ FILE: csharp/demo/demo23a02-thread-local.cs ================================================ /* * THREAD-LOCAL STORAGE * Introduction * * Use valueFactory function for better initialization. */ using System; using System.Threading; class Demo23A02 : IRunnable { public void run() { // Main thread sets value = "APPLE" MyTask.set("APPLE"); Console.WriteLine(MyTask.get()); // Child thread gets value // Expected output: "NOT SET" new Thread(() => { Console.WriteLine(MyTask.get()); }).Start(); } class MyTask { private static ThreadLocal data = new ThreadLocal(() => "NOT SET"); public static string get() { return data.Value; } public static void set(string value) { data.Value = value; } } } ================================================ FILE: csharp/demo/demo23b-thread-local.cs ================================================ /* * THREAD-LOCAL STORAGE * Avoiding synchronization using Thread-Local Storage */ using System; using System.Collections.Generic; using System.Threading; class Demo23B : IRunnable { public void run() { const int NUM_THREADS = 3; var lstTh = new List(); for (int i = 0; i < NUM_THREADS; ++i) { int t = i; lstTh.Add(new Thread(() => { Thread.Sleep(1000); for (int i = 0; i < 1000; ++i) MyTask.increaseCounter(); Console.WriteLine($"Thread {t} gives counter = {MyTask.getCounter()}"); })); } lstTh.ForEach(th => th.Start()); /* * By using thread-local storage, each thread has its own counter. * So, the counter in one thread is completely independent of each other. * * Thread-local storage helps us to AVOID SYNCHRONIZATION. */ } class Counter { public int value = 0; } class MyTask { private static ThreadLocal thlCounter = new ThreadLocal(() => new Counter()); public static int getCounter() { return thlCounter.Value.value; } public static void increaseCounter() { var counter = thlCounter.Value; ++counter.value; } } } ================================================ FILE: csharp/demo/demo24-volatile.cs ================================================ /* * THE VOLATILE KEYWORD */ using System; using System.Threading; class Demo24 : IRunnable { public void run() { Global.isRunning = true; new Thread(doTask).Start(); Thread.Sleep(6000); Global.isRunning = false; } private void doTask() { while (Global.isRunning) { Console.WriteLine("Running..."); Thread.Sleep(2000); } } class Global { public static volatile bool isRunning; } } ================================================ FILE: csharp/demo/demo25a-atomic.cs ================================================ /* * ATOMIC ACCESS */ using System; using System.Collections.Generic; using System.Threading; class Demo25A : IRunnable { public void run() { ThreadStart doTask = () => { Thread.Sleep(1000); Global.counter += 1; }; var lstTh = new List(); for (int i = 0; i < 1000; ++i) lstTh.Add(new Thread(doTask)); lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); // Unpredictable result Console.WriteLine("counter = " + Global.counter); } class Global { public static volatile int counter = 0; } } ================================================ FILE: csharp/demo/demo25b-atomic.cs ================================================ /* * ATOMIC ACCESS */ using System; using System.Collections.Generic; using System.Threading; class Demo25B : IRunnable { public void run() { ThreadStart doTask = () => { Thread.Sleep(1000); Interlocked.Increment(ref Global.counter); }; var lstTh = new List(); for (int i = 0; i < 1000; ++i) lstTh.Add(new Thread(doTask)); lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); // counter = 1000 Console.WriteLine("counter = " + Global.counter); } class Global { public static volatile int counter = 0; } } ================================================ FILE: csharp/demoex/demoex-async-future-a01.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; #pragma warning disable CS1998 class DemoExAsyncA01 : IRunnable { public async void run() { var task = doSomething("cleaning house"); while (false == task.IsCompleted) { // Waiting... } } private async Task doSomething(string taskName) { Console.WriteLine("I am doing " + taskName); } } #pragma warning restore CS1998 ================================================ FILE: csharp/demoex/demoex-async-future-a02.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; #pragma warning disable CS1998 class DemoExAsyncA02 : IRunnable { public async void run() { var task = getSquared(7); while (false == task.IsCompleted) { // Waiting... } int result = await task; Console.WriteLine(result); } private async Task getSquared(int x) { return x * x; } } #pragma warning restore CS1998 ================================================ FILE: csharp/demoex/demoex-async-future-a03.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; #pragma warning disable CS1998 class DemoExAsyncA03 : IRunnable { public async void run() { var task = getSquared(7); // Waiting for task completion task.Wait(); int result = await task; Console.WriteLine(result); } private async Task getSquared(int x) { return x * x; } } #pragma warning restore CS1998 ================================================ FILE: csharp/demoex/demoex-async-future-a04.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; class DemoExAsyncA04 : IRunnable { public async void run() { var task = cookEggs(); // Waiting for task completion task.Wait(); string result = await task; Console.WriteLine(result); } private async Task cookEggs() { Console.WriteLine("I am cooking eggs"); // Cooking eggs in 2 seconds await Task.Delay(2000); /* * Avoid using System.Threading.Thread.Sleep(2000) * Because one thread can work on multiple tasks at once. * * Thread.Sleep pauses current thread. * Task.Delay pauses current task. */ return "fried eggs"; } } ================================================ FILE: csharp/demoex/demoex-async-future-a05.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; class DemoExAsyncA05 : IRunnable { public void run() { var task = cookEggs(); /* * By calling property "Task.Result", app should wait if necessary for the computation to complete * and then retrieves its result. * * So, we can omit the statement "task.Wait()" */ string result = task.Result; Console.WriteLine(result); } private async Task cookEggs() { Console.WriteLine("I am cooking eggs"); // Cooking eggs in 2 seconds await Task.Delay(2000); return "fried eggs"; } } ================================================ FILE: csharp/demoex/demoex-async-future-b01.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK * * The app takes about 1000 miliseconds to run * because all 3 tasks are running simultaneously. */ using System; using System.Threading.Tasks; class DemoExAsyncB01 : IRunnable { public void run() { var timePointStart = DateTime.Now; var taskA = doAction("cooking eggs"); var taskB = doAction("making coffee"); var taskC = doAction("watching movies"); taskA.Wait(); taskB.Wait(); taskC.Wait(); var timePointEnd = DateTime.Now; var duration = (timePointEnd - timePointStart).TotalMilliseconds; Console.WriteLine($"Total time: {duration} millis"); } private async Task doAction(string actionName) { Console.WriteLine(actionName); // Doing action in one second... await Task.Delay(1000); } } ================================================ FILE: csharp/demoex/demoex-async-future-b02.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK * * The app takes about 3000 miliseconds to run * because each method "Task.Wait" pauses app until the task finishes. */ using System; using System.Threading.Tasks; class DemoExAsyncB02 : IRunnable { public void run() { var timePointStart = DateTime.Now; var taskA = doAction("cooking eggs"); taskA.Wait(); var taskB = doAction("making coffee"); taskB.Wait(); var taskC = doAction("watching movies"); taskC.Wait(); var timePointEnd = DateTime.Now; var duration = (timePointEnd - timePointStart).TotalMilliseconds; Console.WriteLine($"Total time: {duration} millis"); } private async Task doAction(string actionName) { Console.WriteLine(actionName); // Doing action in one second... await Task.Delay(1000); } } ================================================ FILE: csharp/demoex/demoex-async-future-b03.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; class DemoExAsyncB03 : IRunnable { public void run() { var taskCookingEggs = cookEggs(); var taskMakingCoffee = makeCoffee(); string resultEggs = taskCookingEggs.Result; string resultCoffee = taskMakingCoffee.Result; Console.WriteLine("Done!"); Console.WriteLine(resultEggs); Console.WriteLine(resultCoffee); } private async Task cookEggs() { Console.WriteLine("I am cooking eggs"); // Cooking eggs in two seconds... await Task.Delay(2000); return "fried eggs"; } private async Task makeCoffee() { Console.WriteLine("I am making coffee"); // Making coffee in four seconds... await Task.Delay(4000); return "a cup of coffee"; } } ================================================ FILE: csharp/demoex/demoex-async-future-b04.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; class DemoExAsyncB04 : IRunnable { public void run() { var taskValidation = validate("John"); bool result = taskValidation.Result; if (result) Console.WriteLine("User can view movies."); else Console.WriteLine("Age must be >= 18 to view movies."); } private async Task validate(string userName) { int userAge = await queryUserAge(userName); Console.WriteLine("Validating..."); // Validating in two seconds... await Task.Delay(2000); return userAge >= 18; } private async Task queryUserAge(string userName) { Console.WriteLine("Querying userAge in database..."); // Querying database in two seconds... await Task.Delay(2000); if (userName == "Thanh") return 26; else return 17; } } ================================================ FILE: csharp/demoex/demoex-async-future-c01.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; class DemoExAsyncC01 : IRunnable { public void run() { var task = getSquared(7) .ContinueWith((previousTask) => getDiv2(previousTask.Result)) .Unwrap(); int result = task.Result; Console.WriteLine(result); } private async Task getSquared(int x) { await Task.Delay(100); return x * x; } private async Task getDiv2(int x) { await Task.Delay(100); return x / 2; } } ================================================ FILE: csharp/demoex/demoex-async-future-c02.cs ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ using System; using System.Threading.Tasks; class DemoExAsyncC02 : IRunnable { public void run() { var task = getSquared(7) .ContinueWith((previousTask) => getDiv2(previousTask.Result)) .Unwrap() .ContinueWith((previousTask) => Console.WriteLine(previousTask.Result)); task.Wait(); } private async Task getSquared(int x) { await Task.Delay(100); return x * x; } private async Task getDiv2(int x) { await Task.Delay(100); return x / 2; } } ================================================ FILE: csharp/exercise/exer01a-max-div.cs ================================================ /* * MAXIMUM NUMBER OF DIVISORS */ using System; class Exer01A : IRunnable { public void run() { const int RANGE_START = 1; const int RANGE_END = 100000; int resValue = 0; int resNumDiv = 0; // number of divisors of result var tpStart = DateTime.Now; for (int i = RANGE_START; i <= RANGE_END; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } var timeElapsed = (DateTime.Now - tpStart).TotalSeconds; Console.WriteLine("The integer which has largest number of divisors is " + resValue); Console.WriteLine("The largest number of divisor is " + resNumDiv); Console.WriteLine("Time elapsed = " + timeElapsed); } } ================================================ FILE: csharp/exercise/exer01b-max-div.cs ================================================ /* * MAXIMUM NUMBER OF DIVISORS */ using System; using System.Collections.Generic; using System.Threading; using System.Linq; class Exer01B : IRunnable { public void run() { const int RANGE_START = 1; const int RANGE_END = 100000; const int NUM_THREADS = 8; var lstWorkerArg = prepareArg(RANGE_START, RANGE_END, NUM_THREADS); var lstWorkerRes = new List(); var lstTh = new List(); for (int ith = 0; ith < NUM_THREADS; ++ith) { var arg = lstWorkerArg[ith]; lstTh.Add(new Thread(() => { int resValue = 0; int resNumDiv = 0; for (int i = arg.iStart; i <= arg.iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } lock (lstWorkerRes) { lstWorkerRes.Add(new WorkerResult(resValue, resNumDiv)); } })); } var tpStart = DateTime.Now; lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); var finalRes = lstWorkerRes.OrderByDescending(res => res.numDiv).First(); var timeElapsed = (DateTime.Now - tpStart).TotalSeconds; Console.WriteLine("The integer which has largest number of divisors is " + finalRes.value); Console.WriteLine("The largest number of divisor is " + finalRes.numDiv); Console.WriteLine("Time elapsed = " + timeElapsed); /* * BETTER WAY (avoiding synchronization of lstWorkerRes): * * - Initialize lstWorkerRes with null objects. * Of course, the number of objects is NUM_THREADS. * * - In thread function: * lstWorkerRes[threadIndex] = new WorkerResult(resValue, resNumDiv); */ } private List prepareArg(int rangeStart, int rangeEnd, int numThreads) { int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; var lstWorkerArg = new List(); for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg.Add(new WorkerArg(rangeA, rangeB)); } return lstWorkerArg; } readonly struct WorkerArg { public readonly int iStart, iEnd; public WorkerArg(int iStart, int iEnd) => (this.iStart, this.iEnd) = (iStart, iEnd); } readonly struct WorkerResult { public readonly int value, numDiv; public WorkerResult(int value, int numDiv) => (this.value, this.numDiv) = (value, numDiv); } } ================================================ FILE: csharp/exercise/exer01c-max-div.cs ================================================ /* * MAXIMUM NUMBER OF DIVISORS */ using System; using System.Collections.Generic; using System.Threading; class Exer01C : IRunnable { public void run() { const int RANGE_START = 1; const int RANGE_END = 100000; const int NUM_THREADS = 8; var lstWorkerArg = prepareArg(RANGE_START, RANGE_END, NUM_THREADS); var lstTh = new List(); var finalRes = new WorkerResult(); for (int ith = 0; ith < NUM_THREADS; ++ith) { var arg = lstWorkerArg[ith]; lstTh.Add(new Thread(() => { int resValue = 0; int resNumDiv = 0; for (int i = arg.iStart; i <= arg.iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } finalRes.update(resValue, resNumDiv); })); } var tpStart = DateTime.Now; lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); var timeElapsed = (DateTime.Now - tpStart).TotalSeconds; Console.WriteLine("The integer which has largest number of divisors is " + finalRes.value); Console.WriteLine("The largest number of divisor is " + finalRes.numDiv); Console.WriteLine("Time elapsed = " + timeElapsed); } private List prepareArg(int rangeStart, int rangeEnd, int numThreads) { int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; var lstWorkerArg = new List(); for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg.Add(new WorkerArg(rangeA, rangeB)); } return lstWorkerArg; } readonly struct WorkerArg { public readonly int iStart, iEnd; public WorkerArg(int iStart, int iEnd) => (this.iStart, this.iEnd) = (iStart, iEnd); } class WorkerResult { public int value = 0; public int numDiv = 0; public void update(int value, int numDiv) { lock (this) { if (this.numDiv < numDiv) { this.numDiv = numDiv; this.value = value; } } } } } ================================================ FILE: csharp/exercise/exer02a01-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A01: 1 slow producer, 1 fast consumer */ using System; using System.Collections.Concurrent; using System.Threading; class Exer02A01 : IRunnable { public void run() { var queue = new BlockingCollection(1); new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); } private void producer(BlockingCollection queue) { int i = 1; for (; ; ++i) { queue.Add(i); Thread.Sleep(1000); } } private void consumer(BlockingCollection queue) { for (; ; ) { int data = queue.Take(); Console.WriteLine("Consumer " + data); } } } ================================================ FILE: csharp/exercise/exer02a02-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A02: 2 slow producers, 1 fast consumer */ using System; using System.Collections.Concurrent; using System.Threading; class Exer02A02 : IRunnable { public void run() { var queue = new BlockingCollection(1); new Thread(() => producer(queue)).Start(); new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); } private void producer(BlockingCollection queue) { int i = 1; for (; ; ++i) { queue.Add(i); Thread.Sleep(1000); } } private void consumer(BlockingCollection queue) { for (; ; ) { int data = queue.Take(); Console.WriteLine("Consumer " + data); } } } ================================================ FILE: csharp/exercise/exer02a03-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A03: 1 slow producer, 2 fast consumers */ using System; using System.Collections.Concurrent; using System.Threading; class Exer02A03 : IRunnable { public void run() { var queue = new BlockingCollection(1); new Thread(() => producer(queue)).Start(); new Thread(() => consumer("foo", queue)).Start(); new Thread(() => consumer("bar", queue)).Start(); } private void producer(BlockingCollection queue) { int i = 1; for (; ; ++i) { queue.Add(i); Thread.Sleep(1000); } } private void consumer(string name, BlockingCollection queue) { for (; ; ) { int data = queue.Take(); Console.WriteLine($"Consumer {name}: {data}"); } } } ================================================ FILE: csharp/exercise/exer02a04-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A04: Multiple fast producers, multiple slow consumers */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; class Exer02A04 : IRunnable { public void run() { var queue = new BlockingCollection(5); const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 2; var lstThProducer = new List(); var lstThConsumer = new List(); for (int i = 0; i < NUM_PRODUCERS; ++i) { int t = i; lstThProducer.Add(new Thread(() => producer(queue, t * 1000))); } for (int i = 0; i < NUM_CONSUMERS; ++i) { lstThConsumer.Add(new Thread(() => consumer(queue))); } lstThProducer.ForEach(th => th.Start()); lstThConsumer.ForEach(th => th.Start()); } private void producer(BlockingCollection queue, int startValue) { int i = 1; if (startValue == 2000) { Console.WriteLine("FUCK IT"); } for (; ; ++i) { queue.Add(i + startValue); } } private void consumer(BlockingCollection queue) { for (; ; ) { int data = queue.Take(); Console.WriteLine("Consumer " + data); Thread.Sleep(1000); } } } ================================================ FILE: csharp/exercise/exer02b01-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B01: 1 slow producer, 1 fast consumer */ using System; using System.Collections.Generic; using System.Threading; class Exer02B01 : IRunnable { public void run() { var semFill = new Semaphore(0, int.MaxValue); // item produced var semEmpty = new Semaphore(1, int.MaxValue); // remaining space in queue var queue = new Queue(); new Thread(() => producer(semFill, semEmpty, queue)).Start(); new Thread(() => consumer(semFill, semEmpty, queue)).Start(); } private void producer(Semaphore semFill, Semaphore semEmpty, Queue queue) { int i = 1; for (; ; ++i) { semEmpty.WaitOne(); queue.Enqueue(i); Thread.Sleep(1000); semFill.Release(); } } private void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (; ; ) { semFill.WaitOne(); int data = queue.Dequeue(); Console.WriteLine("Consumer " + data); semEmpty.Release(); } } } ================================================ FILE: csharp/exercise/exer02b02-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B02: 2 slow producers, 1 fast consumer */ using System; using System.Collections.Generic; using System.Threading; class Exer02B02 : IRunnable { public void run() { var semFill = new Semaphore(0, int.MaxValue); // item produced var semEmpty = new Semaphore(1, int.MaxValue); // remaining space in queue var queue = new Queue(); new Thread(() => producer(semFill, semEmpty, queue, 0)).Start(); new Thread(() => producer(semFill, semEmpty, queue, 1000)).Start(); new Thread(() => consumer(semFill, semEmpty, queue)).Start(); } private void producer(Semaphore semFill, Semaphore semEmpty, Queue queue, int startValue) { int i = 1; for (; ; ++i) { semEmpty.WaitOne(); queue.Enqueue(i + startValue); Thread.Sleep(1000); semFill.Release(); } } private void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (; ; ) { semFill.WaitOne(); int data = queue.Dequeue(); Console.WriteLine("Consumer " + data); semEmpty.Release(); } } } ================================================ FILE: csharp/exercise/exer02b03-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B03: 2 fast producers, 1 slow consumer */ using System; using System.Collections.Generic; using System.Threading; class Exer02B03 : IRunnable { public void run() { var semFill = new Semaphore(0, int.MaxValue); // item produced var semEmpty = new Semaphore(1, int.MaxValue); // remaining space in queue var queue = new Queue(); new Thread(() => producer(semFill, semEmpty, queue, 0)).Start(); new Thread(() => producer(semFill, semEmpty, queue, 1000)).Start(); new Thread(() => consumer(semFill, semEmpty, queue)).Start(); } private void producer(Semaphore semFill, Semaphore semEmpty, Queue queue, int startValue) { int i = 1; for (; ; ++i) { semEmpty.WaitOne(); queue.Enqueue(i + startValue); semFill.Release(); } } private void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (; ; ) { semFill.WaitOne(); int data = queue.Dequeue(); Console.WriteLine("Consumer " + data); Thread.Sleep(1000); semEmpty.Release(); } } } ================================================ FILE: csharp/exercise/exer02b04-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B04: Multiple fast producers, multiple slow consumers */ using System; using System.Collections.Generic; using System.Threading; class Exer02B04 : IRunnable { public void run() { var semFill = new Semaphore(0, int.MaxValue); // item produced var semEmpty = new Semaphore(1, int.MaxValue); // remaining space in queue var queue = new Queue(); const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 2; var lstThProducer = new List(); var lstThConsumer = new List(); for (int i = 0; i < NUM_PRODUCERS; ++i) { int t = i; lstThProducer.Add(new Thread(() => producer(semFill, semEmpty, queue, t * 1000))); } for (int i = 0; i < NUM_CONSUMERS; ++i) { lstThConsumer.Add(new Thread(() => consumer(semFill, semEmpty, queue))); } lstThProducer.ForEach(th => th.Start()); lstThConsumer.ForEach(th => th.Start()); } private void producer(Semaphore semFill, Semaphore semEmpty, Queue queue, int startValue) { int i = 1; for (; ; ++i) { semEmpty.WaitOne(); queue.Enqueue(i + startValue); semFill.Release(); } } private void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (; ; ) { semFill.WaitOne(); int data = queue.Dequeue(); Console.WriteLine("Consumer " + data); Thread.Sleep(1000); semEmpty.Release(); } } } ================================================ FILE: csharp/exercise/exer02c-producer-consumer.cs ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE C: USING CONDITION VARIABLES & CUSTOM MONITORS * Multiple fast producers, multiple slow consumers */ using System; using System.Collections.Generic; using System.Threading; class Exer02C : IRunnable { public void run() { var monitor = new ProdConsMonitor(); var queue = new Queue(); const int MAX_QUEUE_SIZE = 6; const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 2; monitor.init(MAX_QUEUE_SIZE, queue); var lstProd = new List(); var lstCons = new List(); for (int i = 0; i < NUM_PRODUCERS; ++i) { int t = i; lstProd.Add(new Thread(() => producer(monitor, t * 1000))); } for (int i = 0; i < NUM_CONSUMERS; ++i) { lstCons.Add(new Thread(() => consumer(monitor))); } lstProd.ForEach(th => th.Start()); lstCons.ForEach(th => th.Start()); } private void producer(ProdConsMonitor monitor, int startValue) { int i = 1; for (; ; ++i) { monitor.add(i + startValue); } } private void consumer(ProdConsMonitor monitor) { for (; ; ) { int data = monitor.remove(); Console.WriteLine("Consumer " + data); Thread.Sleep(1000); } } class ProdConsMonitor { private Queue queue; private int maxQueueSize; private object condFull = new object(); private object condEmpty = new object(); public void init(int maxQueueSize, Queue queue) { this.maxQueueSize = maxQueueSize; this.queue = queue; } public void add(T item) { lock (condFull) { while (queue.Count == maxQueueSize) Monitor.Wait(condFull); lock (queue) { queue.Enqueue(item); } } lock (condEmpty) { if (queue.Count == 1) Monitor.Pulse(condEmpty); } } public T remove() { T item = default; lock (condEmpty) { while (queue.Count == 0) Monitor.Wait(condEmpty); lock (queue) { item = queue.Dequeue(); } } lock (condFull) { if (queue.Count == maxQueueSize - 1) Monitor.Pulse(condFull); } return item; } } } ================================================ FILE: csharp/exercise/exer03a-readers-writers.cs ================================================ /* * THE READERS-WRITERS PROBLEM * Solution for the first readers-writers problem */ using System; using System.Collections.Generic; using System.Threading; class Exer03A : IRunnable { public void run() { const int NUM_READER = 8; const int NUM_WRITER = 6; var rand = new Random(); var lstThReader = new List(); var lstThWriter = new List(); for (int i = 0; i < NUM_READER; ++i) lstThReader.Add(new Thread(() => doTaskReader(rand.Next(3)))); for (int i = 0; i < NUM_WRITER; ++i) lstThWriter.Add(new Thread(() => doTaskWriter(rand.Next(3)))); lstThReader.ForEach(th => th.Start()); lstThWriter.ForEach(th => th.Start()); } private static void doTaskWriter(int delayTime) { var rand = new Random(); Thread.Sleep(1000 * delayTime); Global.mutResource.WaitOne(); Global.resource = rand.Next(100); Console.WriteLine("Write " + Global.resource); Global.mutResource.Release(); } private void doTaskReader(int delayTime) { Thread.Sleep(1000 * delayTime); // Increase reader count lock (Global.mutReaderCount) { Global.readerCount += 1; if (1 == Global.readerCount) Global.mutResource.WaitOne(); } // Do the reading int data = Global.resource; Console.WriteLine("Read " + data); // Decrease reader count lock (Global.mutReaderCount) { Global.readerCount -= 1; if (0 == Global.readerCount) Global.mutResource.Release(); } } class Global { public static Semaphore mutResource = new Semaphore(1, int.MaxValue); public static object mutReaderCount = new object(); public static volatile int resource = 0; public static int readerCount = 0; } } ================================================ FILE: csharp/exercise/exer03b-readers-writers.cs ================================================ /* * THE READERS-WRITERS PROBLEM * Solution for the third readers-writers problem */ using System; using System.Collections.Generic; using System.Threading; class Exer03B : IRunnable { public void run() { const int NUM_READER = 8; const int NUM_WRITER = 6; var rand = new Random(); var lstThReader = new List(); var lstThWriter = new List(); for (int i = 0; i < NUM_READER; ++i) lstThReader.Add(new Thread(() => doTaskReader(rand.Next(3)))); for (int i = 0; i < NUM_WRITER; ++i) lstThWriter.Add(new Thread(() => doTaskWriter(rand.Next(3)))); lstThReader.ForEach(th => th.Start()); lstThWriter.ForEach(th => th.Start()); } private static void doTaskWriter(int delayTime) { var rand = new Random(); Thread.Sleep(1000 * delayTime); lock (Global.mutServiceQueue) { Global.mutResource.WaitOne(); } Global.resource = rand.Next(100); Console.WriteLine("Write " + Global.resource); Global.mutResource.Release(); } private void doTaskReader(int delayTime) { Thread.Sleep(1000 * delayTime); lock (Global.mutServiceQueue) { // Increase reader count lock (Global.mutReaderCount) { Global.readerCount += 1; if (1 == Global.readerCount) Global.mutResource.WaitOne(); } } // Do the reading int data = Global.resource; Console.WriteLine("Read " + data); // Decrease reader count lock (Global.mutReaderCount) { Global.readerCount -= 1; if (0 == Global.readerCount) Global.mutResource.Release(); } } class Global { public static object mutServiceQueue = new object(); public static Semaphore mutResource = new Semaphore(1, int.MaxValue); public static object mutReaderCount = new object(); public static volatile int resource = 0; public static int readerCount = 0; } } ================================================ FILE: csharp/exercise/exer04a-dining-philosophers.cs ================================================ /* * THE DINING PHILOSOPHERS PROBLEM */ using System; using System.Collections.Generic; using System.Threading; class Exer04A : IRunnable { public void run() { const int NUM_PHILOSOPHERS = 5; var chopstick = new Semaphore[NUM_PHILOSOPHERS]; for (int i = 0; i < NUM_PHILOSOPHERS; ++i) chopstick[i] = new Semaphore(1, NUM_PHILOSOPHERS + 1); var lstTh = new List(); for (int ith = 0; ith < NUM_PHILOSOPHERS; ++ith) { int i = ith; lstTh.Add(new Thread(() => { int n = NUM_PHILOSOPHERS; Thread.Sleep(1000); chopstick[i].WaitOne(); chopstick[(i + 1) % n].WaitOne(); Console.WriteLine($"Philosopher #{i} is eating the rice"); chopstick[(i + 1) % n].Release(); chopstick[i].Release(); })); } lstTh.ForEach(th => th.Start()); } } ================================================ FILE: csharp/exercise/exer04b-dining-philosophers.cs ================================================ /* * THE DINING PHILOSOPHERS PROBLEM */ using System; using System.Collections.Generic; using System.Threading; class Exer04B : IRunnable { public void run() { const int NUM_PHILOSOPHERS = 5; var chopstick = new object[NUM_PHILOSOPHERS]; for (int i = 0; i < NUM_PHILOSOPHERS; ++i) chopstick[i] = new object(); var lstTh = new List(); for (int ith = 0; ith < NUM_PHILOSOPHERS; ++ith) { int i = ith; lstTh.Add(new Thread(() => { int n = NUM_PHILOSOPHERS; Thread.Sleep(1000); lock (chopstick[i]) { lock (chopstick[(i + 1) % n]) { Console.WriteLine($"Philosopher #{i} is eating the rice"); } } })); } lstTh.ForEach(th => th.Start()); } } ================================================ FILE: csharp/exercise/exer05-util.cs ================================================ using System; class Exer05Util { public static double getScalarProduct(double[] u, double[] v) { double sum = 0; int sizeVector = u.Length; for (int i = sizeVector - 1; i >= 0; --i) sum += u[i] * v[i]; return sum; } public static double[][] getTransposeMatrix(double[][] input) { int numRow = input.Length; int numCol = input[0].Length; var output = create2dArray(numCol, numRow); for (int i = 0; i < numRow; ++i) for (int j = 0; j < numCol; ++j) output[j][i] = input[i][j]; return output; } public static void printMatrix(double[][] mat) { foreach (var row in mat) { foreach (var value in row) Console.Write($"\t{value:0.0}"); Console.WriteLine(); } } public static T[][] create2dArray(int height, int width) { T[][] res = new T[height][]; for (int i = 0; i < height; ++i) res[i] = new T[width]; return res; } } ================================================ FILE: csharp/exercise/exer05a-product-matrix-vector.cs ================================================ /* * MATRIX-VECTOR MULTIPLICATION */ using System; using System.Collections.Generic; using System.Threading; class Exer05A : IRunnable { public void run() { double[][] A = new double[][] { new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 }, new double[] { 7, 8, 9 } }; double[] b = { 3, -1, 0 }; var result = getProduct(A, b); Array.ForEach(result, Console.WriteLine); } private double[] getProduct(double[][] mat, double[] vec) { // Assume that size of mat and vec are both correct int sizeRowMat = mat.Length; // int sizeColMat = mat[0].Length; // int sizeVec = vec.Length; var result = new double[sizeRowMat]; var lstTh = new List(); for (int ith = 0; ith < sizeRowMat; ++ith) { int i = ith; lstTh.Add(new Thread(() => { var u = mat[i]; var v = vec; result[i] = Exer05Util.getScalarProduct(u, v); })); } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); return result; } } ================================================ FILE: csharp/exercise/exer05b-product-matrix-matrix.cs ================================================ /* * MATRIX-MATRIX MULTIPLICATION (DOT PRODUCT) */ using System; using System.Collections.Generic; using System.Threading; class Exer05B : IRunnable { public void run() { double[][] A = new double[][] { new double[] { 1, 3, 5 }, new double[] { 2, 4, 6 } }; double[][] B = new double[][] { new double[] { 1, 0, 1, 0 }, new double[] { 0, 1, 0, 1 }, new double[] { 1, 0, 0, -2 } }; double[][] result = getProduct(A, B); Exer05Util.printMatrix(result); } private double[][] getProduct(double[][] matA, double[][] matB) { // Assume that the size of A and B are both correct int sizeRowA = matA.Length; int sizeColB = matB[0].Length; double[][] matBT = Exer05Util.getTransposeMatrix(matB); var result = Exer05Util.create2dArray(sizeRowA, sizeColB); var lstTh = new List(); for (int ith = 0; ith < sizeRowA; ++ith) { int i = ith; for (int jth = 0; jth < sizeColB; ++jth) { int j = jth; var u = matA[i]; var v = matBT[j]; lstTh.Add(new Thread(() => { result[i][j] = Exer05Util.getScalarProduct(u, v); })); } } lstTh.ForEach(th => th.Start()); lstTh.ForEach(th => th.Join()); return result; } } ================================================ FILE: csharp/exercise/exer06a-blocking-queue.cs ================================================ /* * BLOCKING QUEUE IMPLEMENTATION * Version A: Synchronous queues */ using System; using System.Threading; class Exer06A : IRunnable { public void run() { var queue = new MySynchronousQueue(); new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); } private void producer(MySynchronousQueue queue) { string[] arr = { "lorem", "ipsum", "dolor" }; foreach (var data in arr) { Console.WriteLine($"Producer: {data}"); queue.put(data); Console.WriteLine($"Producer: {data}\t\t\t[done]"); } } private void consumer(MySynchronousQueue queue) { string data; Thread.Sleep(5000); for (int i = 0; i < 3; ++i) { data = queue.take(); Console.WriteLine($"\tConsumer: {data}"); } } class MySynchronousQueue { private Semaphore semPut = new Semaphore(1, int.MaxValue); private Semaphore semTake = new Semaphore(0, int.MaxValue); private T element = default; public void put(T value) { semPut.WaitOne(); element = value; semTake.Release(); } public T take() { semTake.WaitOne(); T result = element; element = default; semPut.Release(); return result; } } } ================================================ FILE: csharp/exercise/exer06b01-blocking-queue.cs ================================================ /* * BLOCKING QUEUE IMPLEMENTATION * Version B01: General blocking queues * Underlying mechanism: Semaphores */ using System; using System.Collections.Generic; using System.Threading; class Exer06B01 : IRunnable { public void run() { var queue = new MyBlockingQueue(2); // capacity = 2 new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); } private void producer(MyBlockingQueue queue) { string[] arr = { "nice", "to", "meet", "you" }; foreach (var data in arr) { Console.WriteLine($"Producer: {data}"); queue.put(data); Console.WriteLine($"Producer: {data}\t\t\t[done]"); } } private void consumer(MyBlockingQueue queue) { string data; Thread.Sleep(5000); for (int i = 0; i < 4; ++i) { data = queue.take(); Console.WriteLine($"\tConsumer: {data}"); if (0 == i) Thread.Sleep(5000); } } ////////////////////////////////////////////// class MyBlockingQueue { private Semaphore semRemain = null; private Semaphore semFill = null; private int capacity = 0; private Queue queue = null; public MyBlockingQueue(int capacity) { if (capacity <= 0) throw new ArgumentException("capacity must be a positive integer"); semRemain = new Semaphore(capacity, capacity); semFill = new Semaphore(0, capacity); this.capacity = capacity; queue = new Queue(); } public void put(T value) { semRemain.WaitOne(); lock (queue) { queue.Enqueue(value); } semFill.Release(); } public T take() { T result = default; semFill.WaitOne(); lock (queue) { result = queue.Dequeue(); } semRemain.Release(); return result; } } } ================================================ FILE: csharp/exercise/exer06b02-blocking-queue.cs ================================================ /* * BLOCKING QUEUE IMPLEMENTATION * Version B02: General blocking queues * Underlying mechanism: Condition variables */ using System; using System.Collections.Generic; using System.Threading; class Exer06B02 : IRunnable { public void run() { var queue = new MyBlockingQueue(2); // capacity = 2 new Thread(() => producer(queue)).Start(); new Thread(() => consumer(queue)).Start(); } private void producer(MyBlockingQueue queue) { string[] arr = { "nice", "to", "meet", "you" }; foreach (var data in arr) { queue.put(data); Console.WriteLine($"Producer: {data}\t\t\t[done]"); } } private void consumer(MyBlockingQueue queue) { string data; Thread.Sleep(5000); for (int i = 0; i < 4; ++i) { data = queue.take(); Console.WriteLine($"\tConsumer: {data}"); if (0 == i) Thread.Sleep(5000); } } ////////////////////////////////////////////// class MyBlockingQueue { private object condEmpty = new object(); private object condFull = new object(); private int capacity = 0; private Queue queue = null; public MyBlockingQueue(int capacity) { if (capacity <= 0) throw new ArgumentException("capacity must be a positive integer"); this.capacity = capacity; queue = new Queue(); } public void put(T value) { lock (condFull) { while (queue.Count >= capacity) { // Queue is full, must wait for 'take' Monitor.Wait(condFull); } lock (queue) { queue.Enqueue(value); } } lock (condEmpty) { Monitor.Pulse(condEmpty); } } public T take() { T result = default; lock (condEmpty) { while (0 == queue.Count) { // Queue is empty, must wait for 'put' Monitor.Wait(condEmpty); } lock (queue) { result = queue.Dequeue(); } } lock (condFull) { Monitor.Pulse(condFull); } return result; } } } ================================================ FILE: csharp/exercise/exer07a-data-server.cs ================================================ /* * THE DATA SERVER PROBLEM * Version A: Solving the problem using a condition variable */ using System; using System.Threading; class Exer07A : IRunnable { public void run() { var server = new DataServer(); server.processRequest(); } private class DataServer { private class Counter { public int value; public Counter(int value) { this.value = value; } } public void processRequest() { var lstFileName = new String[] { "foo.html", "bar.json" }; var counter = new Counter(lstFileName.Length); // The server checks auth user while reading files, concurrently new Thread(() => processFiles(lstFileName, counter)).Start(); checkAuthUser(); // The server waits for completion of loading files lock (counter) { while (counter.value > 0) { Monitor.Wait(counter, 10000); // timeout = 10 seconds } } Console.WriteLine("\nNow user is authorized and files are loaded"); Console.WriteLine("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { Console.WriteLine("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... Thread.Sleep(20000); Console.WriteLine("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, Counter counter) { foreach (var fileName in lstFileName) { // Read file Console.WriteLine("[ ReadFile ] Start " + fileName); Thread.Sleep(10000); Console.WriteLine("[ ReadFile ] Done " + fileName); lock (counter) { --counter.value; Monitor.Pulse(counter); } // Write log into disk Thread.Sleep(5000); Console.WriteLine("[ WriteLog ]"); } } } } ================================================ FILE: csharp/exercise/exer07b-data-server.cs ================================================ /* * THE DATA SERVER PROBLEM * Version B: Solving the problem using a semaphore */ using System; using System.Threading; class Exer07B : IRunnable { public void run() { var server = new DataServer(); server.processRequest(); } private class DataServer { public void processRequest() { var lstFileName = new String[] { "foo.html", "bar.json" }; var sem = new Semaphore(0, int.MaxValue); // The server checks auth user while reading files, concurrently new Thread(() => processFiles(lstFileName, sem)).Start(); checkAuthUser(); // The server waits for completion of loading files for (int i = lstFileName.Length; i > 0; --i) { sem.WaitOne(); } Console.WriteLine("\nNow user is authorized and files are loaded"); Console.WriteLine("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { Console.WriteLine("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... Thread.Sleep(20000); Console.WriteLine("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, Semaphore sem) { foreach (var fileName in lstFileName) { // Read file Console.WriteLine("[ ReadFile ] Start " + fileName); Thread.Sleep(10000); Console.WriteLine("[ ReadFile ] Done " + fileName); sem.Release(); // Write log into disk Thread.Sleep(5000); Console.WriteLine("[ WriteLog ]"); } } } } ================================================ FILE: csharp/exercise/exer07c-data-server.cs ================================================ /* * THE DATA SERVER PROBLEM * Version C: Solving the problem using a count-down latch */ using System; using System.Threading; class Exer07C : IRunnable { public void run() { var server = new DataServer(); server.processRequest(); } private class DataServer { public void processRequest() { var lstFileName = new String[] { "foo.html", "bar.json" }; var readFileLatch = new CountdownEvent(lstFileName.Length); // The server checks auth user while reading files, concurrently new Thread(() => processFiles(lstFileName, readFileLatch)).Start(); checkAuthUser(); // The server waits for completion of loading files readFileLatch.Wait(); Console.WriteLine("\nNow user is authorized and files are loaded"); Console.WriteLine("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { Console.WriteLine("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... Thread.Sleep(20000); Console.WriteLine("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, CountdownEvent latch) { foreach (var fileName in lstFileName) { // Read file Console.WriteLine("[ ReadFile ] Start " + fileName); Thread.Sleep(10000); Console.WriteLine("[ ReadFile ] Done " + fileName); latch.Signal(); // Write log into disk Thread.Sleep(5000); Console.WriteLine("[ WriteLog ]"); } } } } ================================================ FILE: csharp/exercise/exer07d-data-server.cs ================================================ /* * THE DATA SERVER PROBLEM * Version D: Solving the problem using a blocking queue */ using System; using System.Collections.Concurrent; using System.Threading; class Exer07D : IRunnable { public void run() { var server = new DataServer(); server.processRequest(); } private class DataServer { public void processRequest() { var lstFileName = new String[] { "foo.html", "bar.json" }; var queue = new BlockingCollection(); // The server checks auth user while reading files, concurrently new Thread(() => processFiles(lstFileName, queue)).Start(); checkAuthUser(); // The server waits for completion of loading files for (int i = lstFileName.Length; i > 0; --i) { queue.Take(); } Console.WriteLine("\nNow user is authorized and files are loaded"); Console.WriteLine("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { Console.WriteLine("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... Thread.Sleep(20000); Console.WriteLine("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, BlockingCollection queue) { foreach (var fileName in lstFileName) { // Read file Console.WriteLine("[ ReadFile ] Start " + fileName); Thread.Sleep(10000); Console.WriteLine("[ ReadFile ] Done " + fileName); queue.Add(fileName); // You may put file data here // Write log into disk Thread.Sleep(5000); Console.WriteLine("[ WriteLog ]"); } } } } ================================================ FILE: csharp/exercise/exer08-exec-service-main.cs ================================================ /* * EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION */ using System; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MainApp : IRunnable { public void run() { const int NUM_THREADS = 2; const int NUM_TASKS = 5; var execService = new MyExecServiceV0A(NUM_THREADS); var lstTask = new List(); for (int i = 0; i < NUM_TASKS; ++i) lstTask.Add(new MyTask((char)('A' + i))); lstTask.ForEach(task => execService.submit(task)); Console.WriteLine("All tasks are submitted"); execService.waitTaskDone(); Console.WriteLine("All tasks are completed"); execService.shutdown(); } } class MyTask : IRunnable { public char id; public MyTask(char id) { this.id = id; } public void run() { Console.WriteLine($"Task {id} is starting"); Thread.Sleep(3000); Console.WriteLine($"Task {id} is completed"); } } } ================================================ FILE: csharp/exercise/exer08-exec-service-v0a.cs ================================================ /* * MY EXECUTOR SERVICE * * Version 0A: The easiest executor service * - It uses a blocking queue as underlying mechanism. */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MyExecServiceV0A { private int numThreads = 0; private List lstTh = new List(); private BlockingCollection taskPending = new BlockingCollection(); public MyExecServiceV0A(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { numThreads = inpNumThreads; for (int i = 0; i < numThreads; ++i) lstTh.Add(new Thread(() => threadWorkerFunc(this))); lstTh.ForEach(th => th.Start()); } public void submit(IRunnable task) { taskPending.Add(task); } public void waitTaskDone() { // This ExecService is too simple, // so there is no implementation for waitTaskDone() Thread.Sleep(11000); // fake behaviour } public void shutdown() { // This ExecService is too simple, // so there is no implementation for shutdown() Console.WriteLine("No implementation for shutdown()."); Console.WriteLine("You need to exit the app manually."); } private static void threadWorkerFunc(MyExecServiceV0A thisPtr) { ref var taskPending = ref thisPtr.taskPending; IRunnable task = null; for (; ; ) { // WAIT FOR AN AVAILABLE PENDING TASK task = taskPending.Take(); // DO THE TASK task.run(); } } } } ================================================ FILE: csharp/exercise/exer08-exec-service-v0b.cs ================================================ /* * MY EXECUTOR SERVICE * * Version 0B: The easiest executor service * - It uses a blocking queue as underlying mechanism. * - It supports waitTaskDone() and shutdown(). */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MyExecServiceV0B { private int numThreads = 0; private List lstTh = new List(); private BlockingCollection taskPending = new BlockingCollection(); private int counterTaskRunning = 0; private volatile bool forceThreadShutdown = false; private class EmptyTask : IRunnable { public void run() { } } public MyExecServiceV0B(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { numThreads = inpNumThreads; Interlocked.Exchange(ref counterTaskRunning, 0); forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) lstTh.Add(new Thread(() => threadWorkerFunc(this))); lstTh.ForEach(th => th.Start()); } public void submit(IRunnable task) { taskPending.Add(task); } public void waitTaskDone() { // This ExecService is too simple, // so there is no good implementation for waitTaskDone() while ( taskPending.Count > 0 || Interlocked.CompareExchange(ref counterTaskRunning, 0, 0) > 0 ) { Thread.Sleep(1000); // Thread.Yield(); } } public void shutdown() { forceThreadShutdown = true; IRunnable temp; while (taskPending.Count > 0) taskPending.TryTake(out temp, 1000); // Invoke blocked threads by adding "empty" tasks for (int i = 0; i < numThreads; ++i) taskPending.Add(new EmptyTask()); lstTh.ForEach(th => th.Join()); numThreads = 0; lstTh.Clear(); } private static void threadWorkerFunc(MyExecServiceV0B thisPtr) { ref var taskPending = ref thisPtr.taskPending; ref var counterTaskRunning = ref thisPtr.counterTaskRunning; IRunnable task = null; for (; ; ) { // WAIT FOR AN AVAILABLE PENDING TASK task = taskPending.Take(); // If shutdown() was called, then exit the function if (thisPtr.forceThreadShutdown) { break; } // DO THE TASK Interlocked.Increment(ref counterTaskRunning); task.run(); Interlocked.Decrement(ref counterTaskRunning); } } } } ================================================ FILE: csharp/exercise/exer08-exec-service-v1a.cs ================================================ /* * MY EXECUTOR SERVICE * * Version 1A: Simple executor service * - Method "waitTaskDone" invokes thread sleeps in loop (which can cause performance problems). */ using System; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MyExecServiceV1A { private int numThreads = 0; private List lstTh = new List(); private Queue taskPending = new Queue(); private int counterTaskRunning = 0; private volatile bool forceThreadShutdown = false; public MyExecServiceV1A(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; Interlocked.Exchange(ref counterTaskRunning, 0); forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) lstTh.Add(new Thread(() => threadWorkerFunc(this))); lstTh.ForEach(th => th.Start()); } public void submit(IRunnable task) { lock (taskPending) { taskPending.Enqueue(task); Monitor.Pulse(taskPending); } } public void waitTaskDone() { bool done = false; for (; ; ) { lock (taskPending) { if (0 == taskPending.Count && 0 == counterTaskRunning) { done = true; } } if (done) break; Thread.Sleep(1000); // Thread.Yield(); } } public void shutdown() { lock (taskPending) { forceThreadShutdown = true; taskPending.Clear(); Monitor.PulseAll(taskPending); } lstTh.ForEach(th => th.Join()); numThreads = 0; lstTh.Clear(); } private static void threadWorkerFunc(MyExecServiceV1A thisPtr) { ref var taskPending = ref thisPtr.taskPending; ref var counterTaskRunning = ref thisPtr.counterTaskRunning; IRunnable task = null; for (; ; ) { // WAIT FOR AN AVAILABLE PENDING TASK lock (taskPending) { while (0 == taskPending.Count && false == thisPtr.forceThreadShutdown) { Monitor.Wait(taskPending); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.Dequeue(); Interlocked.Increment(ref counterTaskRunning); } // DO THE TASK task.run(); Interlocked.Decrement(ref counterTaskRunning); } } } } ================================================ FILE: csharp/exercise/exer08-exec-service-v1b.cs ================================================ /* * MY EXECUTOR SERVICE * * Version 1B: Simple executor service * - Method "waitTaskDone" uses a condition variable to synchronize. */ using System; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MyExecServiceV1B { private int numThreads = 0; private List lstTh = new List(); private Queue taskPending = new Queue(); private int counterTaskRunning = 0; private object lkTaskRunning = new object(); private volatile bool forceThreadShutdown = false; public MyExecServiceV1B(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; counterTaskRunning = 0; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) lstTh.Add(new Thread(() => threadWorkerFunc(this))); lstTh.ForEach(th => th.Start()); } public void submit(IRunnable task) { lock (taskPending) { taskPending.Enqueue(task); Monitor.Pulse(taskPending); } } public void waitTaskDone() { for (; ; ) { lock (taskPending) { if (0 == taskPending.Count) { lock (lkTaskRunning) { while (counterTaskRunning > 0) { Monitor.Wait(lkTaskRunning); } // no pending task and no running task break; } } } } } public void shutdown() { lock (taskPending) { forceThreadShutdown = true; taskPending.Clear(); Monitor.PulseAll(taskPending); } lstTh.ForEach(th => th.Join()); numThreads = 0; lstTh.Clear(); } private static void threadWorkerFunc(MyExecServiceV1B thisPtr) { ref var taskPending = ref thisPtr.taskPending; ref var counterTaskRunning = ref thisPtr.counterTaskRunning; ref var lkTaskRunning = ref thisPtr.lkTaskRunning; IRunnable task = null; for (; ; ) { // WAIT FOR AN AVAILABLE PENDING TASK lock (taskPending) { while (0 == taskPending.Count && false == thisPtr.forceThreadShutdown) { Monitor.Wait(taskPending); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.Dequeue(); ++counterTaskRunning; } // DO THE TASK task.run(); lock (lkTaskRunning) { --counterTaskRunning; if (0 == counterTaskRunning) { Monitor.Pulse(lkTaskRunning); } } } } } } ================================================ FILE: csharp/exercise/exer08-exec-service-v2a.cs ================================================ /* * MY EXECUTOR SERVICE * * Version 2A: The executor service storing running tasks * - Method "waitTaskDone" uses a semaphore to synchronize. */ using System; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MyExecServiceV2A { private int numThreads = 0; private List lstTh = new List(); private Queue taskPending = new Queue(); private List taskRunning = new List(); private SemaphoreSlim counterTaskRunning = new SemaphoreSlim(0); private volatile bool forceThreadShutdown = false; public MyExecServiceV2A(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) lstTh.Add(new Thread(() => threadWorkerFunc(this))); lstTh.ForEach(th => th.Start()); } public void submit(IRunnable task) { lock (taskPending) { taskPending.Enqueue(task); Monitor.Pulse(taskPending); } } public void waitTaskDone() { for (; ; ) { counterTaskRunning.Wait(); lock (taskPending) { lock (taskRunning) { if (0 == taskPending.Count && 0 == taskRunning.Count /* && 0 == counterTaskRunning.CurrentCount */ ) break; } } } } public void shutdown() { lock (taskPending) { forceThreadShutdown = true; taskPending.Clear(); Monitor.PulseAll(taskPending); } lstTh.ForEach(th => th.Join()); numThreads = 0; lstTh.Clear(); taskRunning.Clear(); if (counterTaskRunning.CurrentCount > 0) counterTaskRunning.Release(counterTaskRunning.CurrentCount); } private static void threadWorkerFunc(MyExecServiceV2A thisPtr) { ref var taskPending = ref thisPtr.taskPending; ref var taskRunning = ref thisPtr.taskRunning; ref var counterTaskRunning = ref thisPtr.counterTaskRunning; IRunnable task = null; for (; ; ) { lock (taskPending) { // WAIT FOR AN AVAILABLE PENDING TASK while (0 == taskPending.Count && false == thisPtr.forceThreadShutdown) { Monitor.Wait(taskPending); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.Dequeue(); // PUSH IT TO THE RUNNING QUEUE lock (taskRunning) { taskRunning.Add(task); } } // DO THE TASK task.run(); // REMOVE IT FROM THE RUNNING QUEUE lock (taskRunning) { taskRunning.Remove(task); } counterTaskRunning.Release(); } } } } ================================================ FILE: csharp/exercise/exer08-exec-service-v2b.cs ================================================ /* * MY EXECUTOR SERVICE * * Version 2B: The executor service storing running tasks * - Method "waitTaskDone" uses a condition variable to synchronize. */ using System; using System.Collections.Generic; using System.Threading; namespace Exer08 { class MyExecServiceV2B { private int numThreads = 0; private List lstTh = new List(); private Queue taskPending = new Queue(); private List taskRunning = new List(); private volatile bool forceThreadShutdown = false; public MyExecServiceV2B(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; forceThreadShutdown = false; for (int i = 0; i < numThreads; ++i) lstTh.Add(new Thread(() => threadWorkerFunc(this))); lstTh.ForEach(th => th.Start()); } public void submit(IRunnable task) { lock (taskPending) { taskPending.Enqueue(task); Monitor.Pulse(taskPending); } } public void waitTaskDone() { for (; ; ) { lock (taskPending) { if (0 == taskPending.Count) { lock (taskRunning) { while (taskRunning.Count > 0) Monitor.Wait(taskRunning); // no pending task and no running task break; } } } } } public void shutdown() { lock (taskPending) { forceThreadShutdown = true; taskPending.Clear(); Monitor.PulseAll(taskPending); } lstTh.ForEach(th => th.Join()); numThreads = 0; lstTh.Clear(); taskRunning.Clear(); } private static void threadWorkerFunc(MyExecServiceV2B thisPtr) { ref var taskPending = ref thisPtr.taskPending; ref var taskRunning = ref thisPtr.taskRunning; IRunnable task = null; for (; ; ) { lock (taskPending) { // WAIT FOR AN AVAILABLE PENDING TASK while (0 == taskPending.Count && false == thisPtr.forceThreadShutdown) { Monitor.Wait(taskPending); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.Dequeue(); // PUSH IT TO THE RUNNING QUEUE lock (taskRunning) { taskRunning.Add(task); } } // DO THE TASK task.run(); // REMOVE IT FROM THE RUNNING QUEUE lock (taskRunning) { taskRunning.Remove(task); Monitor.PulseAll(taskRunning); } } } } } ================================================ FILE: csharp/multithreading.csproj ================================================ Exe net6.0 ================================================ FILE: csharp/multithreading.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31729.503 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "multithreading", "multithreading.csproj", "{E4A5B6AD-5516-49B7-90D2-08E0CC56D10B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E4A5B6AD-5516-49B7-90D2-08E0CC56D10B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E4A5B6AD-5516-49B7-90D2-08E0CC56D10B}.Debug|Any CPU.Build.0 = Debug|Any CPU {E4A5B6AD-5516-49B7-90D2-08E0CC56D10B}.Release|Any CPU.ActiveCfg = Release|Any CPU {E4A5B6AD-5516-49B7-90D2-08E0CC56D10B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5BE3393C-A944-4DC7-AC10-4ABF4213147E} EndGlobalSection EndGlobal ================================================ FILE: java/.gitignore ================================================ /bin/ /build/ /dist/ .classpath .project ================================================ FILE: java/README.md ================================================ # JAVA MULTITHREADING ## DESCRIPTION Multithreading in Java. ## PROJECT SPECIFICATIONS WARNING: Java-17 features. ================================================ FILE: java/src/demo00_intro/App.java ================================================ /* * INTRODUCTION TO MULTITHREADING * You should try running this app several times and see results. */ package demo00_intro; public class App { public static void main(String[] args) { Thread th = new ExampleThread(); th.start(); for (int i = 0; i < 300; ++i) System.out.print("A"); } } class ExampleThread extends Thread { @Override public void run() { for (int i = 0; i < 300; ++i) System.out.print("B"); } } ================================================ FILE: java/src/demo01_hello/AppA01.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version A01: Using class Thread (original way) */ package demo01_hello; public class AppA01 { public static void main(String[] args) throws InterruptedException { var th = new ExampleThread(); th.start(); System.out.println("Hello from main thread"); } } class ExampleThread extends Thread { @Override public void run() { System.out.println("Hello from example thread"); } } ================================================ FILE: java/src/demo01_hello/AppA02.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version A02: Using class Thread (anonymous subclass) */ package demo01_hello; public class AppA02 { public static void main(String[] args) throws InterruptedException { var th = new Thread() { @Override public void run() { System.out.println("Hello from example thread"); } }; th.start(); System.out.println("Hello from main thread"); } } ================================================ FILE: java/src/demo01_hello/AppB01.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version B01: Using inteface Runnable */ package demo01_hello; public class AppB01 { public static void main(String[] args) throws InterruptedException { var doTask = new ExampleRunnable(); var th1 = new Thread(doTask); var th2 = new Thread(doTask); th1.start(); th2.start(); System.out.println("Hello from main thread"); } } class ExampleRunnable implements Runnable { @Override public void run() { System.out.println("Hello from example thread"); } } ================================================ FILE: java/src/demo01_hello/AppB02.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version B02: Using inteface Runnable with lambdas */ package demo01_hello; public class AppB02 { public static void main(String[] args) throws InterruptedException { Runnable doTask = () -> System.out.println("Hello from example thread"); var th1 = new Thread(doTask); var th2 = new Thread(doTask); th1.start(); th2.start(); System.out.println("Hello from main thread"); } } ================================================ FILE: java/src/demo01_hello/AppB03.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version B03: Using inteface Runnable with lambdas */ package demo01_hello; public class AppB03 { public static void main(String[] args) throws InterruptedException { var th = new Thread(() -> System.out.println("Hello from example thread")); th.start(); System.out.println("Hello from main thread"); } } ================================================ FILE: java/src/demo01_hello/AppC01.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version C01: Using functions (via delegation from lambdas) */ package demo01_hello; public class AppC01 { public static void main(String[] args) throws InterruptedException { var th = new Thread(() -> doTask()); th.start(); System.out.println("Hello from main thread"); } private static void doTask() { System.out.println("Hello from example thread"); } } ================================================ FILE: java/src/demo01_hello/AppC02.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version C02: Using function references */ package demo01_hello; public class AppC02 { public static void main(String[] args) throws InterruptedException { var th = new Thread(AppC02::doTask); th.start(); System.out.println("Hello from main thread"); } private static void doTask() { System.out.println("Hello from example thread"); } } ================================================ FILE: java/src/demo01_hello/AppExtra.java ================================================ /* * HELLO WORLD VERSION MULTITHREADING * Version extra: Getting thread name and reference to thread instance executing current thread */ package demo01_hello; public class AppExtra { public static void main(String[] args) throws InterruptedException { var th = new Thread("Lorem") { @Override public void run() { Thread myself = Thread.currentThread(); // th is myself System.out.println("My name is " + this.getName()); // or myself.getName() System.out.println("My self is " + myself); } }; th.start(); } } ================================================ FILE: java/src/demo02_join/AppA.java ================================================ /* * THREAD JOINS */ package demo02_join; public class AppA { public static void main(String[] args) throws InterruptedException { Thread th = new Thread(() -> doHeavyTask()); th.start(); th.join(); System.out.println("Good bye!"); } @SuppressWarnings("unused") private static void doHeavyTask() { // Do a heavy task, which takes a little time long sum = 0; for (int i = 0; i < 2000000000; ++i) sum += i; System.out.println("Done!"); } } ================================================ FILE: java/src/demo02_join/AppB.java ================================================ /* * THREAD JOINS */ package demo02_join; public class AppB { public static void main(String[] args) { Thread thFoo = new Thread(() -> System.out.println("foo")); Thread thBar = new Thread(() -> System.out.println("bar")); thFoo.start(); thBar.start(); // thFoo.join(); // thBar.join(); /* * We do not need to call thFoo.join() and thBar.join(). * The reason is main thread will wait for the completion of all threads before app exits. */ } } ================================================ FILE: java/src/demo03_pass_arg/AppA.java ================================================ /* * PASSING ARGUMENTS * Version A: Passing arguments to Thread objects */ package demo03_pass_arg; public class AppA { public static void main(String[] args) throws InterruptedException { var thFoo = new MyThread(1, 2, "red"); var thBar = new MyThread(3, 4, "blue"); thFoo.start(); thBar.start(); } } class MyThread extends Thread { private int a; private double b; private String c; public MyThread(int a, double b, String c) { super(); this.a = a; this.b = b; this.c = c; } @Override public void run() { System.out.printf("%d %.1f %s %n", a, b, c); } } ================================================ FILE: java/src/demo03_pass_arg/AppB.java ================================================ /* * PASSING ARGUMENTS * Version B: Passing arguments to functions */ package demo03_pass_arg; public class AppB { public static void main(String[] args) throws InterruptedException { var thFoo = new Thread(() -> doTask(1, 2, "red")); var thBar = new Thread(() -> doTask(3, 4, "blue")); thFoo.start(); thBar.start(); } private static void doTask(int a, double b, String c) { System.out.printf("%d %.1f %s %n", a, b, c); } } ================================================ FILE: java/src/demo03_pass_arg/AppC.java ================================================ /* * PASSING ARGUMENTS * Version C: Passing arguments by capturing them * * Note: The captured variables must be final or effectively final. */ package demo03_pass_arg; public class AppC { public static void main(String[] args) throws InterruptedException { final int COUNT = 10; new Thread(() -> { for (int i = 1; i <= COUNT; ++i) System.out.println("value: " + i); }).start(); } } ================================================ FILE: java/src/demo04_sleep/App.java ================================================ /* * SLEEP */ package demo04_sleep; public class App { public static void main(String[] args) throws InterruptedException { var thFoo = new Thread(() -> { System.out.println("foo is sleeping"); try { Thread.sleep(3000); } catch (InterruptedException e) { } System.out.println("foo wakes up"); }); thFoo.start(); thFoo.join(); System.out.println("Good bye"); } } ================================================ FILE: java/src/demo05_id/App.java ================================================ /* * GETTING THREAD'S ID */ package demo05_id; public class App { public static void main(String[] args) { Runnable doTask = () -> { long id = Thread.currentThread().getId(); System.out.println(id); }; var thFoo = new Thread(doTask); var thBar = new Thread(doTask); System.out.println("foo's id: " + thFoo.getId()); System.out.println("bar's id: " + thBar.getId()); thFoo.start(); thBar.start(); } } ================================================ FILE: java/src/demo06_list_threads/AppA.java ================================================ /* * LIST OF MULTIPLE THREADS * Version A: Using java.util.List / java.util.ArrayList */ package demo06_list_threads; import java.util.ArrayList; public class AppA { public static void main(String[] args) { final int NUM_THREADS = 5; var lstTh = new ArrayList(); for (int i = 0; i < NUM_THREADS; ++i) { final int index = i; lstTh.add(new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.print(index); })); } for (var th : lstTh) th.start(); // We can reduce above for loop with this statement: // lstTh.forEach(Thread::start); } } ================================================ FILE: java/src/demo06_list_threads/AppB01.java ================================================ /* * LIST OF MULTIPLE THREADS * Version B01: Using streams */ package demo06_list_threads; import java.util.stream.IntStream; public class AppB01 { public static void main(String[] args) { var lstTh = IntStream.range(0, 5).mapToObj(i -> new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.print(i); })).toList(); for (var th : lstTh) th.start(); // We can reduce above for loop with this statement: // lstTh.forEach(Thread::start); } } ================================================ FILE: java/src/demo06_list_threads/AppB02.java ================================================ /* * LIST OF MULTIPLE THREADS * Version B02: Using streams (shorten code, no variable to store list of threads) */ package demo06_list_threads; import java.util.stream.IntStream; public class AppB02 { public static void main(String[] args) { IntStream.range(0, 5).forEach(i -> new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.print(i); }).start()); } } ================================================ FILE: java/src/demo07_terminate/AppA.java ================================================ /* * FORCING A THREAD TO TERMINATE (i.e. killing the thread) * Version A: Interrupting the thread */ package demo07_terminate; public class AppA { public static void main(String[] args) throws InterruptedException { var th = new Thread(() -> { while (true) { System.out.println("Running..."); try { Thread.sleep(1000); } catch (InterruptedException e) { // Received interrupt signal, now current thread is going to exit return; } } }); th.start(); Thread.sleep(3000); th.interrupt(); } } ================================================ FILE: java/src/demo07_terminate/AppB.java ================================================ /* * FORCING A THREAD TO TERMINATE (i.e. killing the thread) * Version B: Using a flag to notify the thread * * Beside atomic variables, you can use the "volatile" specifier. */ package demo07_terminate; import java.util.concurrent.atomic.AtomicBoolean; public class AppB { public static void main(String[] args) throws InterruptedException { var flagStop = new AtomicBoolean(false); var th = new Thread(() -> { while (true) { if (flagStop.get()) break; System.out.println("Running..."); try { Thread.sleep(1000); } catch (InterruptedException e) { } } }); th.start(); Thread.sleep(3000); flagStop.set(true); } } ================================================ FILE: java/src/demo08_return_value/AppA.java ================================================ /* * GETTING RETURNED VALUES FROM THREADS */ package demo08_return_value; public class AppA { public static void main(String[] args) throws InterruptedException { var thFoo = new MyThread(5); var thBar = new MyThread(80); thFoo.start(); thBar.start(); thFoo.join(); thBar.join(); System.out.println(thFoo.result); System.out.println(thBar.result); } } class MyThread extends Thread { public int arg = 0; public int result = 0; public MyThread(int arg) { super(); this.arg = arg; } @Override public void run() { result = arg * 2; } } ================================================ FILE: java/src/demo08_return_value/AppB.java ================================================ /* * GETTING RETURNED VALUES FROM THREADS */ package demo08_return_value; public class AppB { public static void main(String[] args) throws InterruptedException { int[] result = new int[2]; var thFoo = new Thread(() -> result[0] = doubleValue(5)); var thBar = new Thread(() -> result[1] = doubleValue(80)); thFoo.start(); thBar.start(); // Wait until thFoo and thBar finish thFoo.join(); thBar.join(); System.out.println(result[0]); System.out.println(result[1]); } private static int doubleValue(int value) { return value * 2; } } ================================================ FILE: java/src/demo08_return_value/AppC.java ================================================ /* * GETTING RETURNED VALUES FROM THREADS */ package demo08_return_value; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppC { public static void main(String[] args) throws InterruptedException, ExecutionException { var cal = new SimpleCalculator(); System.out.println("Begin calculating"); var futResult = cal.calculate(-9); var result = futResult.get(); System.out.println(result); cal.shutdown(); } } class SimpleCalculator { private ExecutorService executor = Executors.newSingleThreadExecutor(); public Future calculate(Integer input) { return executor.submit(() -> { Thread.sleep(1000); return input * 2; }); } public void shutdown() { executor.shutdown(); } } ================================================ FILE: java/src/demo09_detach/App.java ================================================ /* * THREAD DETACHING */ package demo09_detach; public class App { public static void main(String[] args) throws InterruptedException { var thFoo = new Thread(() -> { System.out.println("foo is starting..."); try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println("foo is exiting..."); }); thFoo.setDaemon(true); thFoo.start(); // If I comment this statement, // thFoo will be forced into terminating with main thread Thread.sleep(3000); System.out.println("Main thread is exiting"); } } ================================================ FILE: java/src/demo10_yield/App.java ================================================ /* * THREAD YIELDING */ package demo10_yield; import java.time.Duration; import java.time.Instant; public class App { public static void main(String[] args) { var tpStartMeasure = Instant.now(); littleSleep(130000); var timeElapsed = Duration.between(tpStartMeasure, Instant.now()); System.out.println("Elapsed time: " + timeElapsed.toNanos() + " nanoseonds"); } private static void littleSleep(int ns) { var tpStart = Instant.now(); var tpEnd = tpStart.plusNanos(ns); do { Thread.yield(); } while (Instant.now().isBefore(tpEnd)); } } ================================================ FILE: java/src/demo11_exec_service/AppA.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version A: The executor service (of which thread pool) containing a single thread * * Note: The single thread executor is ideal for creating an event loop. */ package demo11_exec_service; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AppA { public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> System.out.println("Hello World")); executor.submit(() -> System.out.println("Hello Multithreading")); Runnable rnn = () -> System.out.println("Hello the Executor Service"); executor.submit(rnn); executor.shutdown(); } } ================================================ FILE: java/src/demo11_exec_service/AppB01.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version B01: The executor service containing multiple threads - FixedThreadPool */ package demo11_exec_service; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class AppB01 { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 2; final int NUM_TASKS = 5; ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); IntStream.range(0, NUM_TASKS).forEach(i -> executor.submit(() -> { char nameTask = (char) (i + 'A'); System.out.println("Task %c is starting".formatted(nameTask)); try { Thread.sleep(3000); } catch (InterruptedException e) { } System.out.println("Task %c is completed".formatted(nameTask)); })); // shutdown() stops the ExecutorService from accepting new tasks // and closes down idle worker threads executor.shutdown(); } } ================================================ FILE: java/src/demo11_exec_service/AppB02.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version B02: The executor service containing multiple threads - FixedThreadPool */ package demo11_exec_service; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; public class AppB02 { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 2; final int NUM_TASKS = 5; ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); IntStream.range(0, NUM_TASKS).forEach(i -> executor.submit(() -> { char nameTask = (char) (i + 'A'); System.out.println("Task %c is starting".formatted(nameTask)); try { Thread.sleep(3000); } catch (InterruptedException e) { } System.out.println("Task %c is completed".formatted(nameTask)); })); executor.shutdown(); System.out.println("All tasks are submitted"); executor.awaitTermination(20, TimeUnit.SECONDS); System.out.println("All tasks are completed"); } } ================================================ FILE: java/src/demo11_exec_service/AppC01.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C01: The executor service and Future objects - Getting started * * Future objects help you to programatically manage tasks, such as: * - Wait for a task to finish executing (and get result), with the "get" method. * - Cancel a task prematurely, with the "cancel" method. */ package demo11_exec_service; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppC01 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future task = executor.submit(() -> "lorem ipsum"); executor.shutdown(); while (false == task.isDone()) { // Waiting... } String result = task.get(); System.out.println(result); } } ================================================ FILE: java/src/demo11_exec_service/AppC02.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C02: The executor service and Future objects - Getting started */ package demo11_exec_service; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppC02 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future task = executor.submit(() -> getSquared(7)); executor.shutdown(); while (false == task.isDone()) { // Waiting... } Integer result = task.get(); System.out.println(result); } private static int getSquared(int x) { return x * x; } } ================================================ FILE: java/src/demo11_exec_service/AppC03.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C03: The executor service and Future objects - Getting started */ package demo11_exec_service; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppC03 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future task = executor.submit(() -> getSquared(7)); executor.shutdown(); /* * Method "Future.get" should wait if necessary for the computation to complete, * and then retrieves its result. * * So, we can omit the while loop (to wait for task completion). */ Integer result = task.get(); System.out.println(result); } private static int getSquared(int x) { return x * x; } } ================================================ FILE: java/src/demo11_exec_service/AppC04.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C04: The executor service and Future objects - Getting started */ package demo11_exec_service; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppC04 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); // Old syntax // Callable callable = new Callable<>() { // @Override // public Integer call() throws Exception { // return getSquared(7); // } // }; Callable callable = () -> getSquared(7); Future task = executor.submit(callable); executor.shutdown(); System.out.println("Calculating..."); Integer result = task.get(); System.out.println(result); } private static int getSquared(int x) { // Calculating in three seconds... try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return x * x; } } ================================================ FILE: java/src/demo11_exec_service/AppC05.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C05: The executor service and Future objects - List of Future objects */ package demo11_exec_service; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class AppC05 { public static void main(String[] args) throws InterruptedException, ExecutionException { final int NUM_THREADS = 2; final int NUM_TASKS = 5; ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); System.out.println("Begin to submit all tasks"); // lstTask is List< Future > var lstTask = IntStream.range(0, NUM_TASKS) .mapToObj(i -> executor.submit(() -> (char)(i + 'A'))) .toList(); executor.shutdown(); for (var task : lstTask) { System.out.println(task.get()); } } } ================================================ FILE: java/src/demo11_exec_service/AppC06.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Version C06: The executor service and Future objects - List of Future objects */ package demo11_exec_service; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class AppC06 { public static void main(String[] args) throws InterruptedException, ExecutionException { final int NUM_THREADS = 2; final int NUM_TASKS = 5; ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); // List< Callable > todo var todo = IntStream.range(0, NUM_TASKS) .mapToObj(i -> (Callable)() -> doTask(i)) .toList(); System.out.println("Begin to submit all tasks"); /* * invokeAll() will not return until all the tasks are completed * (i.e., all the Futures in your answer collection will report isDone() if asked) */ // lstTask is List< Future > var lstTask = executor.invokeAll(todo); System.out.println("All tasks are completed"); executor.shutdown(); for (var task : lstTask) { System.out.println(task.get()); } } private static String doTask(int number) { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("Finish " + number); return number + " ok"; } } ================================================ FILE: java/src/demo11_exec_service/AppExtra.java ================================================ /* * EXECUTOR SERVICES AND THREAD POOLS * Demo extra: The fork/join pool * * ForkJoinPool is the central part of the fork/join framework introduced in Java 7. * It solves a common problem of spawning multiple tasks in recursive algorithms. * Using a simple ThreadPoolExecutor, you will run out of threads quickly, * as every task or subtask requires its own thread to run. * * In a fork/join framework, any task can spawn (fork) a number of subtasks and wait for their completion * using the join method. The benefit of the fork/join framework is that it does not create a new thread * for each task or subtask, implementing the Work Stealing algorithm instead. * https://www.baeldung.com/thread-pool-java-and-guava */ package demo11_exec_service; import java.util.Arrays; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; import java.util.stream.Collectors; public class AppExtra { public static void main(String[] args) { var tree = new TreeNode(5, new TreeNode(-1), new TreeNode(3, new TreeNode(6), new TreeNode(-4)) ); var forkJoinPool = ForkJoinPool.commonPool(); int result = forkJoinPool.invoke(new TreeSumTask(tree)); System.out.println(result); } } class TreeNode { int value; List children; TreeNode(int value, TreeNode... children) { this.value = value; this.children = Arrays.asList(children); } } class TreeSumTask extends RecursiveTask { private static final long serialVersionUID = 1L; final TreeNode node; TreeSumTask(TreeNode node) { this.node = node; } @Override protected Integer compute() { return node.value + node.children.stream() .map(child -> new TreeSumTask(child).fork()) .collect(Collectors.summingInt(ForkJoinTask::join)); } } ================================================ FILE: java/src/demo12_race_condition/AppA.java ================================================ /* * RACE CONDITIONS */ package demo12_race_condition; import java.util.stream.IntStream; public class AppA { public static void main(String[] args) { final int NUM_THREADS = 4; var lstTh = IntStream.range(0, NUM_THREADS) .mapToObj(i -> new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.print(i); })) .toList(); lstTh.forEach(Thread::start); } } ================================================ FILE: java/src/demo12_race_condition/AppB01.java ================================================ /* * DATA RACES * Version 01: Without multithreading */ package demo12_race_condition; import java.util.Arrays; public class AppB01 { public static void main(String[] args) { final int N = 8; int result = getResult(N); System.out.println("Number of integers that are divisible by 2 or 3 is: " + result); } private static int getResult(int N) { var a = new boolean[N + 1]; Arrays.fill(a, false); for (int i = 1; i <= N; ++i) if (i % 2 == 0 || i % 3 == 0) a[i] = true; int res = countTrue(a, N); return res; } private static int countTrue(boolean[] a, int N) { int count = 0; for (int i = 1; i <= N; ++i) if (a[i]) ++count; return count; } } ================================================ FILE: java/src/demo12_race_condition/AppB02.java ================================================ /* * DATA RACES * Version 02: Multithreading */ package demo12_race_condition; import java.util.Arrays; public class AppB02 { public static void main(String[] args) throws InterruptedException { final int N = 8; var a = new boolean[N + 1]; Arrays.fill(a, false); var thDiv2 = new Thread(() -> { for (int i = 2; i <= N; i += 2) a[i] = true; }); var thDiv3 = new Thread(() -> { for (int i = 3; i <= N; i += 3) a[i] = true; }); thDiv2.start(); thDiv3.start(); thDiv2.join(); thDiv3.join(); int result = countTrue(a, N); System.out.println("Number of integers that are divisible by 2 or 3 is: " + result); } private static int countTrue(boolean[] a, int N) { int count = 0; for (int i = 1; i <= N; ++i) if (a[i]) ++count; return count; } } ================================================ FILE: java/src/demo12_race_condition/AppC01.java ================================================ /* * RACE CONDITIONS AND DATA RACES */ package demo12_race_condition; import java.util.stream.Stream; public class AppC01 { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 16; var lstTh = Stream.generate(() -> new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } for (int i = 0; i < 1000; ++i) { Global.counter += 1; } })).limit(NUM_THREADS).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + Global.counter); /* * We are not sure that counter = 16000 */ } private static class Global { public static int counter = 0; } } ================================================ FILE: java/src/demo12_race_condition/AppC02.java ================================================ /* * RACE CONDITIONS AND DATA RACES */ package demo12_race_condition; public class AppC02 { public static void main(String[] args) throws InterruptedException { var thA = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } while (Global.counter < 10) ++Global.counter; System.out.println("A won !!!"); }); var thB = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } while (Global.counter > -10) --Global.counter; System.out.println("B won !!!"); }); thA.start(); thB.start(); } private static class Global { public static int counter = 0; } } ================================================ FILE: java/src/demo13_mutex/AppA.java ================================================ /* * MUTEXES */ package demo13_mutex; public class AppA { public static void main(String[] args) { /* * Unfortunately, Java does not support the mutex feature by default. * You can use a binary semaphore as a mutex. */ } } ================================================ FILE: java/src/demo13_mutex/AppB.java ================================================ /* * MUTEXES * * A binary semaphore can be used as a mutex. * Without synchronization (by a mutex), we are not sure that counter = 16000. */ package demo13_mutex; import java.util.concurrent.Semaphore; import java.util.stream.Stream; public class AppB { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 16; var lstTh = Stream.generate(() -> new Thread(() -> { try { Thread.sleep(1000); Global.mutex.acquire(); for (int i = 0; i < 1000; ++i) { Global.counter += 1; } Global.mutex.release(); } catch (InterruptedException e) { e.printStackTrace(); } })).limit(NUM_THREADS).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + Global.counter); } private static class Global { public static Semaphore mutex = new Semaphore(1); public static int counter = 0; } } ================================================ FILE: java/src/demo14_synchronized_block/AppA.java ================================================ /* * SYNCHRONIZED BLOCKS * Version A: Synchronized blocks */ package demo14_synchronized_block; import java.util.stream.IntStream; public class AppA { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 16; var worker = new MyTask(); var lstTh = IntStream.range(0, NUM_THREADS).mapToObj(i -> new Thread(worker)).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + MyTask.counter); /* * We are sure that counter = 16000 */ } private static class MyTask implements Runnable { public static int counter = 0; @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { } /* * synchronized (this) means that on "this" object, * only and only one thread can execute the enclosed block at one time. * * "this" is the monitor object, the code inside the block gets synchronized on the monitor object. * Simply put, only one thread per monitor object can execute inside that block of code. */ synchronized (this) { for (int i = 0; i < 1000; ++i) { ++counter; } } } } } ================================================ FILE: java/src/demo14_synchronized_block/AppB01.java ================================================ /* * SYNCHRONIZED BLOCKS * Version B01: Synchronized instance methods */ package demo14_synchronized_block; import java.util.stream.IntStream; public class AppB01 { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 16; var myTask = new MyTask(); var lstTh = IntStream.range(0, NUM_THREADS).mapToObj(i -> new Thread(myTask)).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + MyTask.counter); /* * We are sure that counter = 16000 */ } private static class MyTask implements Runnable { public static int counter = 0; @Override public synchronized void run() { try { Thread.sleep(500); } catch (InterruptedException e) { } for (int i = 0; i < 1000; ++i) { ++counter; } } } } ================================================ FILE: java/src/demo14_synchronized_block/AppB02.java ================================================ /* * SYNCHRONIZED BLOCKS * Version B02: Synchronized instance methods * * Assume there is a synchronized method "run" in object X: * Multiple threads associating with X should synchronize (block) when they execute method "run". * * If multiple threads associate with multiple objects (each thread ~ each object), * they will NOT SYNCHRONIZE when execute method "run". * * In demo code below, we create multiple Worker objects, so the problem happens. */ package demo14_synchronized_block; import java.util.stream.IntStream; public class AppB02 { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 16; var lstTh = IntStream.range(0, NUM_THREADS).mapToObj(i -> new Worker()).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + Worker.counter); /* * We are NOT sure that counter = 16000 */ } private static class Worker extends Thread { public static int counter = 0; @Override public synchronized void run() { try { Thread.sleep(500); } catch (InterruptedException e) { } for (int i = 0; i < 1000; ++i) { ++counter; } } } } ================================================ FILE: java/src/demo14_synchronized_block/AppC.java ================================================ /* * SYNCHRONIZED BLOCKS * Version C: Synchronized static methods */ package demo14_synchronized_block; import java.util.stream.IntStream; public class AppC { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 16; var lstTh = IntStream.range(0, NUM_THREADS).mapToObj(i -> new Worker()).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + Worker.counter); /* * We are sure that counter = 16000 */ } private static class Worker extends Thread { public static int counter = 0; @Override public void run() { incCounter(); } private static synchronized void incCounter() { try { Thread.sleep(500); } catch (InterruptedException e) { } for (int i = 0; i < 1000; ++i) { ++counter; } } } } ================================================ FILE: java/src/demo15_deadlock/AppA.java ================================================ /* * DEADLOCK * Version A */ package demo15_deadlock; import java.util.concurrent.Semaphore; public class AppA { public static void main(String[] args) throws InterruptedException { var mutex = new Semaphore(1); var thFoo = new Thread(() -> doTask(mutex, "foo")); var thBar = new Thread(() -> doTask(mutex, "bar")); thFoo.start(); thBar.start(); thFoo.join(); thBar.join(); System.out.println("You will never see this statement due to deadlock!"); } private static void doTask(Semaphore mutex, String name) { try { mutex.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + " acquired resource"); // mutex.release(); // Forget this statement ==> deadlock } } ================================================ FILE: java/src/demo15_deadlock/AppB.java ================================================ /* * DEADLOCK * Version B */ package demo15_deadlock; public class AppB { public static void main(String[] args) throws InterruptedException { final Object resourceA = "resourceA"; final Object resourceB = "resourceB"; var thFoo = new Thread(() -> { synchronized (resourceA) { System.out.println("foo acquired resource A"); try { Thread.sleep(1000); } catch (InterruptedException e) { } synchronized (resourceB) { System.out.println("foo acquired resource B"); } } }); var thBar = new Thread(() -> { synchronized (resourceB) { System.out.println("bar acquired resource B"); try { Thread.sleep(1000); } catch (InterruptedException e) { } synchronized (resourceA) { System.out.println("bar acquired resource A"); } } }); thFoo.start(); thBar.start(); thFoo.join(); thBar.join(); System.out.println("You will never see this statement due to deadlock!"); } } ================================================ FILE: java/src/demo16_monitor/App.java ================================================ /* * MONITORS * Implementation of a monitor for managing a counter */ package demo16_monitor; import java.util.stream.IntStream; public class App { public static void main(String[] args) throws InterruptedException { var counter = new Counter(); var monitor = new MyMonitor(); monitor.init(counter); final int NUM_THREADS = 16; var lstTh = IntStream.range(0, NUM_THREADS).mapToObj(t -> new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } for (int i = 0; i < 1000; ++i) monitor.increaseCounter(); })).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); System.out.println("counter = " + counter.value); } } class Counter { public int value = 0; } class MyMonitor { private Counter counter = null; public void init(Counter counter) { this.counter = counter; } public void increaseCounter() { synchronized (counter) { ++counter.value; } } } ================================================ FILE: java/src/demo17_reentrant_lock/AppA01.java ================================================ /* * REENTRANT LOCKS (RECURSIVE MUTEXES) * Version A01: A simple example */ package demo17_reentrant_lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class AppA01 { public static void main(String[] args) { Lock lk = new ReentrantLock(); new Thread(() -> { lk.lock(); System.out.println("First time acquiring the resource"); lk.lock(); System.out.println("Second time acquiring the resource"); lk.unlock(); lk.unlock(); }).start(); } } ================================================ FILE: java/src/demo17_reentrant_lock/AppA02.java ================================================ /* * REENTRANT LOCKS (RECURSIVE MUTEXES) * Version A02: A multithreaded app example */ package demo17_reentrant_lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.IntStream; public class AppA02 { public static void main(String[] args) { final int NUM_THREADS = 3; IntStream.range(0, NUM_THREADS).forEach(i -> new Worker((char)(i + 'A')).start()); } private static class Worker extends Thread { private static Lock lk = new ReentrantLock(); private char name; public Worker(char name) { this.name = name; } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { } lk.lock(); System.out.println("First time %c acquiring the resource".formatted(name)); lk.lock(); System.out.println("Second time %c acquiring the resource".formatted(name)); lk.unlock(); lk.unlock(); } } } ================================================ FILE: java/src/demo17_reentrant_lock/AppB01.java ================================================ /* * REENTRANT LOCKS (RECURSIVE MUTEXES) * Version B01: A simple example * * Looking into the lock behind the "synchronized" keyword. * The lock behind the synchronized methods and blocks is reentrant lock. * That is, the current thread can acquire the same synchronized lock over and over again while holding it. */ package demo17_reentrant_lock; public class AppB01 { public static void main(String[] args) { final Object resource = "resource"; new Thread(() -> { synchronized (resource) { System.out.println("First time acquiring the resource"); synchronized (resource) { System.out.println("Second time acquiring the resource"); } } }).start(); } } ================================================ FILE: java/src/demo17_reentrant_lock/AppB02.java ================================================ /* * REENTRANT LOCKS (RECURSIVE MUTEXES) * Version B02: A multithreaded app example */ package demo17_reentrant_lock; import java.util.stream.IntStream; public class AppB02 { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 3; IntStream.range(0, NUM_THREADS).forEach(i -> new Worker((char)(i + 'A')).start()); } private static class Worker extends Thread { private static Object lock = new Object(); private char name; public Worker(char name) { this.name = name; } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { } synchronized (lock) { System.out.println("First time %c acquiring the lock".formatted(name)); synchronized (lock) { System.out.println("Second time %c acquiring the lock".formatted(name)); } } } } } ================================================ FILE: java/src/demo18_barrier_latch/AppA01.java ================================================ /* * BARRIERS AND LATCHES * Version A: Cyclic barriers */ package demo18_barrier_latch; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class AppA01 { public static void main(String[] args) { var syncPoint = new CyclicBarrier(3); // participant count = 3 var lstArg = List.of( new ThreadArg("lorem", 1), new ThreadArg("ipsum", 2), new ThreadArg("dolor", 3) ); lstArg.forEach(arg -> new Thread(() -> { try { Thread.sleep(1000 * arg.waitTime); System.out.println("Get request from " + arg.userName); syncPoint.await(); System.out.println("Process request for " + arg.userName); syncPoint.await(); System.out.println("Done " + arg.userName); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start()); } private record ThreadArg(String userName, int waitTime) { } } ================================================ FILE: java/src/demo18_barrier_latch/AppA02.java ================================================ /* * BARRIERS AND LATCHES * Version A: Cyclic barriers */ package demo18_barrier_latch; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class AppA02 { public static void main(String[] args) { var syncPoint = new CyclicBarrier(2); // participant count = 2 var lstArg = List.of( new ThreadArg("lorem", 1), new ThreadArg("ipsum", 3), new ThreadArg("dolor", 3), new ThreadArg("amet", 10) ); lstArg.forEach(arg -> new Thread(() -> { try { Thread.sleep(1000 * arg.waitTime); System.out.println("Get request from " + arg.userName); syncPoint.await(); System.out.println("Process request for " + arg.userName); syncPoint.await(); System.out.println("Done " + arg.userName); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start()); // Thread with userName = "amet" shall be FREEZED } private record ThreadArg(String userName, int waitTime) { } } ================================================ FILE: java/src/demo18_barrier_latch/AppA03.java ================================================ /* * BARRIERS AND LATCHES * Version A: Cyclic barriers */ package demo18_barrier_latch; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class AppA03 { public static void main(String[] args) { var syncPointA = new CyclicBarrier(2); var syncPointB = new CyclicBarrier(2); var lstArg = List.of( new ThreadArg("lorem", 1), new ThreadArg("ipsum", 3), new ThreadArg("dolor", 3), new ThreadArg("amet", 10) ); lstArg.forEach(arg -> new Thread(() -> { try { Thread.sleep(1000 * arg.waitTime); System.out.println("Get request from " + arg.userName); syncPointA.await(); System.out.println("Process request for " + arg.userName); syncPointB.await(); System.out.println("Done " + arg.userName); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start()); } private record ThreadArg(String userName, int waitTime) { } } ================================================ FILE: java/src/demo18_barrier_latch/AppB01.java ================================================ /* * BARRIERS AND LATCHES * Version B: Count-down latches * * Notes: * - CyclicBarrier maintains a count of threads whereas CountDownLatch maintains a count of tasks. * - CountDownLatch in Java is different from that one in C++. */ package demo18_barrier_latch; import java.util.List; import java.util.concurrent.CountDownLatch; public class AppB01 { public static void main(String[] args) { var syncPoint = new CountDownLatch(3); // participant count = 3 var lstArg = List.of( new ThreadArg("lorem", 1), new ThreadArg("ipsum", 2), new ThreadArg("dolor", 3) ); lstArg.forEach(arg -> new Thread(() -> { try { Thread.sleep(1000 * arg.waitTime); System.out.println("Get request from " + arg.userName); syncPoint.countDown(); syncPoint.await(); System.out.println("Done " + arg.userName); } catch (InterruptedException e) { e.printStackTrace(); } }).start()); } private record ThreadArg(String userName, int waitTime) { } } ================================================ FILE: java/src/demo18_barrier_latch/AppB02.java ================================================ /* * BARRIERS AND LATCHES * Version B: Count-down latches * * Main thread waits for 3 child threads to get enough data to progress. */ package demo18_barrier_latch; import java.util.List; import java.util.concurrent.CountDownLatch; public class AppB02 { public static void main(String[] args) throws InterruptedException { var lstArg = List.of( new ThreadArg("Send request to egg.net to get data", 6), new ThreadArg("Send request to foo.org to get data", 2), new ThreadArg("Send request to bar.com to get data", 4) ); var syncPoint = new CountDownLatch(lstArg.size()); lstArg.forEach(arg -> new Thread(() -> { try { Thread.sleep(1000 * arg.waitTime); System.out.println(arg.message); syncPoint.countDown(); Thread.sleep(8000); System.out.println("Cleanup"); } catch (InterruptedException e) { } }).start()); syncPoint.await(); System.out.println("\nNow we have enough data to progress to next step\n"); } private record ThreadArg(String message, int waitTime) { } } ================================================ FILE: java/src/demo19_read_write_lock/App.java ================================================ /* * READ-WRITE LOCKS */ package demo19_read_write_lock; import java.util.Random; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.IntStream; import java.util.stream.Stream; public class App { public static void main(String[] args) { ReadWriteLock rwlock = new ReentrantReadWriteLock(); final int NUM_THREADS_READ = 10; final int NUM_THREADS_WRITE = 4; final int NUM_ARGS = 3; var lstArg = IntStream.range(0, NUM_ARGS).toArray(); var rand = new Random(); var lstThRead = Stream.generate(() -> new Thread(() -> { int waitTime = lstArg[ rand.nextInt(lstArg.length) ]; try { Thread.sleep(1000 * waitTime);} catch (InterruptedException e) { } rwlock.readLock().lock(); System.out.println("read: " + Resource.value); rwlock.readLock().unlock(); // Should be protected by try...finally... })).limit(NUM_THREADS_READ).toList(); var lstThWrite = Stream.generate(() -> new Thread(() -> { int waitTime = lstArg[ rand.nextInt(lstArg.length) ]; try { Thread.sleep(1000 * waitTime);} catch (InterruptedException e) { } rwlock.writeLock().lock(); Resource.value = rand.nextInt(100); System.out.println("write: " + Resource.value); rwlock.writeLock().unlock(); // Should be protected by try...finally... })).limit(NUM_THREADS_WRITE).toList(); lstThRead.forEach(Thread::start); lstThWrite.forEach(Thread::start); } } class Resource { public static volatile int value; } ================================================ FILE: java/src/demo20_semaphore/AppA01.java ================================================ /* * SEMAPHORES * Version A: Paper sheets and packages */ package demo20_semaphore; import java.util.concurrent.Semaphore; public class AppA01 { public static void main(String[] args) { var semPackage = new Semaphore(0); Runnable makeOneSheet = () -> { for (int i = 0; i < 4; ++i) { try { System.out.println("Make 1 sheet"); Thread.sleep(1000); semPackage.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable combineOnePackage = () -> { for (int i = 0; i < 4; ++i) { try { semPackage.acquire(2); System.out.println("Combine 2 sheets into 1 package"); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(makeOneSheet).start(); new Thread(makeOneSheet).start(); new Thread(combineOnePackage).start(); } } ================================================ FILE: java/src/demo20_semaphore/AppA02.java ================================================ /* * SEMAPHORES * Version A: Paper sheets and packages */ package demo20_semaphore; import java.util.concurrent.Semaphore; public class AppA02 { public static void main(String[] args) { var semPackage = new Semaphore(0); var semSheet = new Semaphore(2); Runnable makeOneSheet = () -> { for (int i = 0; i < 4; ++i) { try { semSheet.acquire(); System.out.println("Make 1 sheet"); semPackage.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable combineOnePackage = () -> { for (int i = 0; i < 4; ++i) { try { semPackage.acquire(2); System.out.println("Combine 2 sheets into 1 package"); Thread.sleep(1000); semSheet.release(2); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(makeOneSheet).start(); new Thread(makeOneSheet).start(); new Thread(combineOnePackage).start(); } } ================================================ FILE: java/src/demo20_semaphore/AppA03.java ================================================ /* * SEMAPHORES * Version A: Paper sheets and packages */ package demo20_semaphore; import java.util.concurrent.Semaphore; public class AppA03 { public static void main(String[] args) { var semPackage = new Semaphore(0); var semSheet = new Semaphore(2); Runnable makeOneSheet = () -> { for (int i = 0; i < 4; ++i) { try { semSheet.acquire(); System.out.println("Make 1 sheet"); semPackage.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable combineOnePackage = () -> { for (int i = 0; i < 4; ++i) { try { semPackage.acquire(2); System.out.println("Combine 2 sheets into 1 package"); Thread.sleep(1000); semSheet.release(1); // The code causes deadlock due to missing one release. // The code should be semSheet.release(2); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(makeOneSheet).start(); new Thread(makeOneSheet).start(); new Thread(combineOnePackage).start(); } } ================================================ FILE: java/src/demo20_semaphore/AppB.java ================================================ /* * SEMAPHORES * Version B: Tires and chassis */ package demo20_semaphore; import java.util.concurrent.Semaphore; public class AppB { public static void main(String[] args) { var semTire = new Semaphore(4); var semChassis = new Semaphore(0); Runnable makeTire = () -> { for (int i = 0; i < 8; ++i) { try { semTire.acquire(); System.out.println("Make 1 tire"); Thread.sleep(1000); semChassis.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable makeChassis = () -> { for (int i = 0; i < 4; ++i) { try { semChassis.acquire(4); System.out.println("Make 1 chassis"); Thread.sleep(3000); semTire.release(4); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(makeTire).start(); new Thread(makeTire).start(); new Thread(makeChassis).start(); } } ================================================ FILE: java/src/demo21_condition_variable/AppA01.java ================================================ /* * CONDITION VARIABLES */ package demo21_condition_variable; public class AppA01 { public static void main(String[] args) { var conditionVar = new Object(); Runnable foo = () -> { try { System.out.println("foo is waiting..."); synchronized (conditionVar) { // foo must own the conditionVar before using it conditionVar.wait(); } System.out.println("foo resumed"); } catch (InterruptedException e) { e.printStackTrace(); } }; Runnable bar = () -> { try { Thread.sleep(3000); } catch (InterruptedException e) { } synchronized (conditionVar) { // bar must own the conditionVar before using it conditionVar.notify(); } }; new Thread(foo).start(); new Thread(bar).start(); } } ================================================ FILE: java/src/demo21_condition_variable/AppA02.java ================================================ /* * CONDITION VARIABLES */ package demo21_condition_variable; public class AppA02 { public static void main(String[] args) { var conditionVar = new Object(); Runnable foo = () -> { try { System.out.println("foo is waiting..."); synchronized (conditionVar) { conditionVar.wait(); } System.out.println("foo resumed"); } catch (InterruptedException e) { e.printStackTrace(); } }; Runnable bar = () -> { for (int i = 0; i < 3; ++i) { try { Thread.sleep(2000); } catch (InterruptedException e) { } synchronized (conditionVar) { conditionVar.notify(); } } }; for (int i = 0; i < 3; ++i) new Thread(foo).start(); new Thread(bar).start(); } } ================================================ FILE: java/src/demo21_condition_variable/AppA03.java ================================================ /* * CONDITION VARIABLES */ package demo21_condition_variable; public class AppA03 { public static void main(String[] args) { var conditionVar = new Object(); Runnable foo = () -> { try { System.out.println("foo is waiting..."); synchronized (conditionVar) { conditionVar.wait(); } System.out.println("foo resumed"); } catch (InterruptedException e) { e.printStackTrace(); } }; Runnable bar = () -> { try { Thread.sleep(3000); } catch (InterruptedException e) { } synchronized (conditionVar) { // Notify all waiting threads conditionVar.notifyAll(); } }; for (int i = 0; i < 3; ++i) new Thread(foo).start(); new Thread(bar).start(); } } ================================================ FILE: java/src/demo21_condition_variable/AppB.java ================================================ /* * CONDITION VARIABLES */ package demo21_condition_variable; public class AppB { public static void main(String[] args) { new FooThread().start(); new EggThread().start(); } } class Global { public static final Object conditionVar = new Object(); public static int counter = 0; public static final int COUNT_HALT_01 = 3; public static final int COUNT_HALT_02 = 6; public static final int COUNT_DONE = 10; } // Write numbers 1-3 and 8-10 as permitted by egg() class FooThread extends Thread { @Override public void run() { for (;;) { synchronized (Global.conditionVar) { try { Global.conditionVar.wait(); Global.counter += 1; System.out.println("foo counter = " + Global.counter); if (Global.counter >= Global.COUNT_DONE) { return; } } catch (InterruptedException e) { e.printStackTrace(); } } } } } // Write numbers 4-7 class EggThread extends Thread { @Override public void run() { for (;;) { synchronized (Global.conditionVar) { if (Global.counter < Global.COUNT_HALT_01 || Global.counter > Global.COUNT_HALT_02) { Global.conditionVar.notify(); } else { Global.counter += 1; System.out.println("egg counter = " + Global.counter); } if (Global.counter >= Global.COUNT_DONE) { return; } } } } } ================================================ FILE: java/src/demo22_blocking_queue/AppA.java ================================================ /* * BLOCKING QUEUES * Version A: A slow producer and a fast consumer */ package demo22_blocking_queue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AppA { public static void main(String[] args) { BlockingQueue queue; queue = new LinkedBlockingQueue<>(); // queue = new ArrayBlockingQueue<>(2); // blocking queue with capacity = 2 new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(BlockingQueue queue) { try { Thread.sleep(2000); queue.put("Alice"); Thread.sleep(2000); queue.put("likes"); Thread.sleep(2000); queue.put("singing"); } catch (InterruptedException e) { e.printStackTrace(); } } private static void consumer(BlockingQueue queue) { String data; try { for (int i = 0; i < 3; ++i) { System.out.println("\nWaiting for data..."); data = queue.take(); System.out.println(" " + data); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/demo22_blocking_queue/AppB.java ================================================ /* * BLOCKING QUEUES * Version B: A fast producer and a slow consumer */ package demo22_blocking_queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class AppB { public static void main(String[] args) { BlockingQueue queue; queue = new ArrayBlockingQueue<>(2); // blocking queue with capacity = 2 new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(BlockingQueue queue) { try { queue.put("Alice"); queue.put("likes"); /* * Due to reaching the maximum capacity = 2, when executing queue.put("singing"), * this thread is going to sleep until the queue removes an element. */ queue.put("singing"); } catch (InterruptedException e) { e.printStackTrace(); } } private static void consumer(BlockingQueue queue) { String data; try { Thread.sleep(2000); for (int i = 0; i < 3; ++i) { System.out.println("\nWaiting for data..."); data = queue.take(); System.out.println(" " + data); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/demo22_blocking_queue/AppExtra.java ================================================ /* * BLOCKING QUEUES * VersionC: Introduction to SynchronousQueue * * The SynchronousQueue is simply the BlockingQueue with zero capacity. * Therefore, each insert operation must wait for a corresponding remove operation by another thread, * and vice versa. */ package demo22_blocking_queue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; public class AppExtra { public static void main(String[] args) { final BlockingQueue queue = new SynchronousQueue<>(); new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(BlockingQueue queue) { try { queue.put("Alice"); queue.put("likes"); queue.put("singing"); } catch (InterruptedException e) { e.printStackTrace(); } } private static void consumer(BlockingQueue queue) { String data; try { Thread.sleep(2000); for (int i = 0; i < 3; ++i) { System.out.println("\nWaiting for data..."); data = queue.take(); System.out.println(" " + data); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/demo23_thread_local/AppA01.java ================================================ /* * THREAD-LOCAL STORAGE * Introduction */ package demo23_thread_local; public class AppA01 { public static void main(String[] args) { // Main thread sets value = "APPLE" MyTask.set("APPLE"); System.out.println(MyTask.get()); // Child thread gets value // Expected output: "NOT SET" new Thread(() -> { System.out.println(MyTask.get()); }).start(); } private static class MyTask { private static final ThreadLocal data = new ThreadLocal<>(); public static String get() { // If this is first time getting data if (null == data.get()) { data.set("NOT SET"); } return data.get(); } public static void set(String value) { data.set(value); } } } ================================================ FILE: java/src/demo23_thread_local/AppA02.java ================================================ /* * THREAD-LOCAL STORAGE * Introduction * * Use ThreadLocal.withInitial for better initialization. */ package demo23_thread_local; public class AppA02 { public static void main(String[] args) { // Main thread sets value = "APPLE" MyTask.set("APPLE"); System.out.println(MyTask.get()); // Child thread gets value // Expected output: "NOT SET" new Thread(() -> { System.out.println(MyTask.get()); }).start(); } private static class MyTask { private static final ThreadLocal data = ThreadLocal.withInitial(() -> "NOT SET"); public static String get() { return data.get(); } public static void set(String value) { data.set(value); } } } ================================================ FILE: java/src/demo23_thread_local/AppB.java ================================================ /* * THREAD-LOCAL STORAGE * Avoiding synchronization using thread-local storage */ package demo23_thread_local; import java.util.stream.IntStream; public class AppB { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 3; var lstTh = IntStream.range(0, NUM_THREADS).mapToObj(t -> new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } for (int i = 0; i < 1000; ++i) MyTask.increaseCounter(); System.out.println("Thread " + t + " gives counter = " + MyTask.getCounter()); })).toList(); lstTh.forEach(Thread::start); /* * By using thread-local storage, each thread has its own counter. * So, the counter in one thread is completely independent of each other. * * Thread-local storage helps us to AVOID SYNCHRONIZATION. */ } private static class Counter { public int value = 0; } private static class MyTask { private static final ThreadLocal thlCounter = ThreadLocal.withInitial(() -> new Counter()); public static int getCounter() { return thlCounter.get().value; } public static void increaseCounter() { var counter = thlCounter.get(); ++counter.value; } } } ================================================ FILE: java/src/demo24_volatile/App.java ================================================ /* * THE VOLATILE KEYWORD */ package demo24_volatile; public class App { public static void main(String[] args) throws InterruptedException { Global.isRunning = true; new Thread(() -> doTask()).start(); Thread.sleep(6000); Global.isRunning = false; } private static void doTask() { while (Global.isRunning) { System.out.println("Running..."); try { Thread.sleep(2000); } catch (InterruptedException e) { } } } private static class Global { public static volatile boolean isRunning; } } ================================================ FILE: java/src/demo25_atomic/AppA.java ================================================ /* * ATOMIC ACCESS */ package demo25_atomic; import java.util.stream.IntStream; public class AppA { public static void main(String[] args) throws InterruptedException { Runnable doTask = () -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } Global.counter += 1; }; var lstTh = IntStream.range(0, 1000).mapToObj(i -> new Thread(doTask)).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); // Unpredictable result System.out.println("counter = " + Global.counter); } private static class Global { public static volatile int counter = 0; } } ================================================ FILE: java/src/demo25_atomic/AppB.java ================================================ /* * ATOMIC ACCESS */ package demo25_atomic; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; public class AppB { public static void main(String[] args) throws InterruptedException { Runnable doTask = () -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } Global.counter.incrementAndGet(); // Global.counter.addAndGet(1); }; var lstTh = IntStream.range(0, 1000).mapToObj(i -> new Thread(doTask)).toList(); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); // counter = 1000 System.out.println("counter = " + Global.counter); } private static class Global { public static AtomicInteger counter = new AtomicInteger(0); } } ================================================ FILE: java/src/demoex/async/AppA.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ package demoex.async; public class AppA { public static void main(String[] args) { System.out.println("Please review demo: Thread pool, version C"); } } ================================================ FILE: java/src/demoex/async/AppB01.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH FUTURE/TASK * * The app takes about 1000 miliseconds to run * because all 3 tasks are running simultaneously. */ package demoex.async; import java.time.Duration; import java.time.Instant; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppB01 { public static void main(String[] args) throws InterruptedException, ExecutionException { // A pool of 5 threads, which helps us to do asynchronous tasks ExecutorService executor = Executors.newFixedThreadPool(5); var timePointStart = Instant.now(); Future taskA = executor.submit(() -> doAction("cooking eggs")); Future taskB = executor.submit(() -> doAction("making coffee")); Future taskC = executor.submit(() -> doAction("watching movies")); taskA.get(); taskB.get(); taskC.get(); var timePointEnd = Instant.now(); var duration = Duration.between(timePointStart, timePointEnd).toMillis(); executor.shutdown(); System.out.println("Total time: " + duration + " millis"); } private static Void doAction(String actionName) { System.out.println(actionName); // Doing action in one second... try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } } ================================================ FILE: java/src/demoex/async/AppB02.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK * * The app takes about 3000 miliseconds to run * because each method "Future.get" pauses app until the task finishes. */ package demoex.async; import java.time.Duration; import java.time.Instant; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppB02 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newFixedThreadPool(5); var timePointStart = Instant.now(); Future taskA = executor.submit(() -> doAction("cooking eggs")); taskA.get(); Future taskB = executor.submit(() -> doAction("making coffee")); taskB.get(); Future taskC = executor.submit(() -> doAction("watching movies")); taskC.get(); var timePointEnd = Instant.now(); var duration = Duration.between(timePointStart, timePointEnd).toMillis(); executor.shutdown(); System.out.println("Total time: " + duration + " millis"); } private static Void doAction(String actionName) { System.out.println(actionName); // Doing action in one second... try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } } ================================================ FILE: java/src/demoex/async/AppB03.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH FUTURE/TASK */ package demoex.async; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppB03 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newFixedThreadPool(5); Future taskCookingEggs = executor.submit(() -> cookEggs()); Future taskMakingCoffee = executor.submit(() -> makeCoffee()); executor.shutdown(); String resultEggs = taskCookingEggs.get(); String resultCoffee = taskMakingCoffee.get(); System.out.println("Done!"); System.out.println(resultEggs); System.out.println(resultCoffee); } private static String cookEggs() { System.out.println("I am cooking eggs"); // Cooking eggs in two seconds... try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "fried eggs"; } private static String makeCoffee() { System.out.println("I am making coffee"); // Making coffee in four seconds... try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } return "a cup of coffee"; } } ================================================ FILE: java/src/demoex/async/AppB04.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ package demoex.async; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AppB04 { private static final ExecutorService executor = Executors.newFixedThreadPool(5); public static void main(String[] args) throws InterruptedException, ExecutionException { Future taskValidation = executor.submit(() -> validate("John")); boolean result = taskValidation.get(); if (result) System.out.println("User can view movies."); else System.out.println("Age must be >= 18 to view movies."); executor.shutdown(); } private static boolean validate(String userName) throws InterruptedException, ExecutionException { var taskGettingAge = executor.submit(() -> queryUserAge(userName)); int userAge = taskGettingAge.get(); System.out.println("Validating..."); // Validating in two seconds... try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return userAge >= 18; } private static int queryUserAge(String userName) { System.out.println("Querying userAge in database..."); // Querying database in two seconds... try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } if (userName.equals("Thanh")) return 26; else return 17; } } ================================================ FILE: java/src/demoex/async/AppC01.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH FUTURE/TASK * * From Javadoc: CompletableFuture is a Future that may be explicitly completed * (setting its value and status), and may be used as a CompletionStage, * supporting dependent functions and actions that trigger upon its completion. * * From that definition, CompletableFuture can be called as "Promise". */ package demoex.async; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class AppC01 { public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture task; task = CompletableFuture .supplyAsync(() -> getSquared(7)) .thenApply(AppC01::getDiv2); Integer result = task.get(); System.out.println(result); } private static int getSquared(int x) { return x * x; } private static int getDiv2(int x) { return x / 2; } } ================================================ FILE: java/src/demoex/async/AppC02.java ================================================ /* * ASYNCHRONOUS PROGRAMMING WITH THE FUTURE/TASK */ package demoex.async; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class AppC02 { public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture task; task = CompletableFuture .supplyAsync(() -> getSquared(7)) .thenApply(AppC02::getDiv2) .thenAccept(System.out::println); task.get(); } private static int getSquared(int x) { return x * x; } private static int getDiv2(int x) { return x / 2; } } ================================================ FILE: java/src/exer01_max_div/AppA.java ================================================ /* * MAXIMUM NUMBER OF DIVISORS */ package exer01_max_div; import java.time.Duration; import java.time.Instant; public class AppA { public static void main(String[] args) { final int RANGE_START = 1; final int RANGE_END = 100000; int resValue = 0; int resNumDiv = 0; // number of divisors of result var tpStart = Instant.now(); for (int i = RANGE_START; i <= RANGE_END; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } var timeElapsed = Duration.between(tpStart, Instant.now()); System.out.println("The integer which has largest number of divisors is " + resValue); System.out.println("The largest number of divisor is " + resNumDiv); System.out.println("Time elapsed = " + timeElapsed.toNanos() / 1e9); } } ================================================ FILE: java/src/exer01_max_div/AppB.java ================================================ /* * MAXIMUM NUMBER OF DIVISORS */ package exer01_max_div; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; public class AppB { public static void main(String[] args) throws InterruptedException { final int RANGE_START = 1; final int RANGE_END = 100000; final int NUM_THREADS = 8; var lstWorkerArg = prepareArg(RANGE_START, RANGE_END, NUM_THREADS); final var lstWorkerRes = new ArrayList(); var lstWorker = lstWorkerArg.stream().map(arg -> new Thread(() -> { int resValue = 0; int resNumDiv = 0; for (int i = arg.iStart; i <= arg.iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } synchronized (lstWorkerRes) { lstWorkerRes.add(new WorkerResult(resValue, resNumDiv)); } })).toList(); var tpStart = Instant.now(); for (var worker : lstWorker) worker.start(); for (var worker : lstWorker) worker.join(); var finalRes = lstWorkerRes.stream().max((lhs, rhs) -> lhs.numDiv - rhs.numDiv).get(); var timeElapsed = Duration.between(tpStart, Instant.now()); System.out.println("The integer which has largest number of divisors is " + finalRes.value); System.out.println("The largest number of divisor is " + finalRes.numDiv); System.out.println("Time elapsed = " + timeElapsed.toNanos() / 1e9); /* * BETTER WAY (avoiding synchronization of lstWorkerRes): * * - Initialize lstWorkerRes with null objects. * Of course, the number of objects is NUM_THREADS. * * - In thread function: * lstWorkerRes.set(threadIndex, new WorkerResult(resValue, resNumDiv)); */ } private static List prepareArg(int rangeStart, int rangeEnd, int numThreads) { int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; var lstWorkerArg = new ArrayList(); for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg.add(new WorkerArg(rangeA, rangeB)); } return lstWorkerArg; } private record WorkerArg(int iStart, int iEnd) { } private record WorkerResult(int value, int numDiv) { } } ================================================ FILE: java/src/exer01_max_div/AppC.java ================================================ /* * MAXIMUM NUMBER OF DIVISORS */ package exer01_max_div; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; public class AppC { public static void main(String[] args) throws InterruptedException { final int RANGE_START = 1; final int RANGE_END = 100000; final int NUM_THREADS = 8; var lstWorkerArg = prepareArg(RANGE_START, RANGE_END, NUM_THREADS); var finalRes = new WorkerResult(); var lstWorker = lstWorkerArg.stream().map(arg -> new Thread(() -> { int resValue = 0; int resNumDiv = 0; for (int i = arg.iStart; i <= arg.iEnd; ++i) { int numDiv = 0; for (int j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } finalRes.update(resValue, resNumDiv); })).toList(); var tpStart = Instant.now(); for (var worker : lstWorker) worker.start(); for (var worker : lstWorker) worker.join(); var timeElapsed = Duration.between(tpStart, Instant.now()); System.out.println("The integer which has largest number of divisors is " + finalRes.value); System.out.println("The largest number of divisor is " + finalRes.numDiv); System.out.println("Time elapsed = " + timeElapsed.toNanos() / 1e9); } private static List prepareArg(int rangeStart, int rangeEnd, int numThreads) { int rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; var lstWorkerArg = new ArrayList(); for (int i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg.add(new WorkerArg(rangeA, rangeB)); } return lstWorkerArg; } private record WorkerArg(int iStart, int iEnd) { } private static class WorkerResult { public int value = 0; public int numDiv = 0; public void update(int value, int numDiv) { synchronized (this) { if (this.numDiv < numDiv) { this.numDiv = numDiv; this.value = value; } } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppA01.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A01: 1 slow producer, 1 fast consumer */ package exer02_producer_consumer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AppA01 { public static void main(String[] args) { var queue = new LinkedBlockingQueue(); new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(BlockingQueue queue) { int i = 1; for (;; ++i) { try { queue.put(i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(BlockingQueue queue) { for (;;) { try { int data = queue.take(); System.out.println("Consumer " + data); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppA02.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A02: 2 slow producers, 1 fast consumer */ package exer02_producer_consumer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AppA02 { public static void main(String[] args) { var queue = new LinkedBlockingQueue(); new Thread(() -> producer(queue)).start(); new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(BlockingQueue queue) { int i = 1; for (;; ++i) { try { queue.put(i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(BlockingQueue queue) { for (;;) { try { int data = queue.take(); System.out.println("Consumer " + data); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppA03.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A03: 1 slow producer, 2 fast consumers */ package exer02_producer_consumer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AppA03 { public static void main(String[] args) { var queue = new LinkedBlockingQueue(); new Thread(() -> producer(queue)).start(); new Thread(() -> consumer("foo", queue)).start(); new Thread(() -> consumer("bar", queue)).start(); } private static void producer(BlockingQueue queue) { int i = 1; for (;; ++i) { try { queue.put(i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(String name, BlockingQueue queue) { for (;;) { try { int data = queue.take(); System.out.println("Consumer %s: %d".formatted(name, data)); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppA04.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE A: USING BLOCKING QUEUES * Version A04: Multiple fast producers, multiple slow consumers */ package exer02_producer_consumer; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.stream.IntStream; public class AppA04 { public static void main(String[] args) { var queue = new ArrayBlockingQueue(5); final int NUM_PRODUCERS = 3; final int NUM_CONSUMERS = 2; IntStream.range(0, NUM_PRODUCERS).forEach( i -> new Thread(() -> producer(queue, i * 1000)).start() ); IntStream.range(0, NUM_CONSUMERS).forEach( i -> new Thread(() -> consumer(queue)).start() ); } private static void producer(BlockingQueue queue, int startValue) { int i = 1; for (;; ++i) { try { queue.put(i + startValue); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(BlockingQueue queue) { for (;;) { try { int data = queue.take(); System.out.println("Consumer " + data); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppB01.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B01: 1 slow producer, 1 fast consumer */ package exer02_producer_consumer; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; public class AppB01 { public static void main(String[] args) { var semFill = new Semaphore(0); // item produced var semEmpty = new Semaphore(1); // remaining space in queue Queue queue = new LinkedList<>(); new Thread(() -> producer(semFill, semEmpty, queue)).start(); new Thread(() -> consumer(semFill, semEmpty, queue)).start(); } private static void producer(Semaphore semFill, Semaphore semEmpty, Queue queue) { int i = 1; for (;; ++i) { try { semEmpty.acquire(); queue.add(i); Thread.sleep(1000); semFill.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (;;) { try { semFill.acquire(); int data = queue.remove(); System.out.println("Consumer " + data); semEmpty.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppB02.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B02: 2 slow producers, 1 fast consumer */ package exer02_producer_consumer; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; public class AppB02 { public static void main(String[] args) { var semFill = new Semaphore(0); // item produced var semEmpty = new Semaphore(1); // remaining space in queue Queue queue = new LinkedList<>(); new Thread(() -> producer(semFill, semEmpty, queue, 0)).start(); new Thread(() -> producer(semFill, semEmpty, queue, 1000)).start(); new Thread(() -> consumer(semFill, semEmpty, queue)).start(); } private static void producer(Semaphore semFill, Semaphore semEmpty, Queue queue, int startValue) { int i = 1; for (;; ++i) { try { semEmpty.acquire(); queue.add(i + startValue); Thread.sleep(1000); semFill.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (;;) { try { semFill.acquire(); int data = queue.remove(); System.out.println("Consumer " + data); semEmpty.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppB03.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B03: 2 fast producers, 1 slow consumer */ package exer02_producer_consumer; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; public class AppB03 { public static void main(String[] args) { var semFill = new Semaphore(0); // item produced var semEmpty = new Semaphore(1); // remaining space in queue Queue queue = new LinkedList<>(); new Thread(() -> producer(semFill, semEmpty, queue, 0)).start(); new Thread(() -> producer(semFill, semEmpty, queue, 1000)).start(); new Thread(() -> consumer(semFill, semEmpty, queue)).start(); } private static void producer(Semaphore semFill, Semaphore semEmpty, Queue queue, int startValue) { int i = 1; for (;; ++i) { try { semEmpty.acquire(); queue.add(i + startValue); semFill.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (;;) { try { semFill.acquire(); int data = queue.remove(); System.out.println("Consumer " + data); Thread.sleep(1000); semEmpty.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppB04.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE B: USING SEMAPHORES * Version B04: Multiple fast producers, multiple slow consumers */ package exer02_producer_consumer; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; import java.util.stream.IntStream; public class AppB04 { public static void main(String[] args) { var semFill = new Semaphore(0); // item produced var semEmpty = new Semaphore(1); // remaining space in queue Queue queue = new LinkedList<>(); final int NUM_PRODUCERS = 3; final int NUM_CONSUMERS = 2; IntStream.range(0, NUM_PRODUCERS).forEach( i -> new Thread(() -> producer(semFill, semEmpty, queue, i * 1000)).start() ); IntStream.range(0, NUM_CONSUMERS).forEach( i -> new Thread(() -> consumer(semFill, semEmpty, queue)).start() ); } private static void producer(Semaphore semFill, Semaphore semEmpty, Queue queue, int startValue) { int i = 1; for (;; ++i) { try { semEmpty.acquire(); queue.add(i + startValue); semFill.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(Semaphore semFill, Semaphore semEmpty, Queue queue) { for (;;) { try { semFill.acquire(); int data = queue.remove(); System.out.println("Consumer " + data); Thread.sleep(1000); semEmpty.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: java/src/exer02_producer_consumer/AppC.java ================================================ /* * THE PRODUCER-CONSUMER PROBLEM * * SOLUTION TYPE C: USING CONDITION VARIABLES & MONITORS * Multiple fast producers, multiple slow consumers */ package exer02_producer_consumer; import java.util.LinkedList; import java.util.Queue; import java.util.stream.IntStream; public class AppC { public static void main(String[] args) { var monitor = new ProdConsMonitor(); Queue queue = new LinkedList<>(); final int MAX_QUEUE_SIZE = 6; final int NUM_PRODUCERS = 3; final int NUM_CONSUMERS = 2; monitor.init(MAX_QUEUE_SIZE, queue); IntStream.range(0, NUM_PRODUCERS).forEach( i -> new Thread(() -> producer(monitor, i * 1000)).start() ); IntStream.range(0, NUM_CONSUMERS).forEach( i -> new Thread(() -> consumer(monitor)).start() ); } private static void producer(ProdConsMonitor monitor, int startValue) { int i = 1; for (;; ++i) { try { monitor.add(i + startValue); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void consumer(ProdConsMonitor monitor) { for (;;) { try { int data = monitor.remove(); System.out.println("Consumer " + data); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class ProdConsMonitor { private Queue queue; private int maxQueueSize; private Object condFull = new Object(); private Object condEmpty = new Object(); public void init(int maxQueueSize, Queue queue) { this.maxQueueSize = maxQueueSize; this.queue = queue; } public void add(T item) throws InterruptedException { synchronized (condFull) { while (queue.size() == maxQueueSize) { condFull.wait(); } synchronized (queue) { queue.add(item); } } synchronized (condEmpty) { if (queue.size() == 1) { condEmpty.notify(); } } } public T remove() throws InterruptedException { T item = null; synchronized (condEmpty) { while (queue.size() == 0) { condEmpty.wait(); } synchronized (queue) { item = queue.remove(); } } synchronized (condFull) { if (queue.size() == maxQueueSize - 1) { condFull.notify(); } } return item; } } ================================================ FILE: java/src/exer03_readers_writers/AppA.java ================================================ /* * THE READERS-WRITERS PROBLEM * Solution for the first readers-writers problem */ package exer03_readers_writers; import java.util.Random; import java.util.concurrent.Semaphore; import java.util.stream.IntStream; public class AppA { public static void main(String[] args) { final int NUM_READERS = 8; final int NUM_WRITERS = 6; var rand = new Random(); var lstThReader = IntStream.range(0, NUM_READERS).mapToObj(i -> new Thread(() -> { try { doTaskReader(rand.nextInt(3)); } catch (InterruptedException e) { e.printStackTrace(); } })); var lstThWriter = IntStream.range(0, NUM_WRITERS).mapToObj(i -> new Thread(() -> { try { doTaskWriter(rand.nextInt(3)); } catch (InterruptedException e) { e.printStackTrace(); } })); lstThReader.forEach(Thread::start); lstThWriter.forEach(Thread::start); } private static void doTaskWriter(int delayTime) throws InterruptedException { var rand = new Random(); Thread.sleep(1000 * delayTime); Global.mutResource.acquire(); Global.resource = rand.nextInt(100); System.out.println("Write " + Global.resource); Global.mutResource.release(); } private static void doTaskReader(int delayTime) throws InterruptedException { Thread.sleep(1000 * delayTime); // Increase reader count synchronized (Global.mutReaderCount) { Global.readerCount += 1; if (1 == Global.readerCount) Global.mutResource.acquire(); } // Do the reading int data = Global.resource; System.out.println("Read " + data); // Decrease reader count synchronized (Global.mutReaderCount) { Global.readerCount -= 1; if (0 == Global.readerCount) Global.mutResource.release(); } } private static class Global { public static final Semaphore mutResource = new Semaphore(1); public static final Object mutReaderCount = new Object(); public static volatile int resource = 0; public static int readerCount = 0; } } ================================================ FILE: java/src/exer03_readers_writers/AppB.java ================================================ /* * THE READERS-WRITERS PROBLEM * Solution for the third readers-writers problem */ package exer03_readers_writers; import java.util.Random; import java.util.concurrent.Semaphore; import java.util.stream.IntStream; public class AppB { public static void main(String[] args) { final int NUM_READERS = 8; final int NUM_WRITERS = 6; var rand = new Random(); var lstThReader = IntStream.range(0, NUM_READERS).mapToObj(i -> new Thread(() -> { try { doTaskReader(rand.nextInt(3)); } catch (InterruptedException e) { e.printStackTrace(); } })); var lstThWriter = IntStream.range(0, NUM_WRITERS).mapToObj(i -> new Thread(() -> { try { doTaskWriter(rand.nextInt(3)); } catch (InterruptedException e) { e.printStackTrace(); } })); lstThReader.forEach(Thread::start); lstThWriter.forEach(Thread::start); } private static void doTaskWriter(int delayTime) throws InterruptedException { var rand = new Random(); Thread.sleep(1000 * delayTime); synchronized (Global.mutServiceQueue) { Global.mutResource.acquire(); } Global.resource = rand.nextInt(100); System.out.println("Write " + Global.resource); Global.mutResource.release(); } private static void doTaskReader(int delayTime) throws InterruptedException { Thread.sleep(1000 * delayTime); synchronized (Global.mutServiceQueue) { // Increase reader count synchronized (Global.mutReaderCount) { Global.readerCount += 1; if (1 == Global.readerCount) Global.mutResource.acquire(); } } // Do the reading int data = Global.resource; System.out.println("Read " + data); // Decrease reader count synchronized (Global.mutReaderCount) { Global.readerCount -= 1; if (0 == Global.readerCount) Global.mutResource.release(); } } private static class Global { public static final Object mutServiceQueue = new Object(); public static final Semaphore mutResource = new Semaphore(1); public static final Object mutReaderCount = new Object(); public static volatile int resource = 0; public static int readerCount = 0; } } ================================================ FILE: java/src/exer04_dining_philosophers/AppA.java ================================================ /* * THE DINING PHILOSOPHERS PROBLEM */ package exer04_dining_philosophers; import java.util.concurrent.Semaphore; import java.util.stream.IntStream; public class AppA { public static void main(String[] args) { final int NUM_PHILOSOPHERS = 5; var chopstick = new Semaphore[NUM_PHILOSOPHERS]; for (int i = 0; i < NUM_PHILOSOPHERS; ++i) chopstick[i] = new Semaphore(1); var lstTh = IntStream.range(0, NUM_PHILOSOPHERS).mapToObj(i -> new Thread(() -> { int n = NUM_PHILOSOPHERS; try { Thread.sleep(1000); chopstick[i].acquire(); chopstick[(i + 1) % n].acquire(); System.out.println("Philosopher #" + i + " is eating the rice"); chopstick[(i + 1) % n].release(); chopstick[i].release(); } catch (InterruptedException e) { e.printStackTrace(); } })); lstTh.forEach(Thread::start); } } ================================================ FILE: java/src/exer04_dining_philosophers/AppB.java ================================================ /* * THE DINING PHILOSOPHERS PROBLEM */ package exer04_dining_philosophers; import java.util.stream.IntStream; public class AppB { public static void main(String[] args) { final int NUM_PHILOSOPHERS = 5; var chopstick = new Object[NUM_PHILOSOPHERS]; for (int i = 0; i < NUM_PHILOSOPHERS; ++i) chopstick[i] = new Object(); var lstTh = IntStream.range(0, NUM_PHILOSOPHERS).mapToObj(i -> new Thread(() -> { int n = NUM_PHILOSOPHERS; try { Thread.sleep(1000); synchronized (chopstick[i]) { synchronized (chopstick[(i + 1) % n]) { System.out.println("Philosopher #" + i + " is eating the rice"); } } } catch (InterruptedException e) { e.printStackTrace(); } })); lstTh.forEach(Thread::start); } } ================================================ FILE: java/src/exer05_product_matrix/AppA.java ================================================ /* * MATRIX-VECTOR MULTIPLICATION */ package exer05_product_matrix; import java.util.Arrays; import java.util.stream.IntStream; public class AppA { public static void main(String[] args) throws InterruptedException { double[][] A = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; double[] b = { 3, -1, 0 }; var result = getProduct(A, b); System.out.println(Arrays.toString(result)); } private static double[] getProduct(double[][] mat, double[] vec) throws InterruptedException { // Assume that size of mat and vec are both eligible int sizeRowMat = mat.length; // int sizeColMat = mat[0].length; // int sizeVec = vec.length; var result = new double[sizeRowMat]; var lstTh = IntStream.range(0, sizeRowMat).mapToObj(i -> new Thread(() -> { var u = mat[i]; var v = vec; result[i] = MyUtil.getScalarProduct(u, v); })).toList(); for (Thread th : lstTh) th.start(); for (Thread th : lstTh) th.join(); return result; } } ================================================ FILE: java/src/exer05_product_matrix/AppB.java ================================================ /* * MATRIX-MATRIX MULTIPLICATION (DOT PRODUCT) */ package exer05_product_matrix; import java.util.ArrayList; import java.util.stream.IntStream; public class AppB { public static void main(String[] args) throws InterruptedException { double[][] A = { { 1, 3, 5 }, { 2, 4, 6 }, }; double[][] B = { { 1, 0, 1, 0 }, { 0, 1, 0, 1 }, { 1, 0, 0, -2 } }; double[][] result = getProduct(A, B); MyUtil.printMatrix(result); } private static double[][] getProduct(double[][] matA, double[][] matB) throws InterruptedException { // Assume that size of matA and matB are both eligible int sizeRowA = matA.length; int sizeColB = matB[0].length; double[][] matBT = MyUtil.getTransposeMatrix(matB); var result = new double[sizeRowA][sizeColB]; var lstTh = new ArrayList(); IntStream.range(0, sizeRowA).forEach(i -> IntStream.range(0, sizeColB).forEach(j -> { var u = matA[i]; var v = matBT[j]; lstTh.add(new Thread(() -> { result[i][j] = MyUtil.getScalarProduct(u, v); })); }) ); for (var th : lstTh) th.start(); for (var th : lstTh) th.join(); return result; } } ================================================ FILE: java/src/exer05_product_matrix/MyUtil.java ================================================ package exer05_product_matrix; import java.text.NumberFormat; public class MyUtil { private static final NumberFormat nf; static { nf = NumberFormat.getInstance(); nf.setMaximumFractionDigits(1); } public static double getScalarProduct(double[] u, double[] v) { double sum = 0; int sizeVector = u.length; for (int i = sizeVector - 1; i >= 0; --i) { sum += u[i] * v[i]; } return sum; } public static double[][] getTransposeMatrix(double[][] input) { double[][] output; int numRow = input.length; int numCol = input[0].length; output = new double[numCol][numRow]; for (int i = 0; i < numRow; ++i) for (int j = 0; j < numCol; ++j) output[j][i] = input[i][j]; return output; } public static void printMatrix(double[][] mat) { for (var row : mat) { for (var value : row) System.out.print("\t" + nf.format(value)); System.out.println(); } } } ================================================ FILE: java/src/exer06_blocking_queue/AppA.java ================================================ /* * BLOCKING QUEUE IMPLEMENTATION * Version A: Synchronous queues */ package exer06_blocking_queue; import java.util.concurrent.Semaphore; public class AppA { public static void main(String[] args) { final var queue = new MySynchronousQueue(); new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(MySynchronousQueue queue) { String[] arr = { "lorem", "ipsum", "dolor" }; try { for (var data : arr) { System.out.println("Producer: " + data); queue.put(data); System.out.println("Producer: " + data + "\t\t\t[done]"); } } catch (InterruptedException e) { e.printStackTrace(); } } private static void consumer(MySynchronousQueue queue) { String data; try { Thread.sleep(5000); for (int i = 0; i < 3; ++i) { data = queue.take(); System.out.println("\tConsumer: " + data); } } catch (InterruptedException e) { e.printStackTrace(); } } private static class MySynchronousQueue { private final Semaphore semPut = new Semaphore(1); private final Semaphore semTake = new Semaphore(0); private T element = null; public void put(T value) throws InterruptedException { semPut.acquire(); element = value; semTake.release(); } public T take() throws InterruptedException { semTake.acquire(); T result = element; element = null; semPut.release(); return result; } } } ================================================ FILE: java/src/exer06_blocking_queue/AppB01.java ================================================ /* * BLOCKING QUEUE IMPLEMENTATION * Version B01: General blocking queues * Underlying mechanism: Semaphores */ package exer06_blocking_queue; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; public class AppB01 { public static void main(String[] args) { final var queue = new MyBlockingQueue(2); // capacity = 2 new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(MyBlockingQueue queue) { String[] arr = { "nice", "to", "meet", "you" }; try { for (var data : arr) { System.out.println("Producer: " + data); queue.put(data); System.out.println("Producer: " + data + "\t\t\t[done]"); } } catch (InterruptedException e) { e.printStackTrace(); } } private static void consumer(MyBlockingQueue queue) { String data; try { Thread.sleep(5000); for (int i = 0; i < 4; ++i) { data = queue.take(); System.out.println("\tConsumer: " + data); if (0 == i) Thread.sleep(5000); } } catch (InterruptedException e) { e.printStackTrace(); } } ////////////////////////////////////////////// private static class MyBlockingQueue { private final Semaphore semRemain; private final Semaphore semFill; private int capacity = 0; private final Queue queue; public MyBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException("capacity must be a positive integer"); this.capacity = capacity; semRemain = new Semaphore(this.capacity); semFill = new Semaphore(0); queue = new LinkedList(); } public void put(T value) throws InterruptedException { semRemain.acquire(); synchronized (queue) { queue.add(value); } semFill.release(); } public T take() throws InterruptedException { T result; semFill.acquire(); synchronized (queue) { result = queue.remove(); } semRemain.release(); return result; } } } ================================================ FILE: java/src/exer06_blocking_queue/AppB02.java ================================================ /* * BLOCKING QUEUE IMPLEMENTATION * Version B02: General blocking queues * Underlying mechanism: Condition variables */ package exer06_blocking_queue; import java.util.LinkedList; import java.util.Queue; public class AppB02 { public static void main(String[] args) { final var queue = new MyBlockingQueue(2); // capacity = 2 new Thread(() -> producer(queue)).start(); new Thread(() -> consumer(queue)).start(); } private static void producer(MyBlockingQueue queue) { String[] arr = { "nice", "to", "meet", "you" }; try { for (var data : arr) { System.out.println("Producer: " + data); queue.put(data); System.out.println("Producer: " + data + "\t\t\t[done]"); } } catch (InterruptedException e) { e.printStackTrace(); } } private static void consumer(MyBlockingQueue queue) { String data; try { Thread.sleep(5000); for (int i = 0; i < 4; ++i) { data = queue.take(); System.out.println("\tConsumer: " + data); if (0 == i) Thread.sleep(5000); } } catch (InterruptedException e) { e.printStackTrace(); } } ////////////////////////////////////////////// private static class MyBlockingQueue { private final Object condEmpty = new Object(); private final Object condFull = new Object(); private int capacity = 0; private final Queue queue; public MyBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException("capacity must be a positive integer"); this.capacity = capacity; queue = new LinkedList(); } public void put(T value) throws InterruptedException { synchronized (condFull) { while (queue.size() >= capacity) { // Queue is full, must wait for 'take' condFull.wait(); } synchronized (queue) { queue.add(value); } } synchronized (condEmpty) { condEmpty.notify(); } } public T take() throws InterruptedException { T result; synchronized (condEmpty) { while (queue.isEmpty()) { // Queue is empty, must wait for 'put' condEmpty.wait(); } synchronized (queue) { result = queue.remove(); } } synchronized (condFull) { condFull.notify(); } return result; } } } ================================================ FILE: java/src/exer07_data_server/AppA.java ================================================ /* * THE DATA SERVER PROBLEM * Version A: Solving the problem using a condition variable */ package exer07_data_server; public class AppA { public static void main(String[] args) throws InterruptedException { var server = new DataServer(); server.processRequest(); } private static class DataServer { private class Counter { public int value; public Counter(int value) { this.value = value; } } public void processRequest() throws InterruptedException { final var lstFileName = new String[] { "foo.html", "bar.json" }; final var counter = new Counter(lstFileName.length); // The server checks auth user while reading files, concurrently new Thread(() -> processFiles(lstFileName, counter)).start(); checkAuthUser(); // The server waits for completion of loading files synchronized (counter) { while (counter.value > 0) { counter.wait(10000); // timeout = 10 seconds } } System.out.println("\nNow user is authorized and files are loaded"); System.out.println("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { System.out.println("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... sleepNoEx(20); System.out.println("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, Counter counter) { for (var fileName : lstFileName) { // Read file System.out.println("[ ReadFile ] Start " + fileName); sleepNoEx(10); System.out.println("[ ReadFile ] Done " + fileName); synchronized (counter) { --counter.value; counter.notify(); } // Write log into disk sleepNoEx(5); System.out.println("[ WriteLog ]"); } } private static void sleepNoEx(long seconds) { try { Thread.sleep(1000 * seconds); } catch (InterruptedException e) { } } } } ================================================ FILE: java/src/exer07_data_server/AppB.java ================================================ /* * THE DATA SERVER PROBLEM * Version B: Solving the problem using a semaphore */ package exer07_data_server; import java.util.concurrent.Semaphore; public class AppB { public static void main(String[] args) throws InterruptedException { var server = new DataServer(); server.processRequest(); } private static class DataServer { public void processRequest() throws InterruptedException { final var lstFileName = new String[] { "foo.html", "bar.json" }; final var sem = new Semaphore(0); // The server checks auth user while reading files, concurrently new Thread(() -> processFiles(lstFileName, sem)).start(); checkAuthUser(); // The server waits for completion of loading files for (int i = lstFileName.length; i > 0; --i) { sem.acquire(); } System.out.println("\nNow user is authorized and files are loaded"); System.out.println("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { System.out.println("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... sleepNoEx(20); System.out.println("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, Semaphore sem) { for (var fileName : lstFileName) { // Read file System.out.println("[ ReadFile ] Start " + fileName); sleepNoEx(10); System.out.println("[ ReadFile ] Done " + fileName); sem.release(); // Write log into disk sleepNoEx(5); System.out.println("[ WriteLog ]"); } } private static void sleepNoEx(long seconds) { try { Thread.sleep(1000 * seconds); } catch (InterruptedException e) { } } } } ================================================ FILE: java/src/exer07_data_server/AppC.java ================================================ /* * THE DATA SERVER PROBLEM * Version C: Solving the problem using a count-down latch */ package exer07_data_server; import java.util.concurrent.CountDownLatch; public class AppC { public static void main(String[] args) throws InterruptedException { var server = new DataServer(); server.processRequest(); } private static class DataServer { public void processRequest() throws InterruptedException { final var lstFileName = new String[] { "foo.html", "bar.json" }; // Count down for 2 files final var readFileLatch = new CountDownLatch(lstFileName.length); // The server checks auth user while reading files, concurrently new Thread(() -> processFiles(lstFileName, readFileLatch)).start(); checkAuthUser(); // The server waits for completion of loading files readFileLatch.await(); System.out.println("\nNow user is authorized and files are loaded"); System.out.println("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { System.out.println("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... sleepNoEx(20); System.out.println("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, CountDownLatch latch) { for (var fileName : lstFileName) { // Read file System.out.println("[ ReadFile ] Start " + fileName); sleepNoEx(10); System.out.println("[ ReadFile ] Done " + fileName); latch.countDown(); // Write log into disk sleepNoEx(5); System.out.println("[ WriteLog ]"); } } private static void sleepNoEx(long seconds) { try { Thread.sleep(1000 * seconds); } catch (InterruptedException e) { } } } } ================================================ FILE: java/src/exer07_data_server/AppD.java ================================================ /* * THE DATA SERVER PROBLEM * Version D: Solving the problem using a blocking queue */ package exer07_data_server; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AppD { public static void main(String[] args) throws InterruptedException { var server = new DataServer(); server.processRequest(); } private static class DataServer { public void processRequest() throws InterruptedException { final var lstFileName = new String[] { "foo.html", "bar.json" }; final var queue = new LinkedBlockingQueue(); // The server checks auth user while reading files, concurrently new Thread(() -> processFiles(lstFileName, queue)).start(); checkAuthUser(); // The server waits for completion of loading files for (int i = lstFileName.length; i > 0; --i) { queue.take(); } System.out.println("\nNow user is authorized and files are loaded"); System.out.println("Do other tasks...\n"); } // This task consumes CPU (and network bandwidth, maybe) private void checkAuthUser() { System.out.println("[ Auth ] Start"); // Send request to authenticator, check permissions, encrypt, decrypt... sleepNoEx(20); System.out.println("[ Auth ] Done"); } // This task consumes disk private void processFiles(String[] lstFileName, BlockingQueue queue) { for (var fileName : lstFileName) { // Read file System.out.println("[ ReadFile ] Start " + fileName); sleepNoEx(10); System.out.println("[ ReadFile ] Done " + fileName); try { queue.put(fileName); // You may put file data here } catch (InterruptedException e) { } // Write log into disk sleepNoEx(5); System.out.println("[ WriteLog ]"); } } private static void sleepNoEx(long seconds) { try { Thread.sleep(1000 * seconds); } catch (InterruptedException e) { } } } } ================================================ FILE: java/src/exer08_exec_service/App.java ================================================ /* * EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION */ package exer08_exec_service; import java.util.ArrayList; public class App { public static void main(String[] args) throws InterruptedException { final int NUM_THREADS = 2; final int NUM_TASKS = 5; var execService = new MyExecServiceV0B(NUM_THREADS); var lstTask = new ArrayList(); for (int i = 0; i < NUM_TASKS; ++i) lstTask.add(new MyTask((char)('A' + i))); lstTask.forEach(task -> execService.submit(task)); System.out.println("All tasks are submitted"); execService.waitTaskDone(); System.out.println("All tasks are completed"); execService.shutdown(); } private static class MyTask implements Runnable { public char id; public MyTask(char id) { this.id = id; } @Override public void run() { System.out.println("Task " + id + " is starting"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task " + id + " is completed"); } } } ================================================ FILE: java/src/exer08_exec_service/MyExecServiceV0A.java ================================================ /* * MY EXECUTOR SERVICE * * Version 0A: The easiest executor service * - It uses a blocking queue as underlying mechanism. */ package exer08_exec_service; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.IntStream; public final class MyExecServiceV0A { private int numThreads = 0; private List lstTh = new LinkedList<>(); private final BlockingQueue taskPending = new LinkedBlockingQueue<>(); public MyExecServiceV0A(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { numThreads = inpNumThreads; lstTh = IntStream.range(0, numThreads) .mapToObj(i -> new Thread(() -> threadWorkerFunc(this))) .toList(); lstTh.forEach(Thread::start); } public void submit(Runnable task) { taskPending.add(task); } public void waitTaskDone() { // This ExecService is too simple, // so there is no implementation for waitTaskDone() try { Thread.sleep(11000); // fake behaviour } catch (InterruptedException e) { e.printStackTrace(); } } public void shutdown() { // This ExecService is too simple, // so there is no implementation for shutdown() System.out.println("No implementation for shutdown()."); System.out.println("You need to exit the app manually."); } private static void threadWorkerFunc(MyExecServiceV0A thisPtr) { Runnable task; try { for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK task = thisPtr.taskPending.take(); // DO THE TASK task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/exer08_exec_service/MyExecServiceV0B.java ================================================ /* * MY EXECUTOR SERVICE * * Version 0B: The easiest executor service * - It uses a blocking queue as underlying mechanism. * - It supports waitTaskDone() and shutdown(). */ package exer08_exec_service; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; public final class MyExecServiceV0B { private int numThreads = 0; private List lstTh = new LinkedList<>(); private final BlockingQueue taskPending = new LinkedBlockingQueue<>(); private final AtomicInteger counterTaskRunning = new AtomicInteger(); private volatile boolean forceThreadShutdown = false; private static final Runnable emptyTask = () -> { }; public MyExecServiceV0B(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { numThreads = inpNumThreads; counterTaskRunning.set(0); forceThreadShutdown = false; lstTh = IntStream.range(0, numThreads) .mapToObj(i -> new Thread(() -> threadWorkerFunc(this))) .toList(); lstTh.forEach(Thread::start); } public void submit(Runnable task) { taskPending.add(task); } public void waitTaskDone() { // This ExecService is too simple, // so there is no good implementation for waitTaskDone() try { while (!taskPending.isEmpty() || counterTaskRunning.get() > 0) { Thread.sleep(1000); // Thread.yield(); } } catch (InterruptedException e) { e.printStackTrace(); } } public void shutdown() { forceThreadShutdown = true; taskPending.clear(); // Invoke blocked threads by adding "empty" tasks for (int i = 0; i < numThreads; ++i) { taskPending.add(emptyTask); } for (var th : lstTh) { try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } } numThreads = 0; // lstTh.clear(); } private static void threadWorkerFunc(MyExecServiceV0B thisPtr) { Runnable task; try { for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK task = thisPtr.taskPending.take(); // If shutdown() was called, then exit the function if (thisPtr.forceThreadShutdown) { break; } // DO THE TASK thisPtr.counterTaskRunning.incrementAndGet(); task.run(); thisPtr.counterTaskRunning.decrementAndGet(); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/exer08_exec_service/MyExecServiceV1A.java ================================================ /* * MY EXECUTOR SERVICE * * Version 1A: Simple executor service * - Method "waitTaskDone" invokes thread sleeps in loop (which can cause performance problems). */ package exer08_exec_service; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; public final class MyExecServiceV1A { private int numThreads = 0; private List lstTh = new LinkedList<>(); private final Queue taskPending = new LinkedList<>(); private final AtomicInteger counterTaskRunning = new AtomicInteger(); private volatile boolean forceThreadShutdown = false; public MyExecServiceV1A(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; counterTaskRunning.set(0); forceThreadShutdown = false; lstTh = IntStream.range(0, numThreads) .mapToObj(i -> new Thread(() -> threadWorkerFunc(this))) .toList(); lstTh.forEach(Thread::start); } public void submit(Runnable task) { synchronized (taskPending) { taskPending.add(task); taskPending.notify(); } } public void waitTaskDone() { boolean done = false; try { for (;;) { synchronized (taskPending) { if (taskPending.isEmpty() && 0 == counterTaskRunning.get()) { done = true; } } if (done) break; Thread.sleep(1000); // Thread.yield(); } } catch (InterruptedException e) { e.printStackTrace(); } } public void shutdown() { synchronized (taskPending) { forceThreadShutdown = true; taskPending.clear(); taskPending.notifyAll(); } for (var th : lstTh) { try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } } numThreads = 0; // lstTh.clear(); } private static void threadWorkerFunc(MyExecServiceV1A thisPtr) { var taskPending = thisPtr.taskPending; var counterTaskRunning = thisPtr.counterTaskRunning; Runnable task; try { for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK synchronized (taskPending) { while (taskPending.isEmpty() && false == thisPtr.forceThreadShutdown) { taskPending.wait(); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.remove(); counterTaskRunning.getAndIncrement(); } // DO THE TASK task.run(); counterTaskRunning.getAndDecrement(); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/exer08_exec_service/MyExecServiceV1B.java ================================================ /* * MY EXECUTOR SERVICE * * Version 1B: Simple executor service * - Method "waitTaskDone" uses a condition variable to synchronize. */ package exer08_exec_service; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.stream.IntStream; public final class MyExecServiceV1B { private int numThreads = 0; private List lstTh = new LinkedList<>(); private final Queue taskPending = new LinkedList<>(); private int counterTaskRunning; private final Object lkTaskRunning = new Object(); private volatile boolean forceThreadShutdown = false; public MyExecServiceV1B(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; counterTaskRunning = 0; forceThreadShutdown = false; lstTh = IntStream.range(0, numThreads) .mapToObj(i -> new Thread(() -> threadWorkerFunc(this))) .toList(); lstTh.forEach(Thread::start); } public void submit(Runnable task) { synchronized (taskPending) { taskPending.add(task); taskPending.notify(); } } public void waitTaskDone() { for (;;) { synchronized (taskPending) { if (taskPending.isEmpty()) { synchronized (lkTaskRunning) { try { while (counterTaskRunning > 0) { lkTaskRunning.wait(); } // no pending task and no running task break; } catch (InterruptedException e) { e.printStackTrace(); } } } } } } public void shutdown() { synchronized (taskPending) { forceThreadShutdown = true; taskPending.clear(); taskPending.notifyAll(); } for (var th : lstTh) { try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } } numThreads = 0; // lstTh.clear(); } private static void threadWorkerFunc(MyExecServiceV1B thisPtr) { var taskPending = thisPtr.taskPending; var lkTaskRunning = thisPtr.lkTaskRunning; Runnable task; try { for (;;) { // WAIT FOR AN AVAILABLE PENDING TASK synchronized (taskPending) { while (taskPending.isEmpty() && false == thisPtr.forceThreadShutdown) { taskPending.wait(); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.remove(); ++thisPtr.counterTaskRunning; } // DO THE TASK task.run(); synchronized (lkTaskRunning) { --thisPtr.counterTaskRunning; if (0 == thisPtr.counterTaskRunning) { lkTaskRunning.notify(); } } } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/exer08_exec_service/MyExecServiceV2A.java ================================================ /* * MY EXECUTOR SERVICE * * Version 2A: The executor service storing running tasks * - Method "waitTaskDone" uses a semaphore to synchronize. */ package exer08_exec_service; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.Semaphore; import java.util.stream.IntStream; public final class MyExecServiceV2A { private int numThreads = 0; private List lstTh = new LinkedList<>(); private final Queue taskPending = new LinkedList<>(); private final Queue taskRunning = new LinkedList<>(); private final Semaphore counterTaskRunning = new Semaphore(0); private volatile boolean forceThreadShutdown = false; public MyExecServiceV2A(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; forceThreadShutdown = false; lstTh = IntStream.range(0, numThreads) .mapToObj(i -> new Thread(() -> threadWorkerFunc(this))) .toList(); lstTh.forEach(Thread::start); } public void submit(Runnable task) { synchronized (taskPending) { taskPending.add(task); taskPending.notify(); } } public void waitTaskDone() { for (;;) { try { counterTaskRunning.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (taskPending) { synchronized (taskRunning) { if (taskPending.isEmpty() && taskRunning.isEmpty() /* && 0 == counterTaskRunning.availablePermits() */ ) break; } } } } public void shutdown() { synchronized (taskPending) { forceThreadShutdown = true; taskPending.clear(); taskPending.notifyAll(); } for (var th : lstTh) { try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } } numThreads = 0; // lstTh.clear(); taskRunning.clear(); counterTaskRunning.release(counterTaskRunning.availablePermits()); } private static void threadWorkerFunc(MyExecServiceV2A thisPtr) { var taskPending = thisPtr.taskPending; var taskRunning = thisPtr.taskRunning; var counterTaskRunning = thisPtr.counterTaskRunning; Runnable task; try { for (;;) { synchronized (taskPending) { // WAIT FOR AN AVAILABLE PENDING TASK while (taskPending.isEmpty() && false == thisPtr.forceThreadShutdown) { taskPending.wait(); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.remove(); // PUSH IT TO THE RUNNING QUEUE synchronized (taskRunning) { taskRunning.add(task); } } // DO THE TASK task.run(); // REMOVE IT FROM THE RUNNING QUEUE synchronized (taskRunning) { taskRunning.remove(task); } counterTaskRunning.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: java/src/exer08_exec_service/MyExecServiceV2B.java ================================================ /* * MY EXECUTOR SERVICE * * Version 2B: The executor service storing running tasks * - Method "waitTaskDone" uses a condition variable to synchronize. */ package exer08_exec_service; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.stream.IntStream; public final class MyExecServiceV2B { private int numThreads = 0; private List lstTh = new LinkedList<>(); private final Queue taskPending = new LinkedList<>(); private final Queue taskRunning = new LinkedList<>(); private volatile boolean forceThreadShutdown = false; public MyExecServiceV2B(int numThreads) { init(numThreads); } private void init(int inpNumThreads) { // shutdown(); numThreads = inpNumThreads; forceThreadShutdown = false; lstTh = IntStream.range(0, numThreads) .mapToObj(i -> new Thread(() -> threadWorkerFunc(this))) .toList(); lstTh.forEach(Thread::start); } public void submit(Runnable task) { synchronized (taskPending) { taskPending.add(task); taskPending.notify(); } } // public void waitTaskDoneBad() { // try { // for (;;) { // synchronized (taskRunning) { // while (!taskRunning.isEmpty()) // taskRunning.wait(); // // synchronized (taskPending) { // if (taskPending.isEmpty()) // break; // } // } // } // } // catch (InterruptedException e) { // e.printStackTrace(); // } // } public void waitTaskDone() { try { for (;;) { synchronized (taskPending) { if (taskPending.isEmpty()) { synchronized (taskRunning) { while (!taskRunning.isEmpty()) taskRunning.wait(); // no pending task and no running task break; } } } } } catch (InterruptedException e) { e.printStackTrace(); } } public void shutdown() { synchronized (taskPending) { forceThreadShutdown = true; taskPending.clear(); taskPending.notifyAll(); } for (var th : lstTh) { try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } } numThreads = 0; // lstTh.clear(); taskRunning.clear(); } private static void threadWorkerFunc(MyExecServiceV2B thisPtr) { var taskPending = thisPtr.taskPending; var taskRunning = thisPtr.taskRunning; Runnable task; try { for (;;) { synchronized (taskPending) { // WAIT FOR AN AVAILABLE PENDING TASK while (taskPending.isEmpty() && false == thisPtr.forceThreadShutdown) { taskPending.wait(); } if (thisPtr.forceThreadShutdown) { break; } // GET THE TASK FROM THE PENDING QUEUE task = taskPending.remove(); // PUSH IT TO THE RUNNING QUEUE synchronized (taskRunning) { taskRunning.add(task); } } // DO THE TASK task.run(); // REMOVE IT FROM THE RUNNING QUEUE synchronized (taskRunning) { taskRunning.remove(task); taskRunning.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: js-nodejs/.gitignore ================================================ /package-lock.json /node_modules/ ================================================ FILE: js-nodejs/demo00.js ================================================ /* INTRODUCTION TO MULTITHREADING You should try running this app several times and see results. */ import * as mylib from './mylib.js'; import { Worker, isMainThread } from 'worker_threads'; const workerFunc = async () => { for (let i = 0; i < 300; ++i) { await mylib.sleep(1); process.stdout.write('B'); } }; const mainFunc = async () => { const worker = new Worker(new URL(import.meta.url)); for (let i = 0; i < 300; ++i) { await mylib.sleep(1); process.stdout.write('A'); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo01a-hello.js ================================================ /* HELLO WORLD VERSION MULTITHREADING Version A: Using the same source code file for both main thread and worker thread */ import { Worker, isMainThread } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const workerFunc = () => { console.log('Hello from example thread'); }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = () => { const worker = new Worker(new URL(import.meta.url)); console.log('Hello from main thread'); }; if (isMainThread) { // If this is main thread then execute mainFunc() mainFunc(); } else { // If this is worker thread then execute workerFunc() workerFunc(); } ================================================ FILE: js-nodejs/demo01b-hello-worker.js ================================================ console.log('Hello from example thread'); ================================================ FILE: js-nodejs/demo01b-hello.js ================================================ /* HELLO WORLD VERSION MULTITHREADING Version B: Using the individual source code files: - Main thread: demo01b-hello.js - Worker thread: demo01b-hello-worker.js */ import * as path from 'path'; import { Worker, isMainThread } from 'worker_threads'; const workerFileName = `./${path.parse(import.meta.url).name}-worker.js`; const worker = new Worker(workerFileName); console.log('Hello from main thread'); ================================================ FILE: js-nodejs/demo02-join.js ================================================ /* THREAD JOINS */ import * as mylib from './mylib.js'; import { isMainThread } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doHeavyTask = async () => { // Do a heavy task, which takes a little time for (let i = 0; i < 2000000000; ++i); console.log('Done!'); }; const workerFunc = async () => { try { await doHeavyTask(); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const [worker, prom] = mylib.createThread(new URL(import.meta.url)); console.log('Begin creating worker thread...'); await prom; // join worker thread (i.e. waiting for the thread completion) console.log('Good bye!'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo03a-pass-arg.js ================================================ /* PASSING ARGUMENTS */ import { isMainThread, Worker, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = input => { console.log('Input value is', input); }; const workerFunc = () => { try { doTask(workerData); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = () => { try { const worker = new Worker( new URL(import.meta.url), { workerData: 12345 } ); const worker2 = new Worker( new URL(import.meta.url), { workerData: { name: 'foo', types: [9, 0, 55] } } ); } catch (error) { console.error(error); } }; if (isMainThread) { mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo03b-pass-arg.js ================================================ /* PASSING ARGUMENTS */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = input => { console.log('Input value is', input); }; const workerFunc = () => { try { doTask(workerData); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const [worker, prom] = mylib.createThread( new URL(import.meta.url), 12345 ); const [worker2, prom2] = mylib.createThread( new URL(import.meta.url), { name: 'foo', types: [9, 0, 55] } ); console.log('Created 2 worker threads'); await Promise.all([prom, prom2]); console.log('Good bye'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo04-sleep.js ================================================ /* SLEEP */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = async (name, timems) => { console.log(name, 'is sleeping'); await mylib.sleep(timems); console.log(name, 'wakes up'); }; const workerFunc = async () => { try { await doTask(workerData.name, workerData.timems); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const [workerFoo, promFoo] = mylib.createThread( new URL(import.meta.url), { name: 'foo', timems: 3000 } ); const [workerBar, promBar] = mylib.createThread( new URL(import.meta.url), { name: 'bar', timems: 2900 } ); console.log('Created 2 worker threads'); await Promise.all([promFoo, promBar]); console.log('Good bye'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo05-id.js ================================================ /* GETTING THREAD'S ID */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = async () => { await mylib.sleep(2000); console.log('Done'); }; const workerFunc = async () => { try { await doTask(); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const [worker, prom] = mylib.createThread(new URL(import.meta.url)); const [worker2, prom2] = mylib.createThread(new URL(import.meta.url)); console.log(worker.threadId, worker2.threadId); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo06a-list-threads.js ================================================ /* LIST OF MULTIPLE THREADS VERSION A: Using standard 'Worker' class */ import { Worker, isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = index => { console.log(index); }; const workerFunc = () => { try { doTask(workerData); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = () => { try { const NUM_THREADS = 5; const lstTh = new Array(NUM_THREADS); for (let i = 0; i < NUM_THREADS; ++i) { lstTh[i] = new Worker(new URL(import.meta.url), { workerData: i }); } console.log('Good bye'); } catch (error) { console.error(error); } }; if (isMainThread) { mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo06b-list-threads.js ================================================ /* LIST OF MULTIPLE THREADS VERSION B: Using mylib.createThread */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = index => { console.log(index); }; const workerFunc = () => { try { doTask(workerData); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const NUM_THREADS = 5; const lstTh = new Array(NUM_THREADS); for (let i = 0; i < NUM_THREADS; ++i) { lstTh[i] = mylib.createThread(new URL(import.meta.url), i); } await Promise.all(lstTh.map(([_,pr]) => pr)); console.log('Good bye'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo07-terminate.js ================================================ /* FORCING A THREAD TO TERMINATE (i.e. killing the thread) */ import * as mylib from './mylib.js'; import { isMainThread, parentPort } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- let workerThreadIsRunning = true; const doTask = async () => { while (workerThreadIsRunning) { console.log('Running...'); await mylib.sleep(2000); } }; const onRecvMessage = msg => { if (msg === 'term') { workerThreadIsRunning = false; } }; const workerFunc = async () => { try { parentPort.once('message', onRecvMessage); await doTask(); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const [worker, prom] = mylib.createThread(new URL(import.meta.url)); await mylib.sleep(6000); worker.postMessage('term'); await prom; console.log('Worker thread is terminated'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo08a-return-value.js ================================================ /* GETTING RETURNED VALUES FROM THREADS */ import * as mylib from './mylib.js'; import { isMainThread, workerData, parentPort } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doubleValue = x => { return x * 2; }; const squareValue = x => { return x * x; }; const workerFunc = () => { try { const [command, inp] = workerData; let result; switch (command) { case 'double': result = doubleValue(inp); console.log({command, inp, result}); break; case 'square': result = squareValue(inp); console.log({command, inp, result}); break; } } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = () => { try { mylib.createThread(new URL(import.meta.url), ['double', 5]); mylib.createThread(new URL(import.meta.url), ['double', 80]); mylib.createThread(new URL(import.meta.url), ['square', 7]); } catch (error) { console.error(error); } }; if (isMainThread) { mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo08b-return-value.js ================================================ /* GETTING RETURNED VALUES FROM THREADS */ import * as mylib from './mylib.js'; import { isMainThread, parentPort } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doubleValue = x => { return x * 2; }; const squareValue = x => { return x * x; }; const onRecvMessage = message => { const [command, inp] = message; let result; switch (command) { case 'double': result = doubleValue(inp); parentPort.postMessage({command, inp, result}); break; case 'square': result = squareValue(inp); parentPort.postMessage({command, inp, result}); break; case 'exit': parentPort.unref(); // parentPort.close(); } }; const workerFunc = () => { try { parentPort.on('message', onRecvMessage); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const [worker, prom] = mylib.createThread(new URL(import.meta.url)); worker.on('message', console.log); worker.postMessage(['double', 5]); worker.postMessage(['double', 80]); worker.postMessage(['square', 7]); worker.postMessage(['exit']); await prom; } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo11a-exec-service.js ================================================ /* EXECUTOR SERVICES AND THREAD POOLS */ import { isMainThread } from 'worker_threads'; import { Piscina } from 'piscina'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- export const printMsg = () => { console.log('Hello Nodejs'); }; export const addTwoNumbers = ({a, b}) => { return a + b; }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const execService = new Piscina({filename: new URL(import.meta.url).href}); await execService.run(undefined, { name: 'printMsg'}); const result = await execService.run({a: 1000, b: -400}, { name: 'addTwoNumbers'}); console.log('Result is', result); await execService.destroy(); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } ================================================ FILE: js-nodejs/demo11b-exec-service.js ================================================ /* EXECUTOR SERVICES AND THREAD POOLS */ import * as mylib from './mylib.js'; import { isMainThread } from 'worker_threads'; import { Piscina } from 'piscina'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- export const doTask = async ({index}) => { const id = String.fromCharCode(65 + index); console.log(`Task ${id} is starting`); await mylib.sleep(3000); console.log(`Task ${id} is completed`); }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const NUM_THREADS = 2; const NUM_TASKS = 5; const lstPromRes = []; const execService = new Piscina({ filename: new URL(import.meta.url).href, minThreads: NUM_THREADS, maxThreads: NUM_THREADS }); for (let i = 0; i < NUM_TASKS; ++i) { lstPromRes.push(execService.run({ index: i }, { name: 'doTask'})); } console.log('All tasks are submitted'); await Promise.all(lstPromRes); console.log('All tasks are completed'); await execService.destroy(); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } ================================================ FILE: js-nodejs/demo12-race-condition.js ================================================ /* RACE CONDITIONS */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = async index => { await mylib.sleep(1000); console.log(index); }; const workerFunc = async () => { try { await doTask(workerData); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = () => { try { const NUM_THREADS = 4; const lstTh = new Array(NUM_THREADS); for (let i = 0; i < NUM_THREADS; ++i) { lstTh[i] = mylib.createThread(new URL(import.meta.url), i); } } catch (error) { console.error(error); } }; if (isMainThread) { mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo12b01-data-race-single.js ================================================ /* DATA RACES Version 01: Without multithreading */ const getResult = N => { const a = new Int8Array(N + 1).fill(0); for (let i = 1; i <= N; ++i) { if (i % 2 === 0 || i % 3 === 0) a[i] = 1; } const result = a.reduce((x, y) => x + y, 0); return result; }; const mainFunc = () => { const N = 1024; const result = getResult(N); console.log('Number of integers that are divisible by 2 or 3 is:', result); }; mainFunc(); ================================================ FILE: js-nodejs/demo12b02-data-race-multi.js ================================================ /* DATA RACES Version 02: Multithreading */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const markDiv2 = (a, N) => { for (let i = 2; i <= N; i += 2) { a[i] = 1; } }; const markDiv3 = (a, N) => { for (let i = 3; i <= N; i += 3) { a[i] = 1; } }; const workerFunc = () => { try { const [action, a, N] = workerData; switch (action) { case 'markDiv2': markDiv2(a, N); break; case 'markDiv3': markDiv3(a, N); break; } } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const N = 1024; const a = new Int8Array(new SharedArrayBuffer((N + 1) * Int8Array.BYTES_PER_ELEMENT)); const [wkr2, prom2] = mylib.createThread(new URL(import.meta.url), [ 'markDiv2', a, N ]); const [wkr3, prom3] = mylib.createThread(new URL(import.meta.url), [ 'markDiv3', a, N ]); await Promise.all([prom2, prom3]); const result = a.reduce((x, y) => x + y, 0); console.log('Number of integers that are divisible by 2 or 3 is:', result); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/demo12c-race-cond-data-race.js ================================================ /* RACE CONDITIONS AND DATA RACES */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const increaseCounter = async bufArr => { await mylib.sleep(1000); for (let i = 0; i < 1000; ++i) { // increase counter by one ++bufArr[0]; } }; const workerFunc = async () => { try { await increaseCounter(workerData); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const bufArr = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)); // counter is the first element of bufArr const NUM_THREADS = 16; const lstTh = new Array(NUM_THREADS); for (let i = 0; i < NUM_THREADS; ++i) { lstTh[i] = mylib.createThread(new URL(import.meta.url), bufArr); } await Promise.all(lstTh.map(([_,pr]) => pr)); console.log('counter =', bufArr[0]); // We are NOT sure that counter = 16000 } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo13-mutex.js ================================================ /* MUTEXES */ import * as mylib from './mylib.js'; import { Mutex } from './mylib_mutex.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const increaseCounter = async (bufArr, mutex) => { await mylib.sleep(1000); mutex.acquire(); try { for (let i = 0; i < 1000; ++i) { // increase counter by one ++bufArr[0]; } } finally { mutex.release(); } // or... // mutex.runExclusive(() => { // for (let i = 0; i < 1000; ++i) { // // increase counter by one // ++bufArr[0]; // } // }); }; const workerFunc = async () => { try { const [bufArr, mutexsab] = workerData; const mutex = new Mutex(mutexsab); await increaseCounter(bufArr, mutex); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const mutex = new Mutex(); const bufArr = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)); // counter is the first element of bufArr const NUM_THREADS = 16; const lstTh = new Array(NUM_THREADS); for (let i = 0; i < NUM_THREADS; ++i) { lstTh[i] = mylib.createThread(new URL(import.meta.url), [ bufArr, mutex.getSAB() ]); } await Promise.all(lstTh.map(([_,pr]) => pr)); console.log('counter =', bufArr[0]); // We are sure that counter = 16000 } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo13ex-mutex-problem.js ================================================ /* MUTEXES Version EX In this demo, we will face the race condition. -------------------------------------> Time | Send request '/register?id=Teo&name=Tran Teo' | ooooo Send request '/register?id=Teo&name=Le Teo' | xxxxx Check database and create user(id='Teo', name='Tran Teo') | oooooooooo Check database and create user(id='Teo', name='Le Teo') | xxxxxxxxxx The final result is that the database contains user(id='Teo', name='Le Teo'). Due to the slow process time from server, user 'Tran Teo' is overwritten by user 'Le Teo'. We do not want this result. What we want is: - Request '/register?id=Teo&name=Tran Teo' comes first, so user(id='Teo', name='Tran Teo') is created first. - Request '/register?id=Teo&name=Le Teo' comes later, and this results a failure due to id 'Teo' is existed. -------------------------------------> Time | Send request '/register?id=Teo&name=Tran Teo' | ooooo Send request '/register?id=Teo&name=Le Teo' | (wait)......xxxxx Check database and create user(id='Teo', name='Tran Teo') | oooooooooo Check database and create user(id='Teo', name='Le Teo') | xxxxxxxxxx */ import * as mylib from './mylib.js'; import express from 'express'; const createServer = () => { const users = new Map(); const app = express(); const createUser = async (userid, username) => { if (!userid || !username) { return false; } if (users.has(userid)) { return false; } // Assume that creating user takes a little time await mylib.sleep(1000); users.set(userid, username); return true; }; app.get('/register', async (req, res) => { const ret = await createUser(req.query.id, req.query.name); res.status(200).send(ret).end(); }); app.get('/', (req, res) => { const resStr = JSON.stringify( [...users].map(([k,v]) => ({ id: k, name: v })) , null, 2); res.status(200).setHeader('Content-Type', 'application/json'); res.send(resStr).end(); }); const server = app.listen(8081); return server; }; const runClient = async () => { const registerUrl1 = new URL('/register?id=teo&name=Tran Teo', 'http://localhost:8081'); const registerUrl2 = new URL('/register?id=teo&name=Le Teo', 'http://localhost:8081'); const infoUrl = new URL('/', 'http://localhost:8081'); const prom1 = mylib.makeHttpGet(registerUrl1); await mylib.sleep(200); const prom2 = mylib.makeHttpGet(registerUrl2); await mylib.sleep(200); await Promise.all([prom1, prom2]); const result = await mylib.makeHttpGet(infoUrl); console.log(result); }; const server = createServer(); await runClient(); server.close(); ================================================ FILE: js-nodejs/demo13ex-mutex-solve.js ================================================ /* MUTEXES Version EX Using a mutex to solve the problem. */ import * as mylib from './mylib.js'; import express from 'express'; import { Mutex } from 'async-mutex'; const createServer = () => { const users = new Map(); const app = express(); const mutex = new Mutex(); const createUser = async (userid, username) => { if (!userid || !username) { return false; } if (users.has(userid)) { return false; } // Assume that creating user takes a little time await mylib.sleep(1000); users.set(userid, username); return true; }; app.get('/register', async (req, res) => { const ret = await mutex.runExclusive(() => createUser(req.query.id, req.query.name)); // const ret = await createUser(req.query.id, req.query.name); res.status(200).send(ret).end(); }); app.get('/', (req, res) => { const resStr = JSON.stringify( [...users].map(([k,v]) => ({ id: k, name: v })) , null, 2); res.status(200).setHeader('Content-Type', 'application/json'); res.send(resStr).end(); }); const server = app.listen(8081); return server; }; const runClient = async () => { const registerUrl1 = new URL('/register?id=teo&name=Tran Teo', 'http://localhost:8081'); const registerUrl2 = new URL('/register?id=teo&name=Le Teo', 'http://localhost:8081'); const infoUrl = new URL('/', 'http://localhost:8081'); const prom1 = mylib.makeHttpGet(registerUrl1); await mylib.sleep(200); const prom2 = mylib.makeHttpGet(registerUrl2); await mylib.sleep(200); await Promise.all([prom1, prom2]); const result = await mylib.makeHttpGet(infoUrl); console.log(result); }; const server = createServer(); await runClient(); server.close(); ================================================ FILE: js-nodejs/demo15a-deadlock.js ================================================ /* DEADLOCK Version A */ import * as mylib from './mylib.js'; import { Mutex } from './mylib_mutex.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = async (name, mutex) => { mutex.acquire(); console.log(name, 'acquired resource'); // mutex.release(); // Forget this statement ==> deadlock }; const workerFunc = async () => { try { const [name, mutexsab] = workerData; const mutex = new Mutex(mutexsab); doTask(name, mutex); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const mutex = new Mutex(); const [wkFoo, promFoo] = mylib.createThread(new URL(import.meta.url), [ 'foo', mutex.getSAB() ]); const [wkBar, promBar] = mylib.createThread(new URL(import.meta.url), [ 'bar', mutex.getSAB() ]); await Promise.all([promFoo, promBar]); console.log('You will never see this statement due to deadlock!'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo15b-deadlock.js ================================================ /* DEADLOCK Version B */ import * as mylib from './mylib.js'; import { Mutex } from './mylib_mutex.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const foo = async (mutResourceA, mutResourceB) => { mutResourceA.acquire(); console.log('foo acquired resource A'); await mylib.sleep(2000); mutResourceB.acquire(); console.log('foo acquired resource B'); mutResourceB.release(); mutResourceA.release(); }; const bar = async (mutResourceA, mutResourceB) => { mutResourceB.acquire(); console.log('bar acquired resource B'); await mylib.sleep(2000); mutResourceA.acquire(); console.log('bar acquired resource A'); mutResourceA.release(); mutResourceB.release(); }; const workerFunc = async () => { try { const [action, mutexsabA, mutexsabB] = workerData; const mutResourceA = new Mutex(mutexsabA); const mutResourceB = new Mutex(mutexsabB); switch (action) { case 'foo': await foo(mutResourceA, mutResourceB); break; case 'bar': await bar(mutResourceA, mutResourceB); break; } } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { const mutResourceA = new Mutex(); const mutResourceB = new Mutex(); const [wkFoo, promFoo] = mylib.createThread(new URL(import.meta.url), [ 'foo', mutResourceA.getSAB(), mutResourceB.getSAB() ]); const [wkBar, promBar] = mylib.createThread(new URL(import.meta.url), [ 'bar', mutResourceA.getSAB(), mutResourceB.getSAB() ]); await Promise.all([promFoo, promBar]); console.log('You will never see this statement due to deadlock!'); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/demo15ex-deadlock.js ================================================ /* DEADLOCK Version EX */ import * as mylib from './mylib.js'; import express from 'express'; import { Mutex } from 'async-mutex'; const createServer = () => { const app = express(); const mutex = new Mutex(); const doTask = async name => { const release = await mutex.acquire(); try { console.log(`Server: ${name} acquired resource`); } finally { // release(); // Forget this statement ==> deadlock } }; app.get('/', async (req, res) => { const name = req.query.name; await doTask(name); res.status(200).setHeader('Content-Type', 'text/html'); res.send(name).end(); }); const server = app.listen(8081); return server; }; const runClient = async () => { const respFoo = await mylib.makeHttpGet(new URL('http://localhost:8081/?name=foo')); console.log(`Client: response: ${respFoo}`); const respBar = await mylib.makeHttpGet(new URL('http://localhost:8081/?name=bar')); console.log('You will never see this statement due to deadlock!'); console.log(`Client: response: ${respBar}`); }; const server = createServer(); await runClient(); server.close(); ================================================ FILE: js-nodejs/demo25-atomic.js ================================================ /* ATOMIC ACCESS */ import * as mylib from './mylib.js'; import { isMainThread, workerData } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const updateArr = async (arr) => { await mylib.sleep(1000); for (let i = 0; i < 1000; ++i) { // increase arr[0] by one atomically Atomics.add(arr, 0, 1); // decrease arr[4] by five atomically Atomics.sub(arr, 4, 5); } }; const workerFunc = async () => { try { const sharedArr = workerData; const arr = new Int32Array(sharedArr); await updateArr(arr); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const mainFunc = async () => { try { // bufArr is an array of which max length = 5 // Its element is bufArr[0], bufArr[1], ... bufArr[4] const ARR_MAX_SIZE = 5; const sharedArr = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * ARR_MAX_SIZE); const arr = new Int32Array(sharedArr); const NUM_THREADS = 16; const lstTh = new Array(NUM_THREADS); for (let i = 0; i < NUM_THREADS; ++i) { lstTh[i] = mylib.createThread(new URL(import.meta.url), sharedArr ); } await Promise.all(lstTh.map(([_,pr]) => pr)); console.log('The result:'); console.log(arr); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { await workerFunc(); } ================================================ FILE: js-nodejs/exer01a-max-div.js ================================================ /* MAXIMUM NUMBER OF DIVISORS */ import * as mylib from './mylib.js'; const mainFunc = () => { try { const RANGE_START = 1; const RANGE_END = 100000; let resValue = 0; let resNumDiv = 0; const tpStart = process.hrtime(); for (let i = RANGE_START; i <= RANGE_END; ++i) { let numDiv = 0; for (let j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } const timeElapsed = mylib.hrtimeToNumber(process.hrtime(tpStart)); console.log('The integer which has largest number of divisors is', resValue); console.log('The largest number of divisor is', resNumDiv); console.log('Time elapsed =', timeElapsed); } catch (error) { console.error(error); } }; mainFunc(); ================================================ FILE: js-nodejs/exer01c-max-div.js ================================================ /* MAXIMUM NUMBER OF DIVISORS */ import * as mylib from './mylib.js'; import { Mutex } from './mylib_mutex.js'; import { isMainThread, workerData, parentPort } from 'worker_threads'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const doTask = arg => { let resValue = 0; let resNumDiv = 0; for (let i = arg.iStart; i <= arg.iEnd; ++i) { let numDiv = 0; for (let j = i / 2; j > 0; --j) if (i % j == 0) ++numDiv; if (resNumDiv < numDiv) { resNumDiv = numDiv; resValue = i; } } parentPort.postMessage({ resValue, resNumDiv }); }; const workerFunc = () => { try { const arg = workerData; doTask(arg); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const prepareArg = (rangeStart, rangeEnd, numThreads) => { let rangeA, rangeB, rangeBlock; rangeBlock = (rangeEnd - rangeStart + 1) / numThreads; rangeA = rangeStart; let lstWorkerArg = []; for (let i = 0; i < numThreads; ++i, rangeA += rangeBlock) { rangeB = rangeA + rangeBlock - 1; if (i == numThreads - 1) rangeB = rangeEnd; lstWorkerArg.push({ iStart: rangeA, iEnd: rangeB }); } return lstWorkerArg; }; const updateFinalResult = (mutex, finalRes, value, numDiv) => { // Should protect by a mutex? mutex.runExclusive(() => { if (finalRes.numDiv < numDiv) { finalRes.numDiv = numDiv; finalRes.value = value; } }); }; const mainFunc = async () => { try { const RANGE_START = 1; const RANGE_END = 100000; const NUM_THREADS = 8; const lstWorkerArg = prepareArg(RANGE_START, RANGE_END, NUM_THREADS); const finalRes = { value: 0, numDiv: 0 }; const lstTh = new Array(NUM_THREADS); const mutex = new Mutex(); const tpStart = process.hrtime(); for (let i = 0; i < NUM_THREADS; ++i) { const arg = lstWorkerArg[i]; const [worker, prom] = mylib.createThread(new URL(import.meta.url), arg); worker.on('message', res => updateFinalResult(mutex, finalRes, ...Object.values(res))); lstTh[i] = [worker, prom]; } await Promise.all(lstTh.map(([_,pr]) => pr)); const timeElapsed = mylib.hrtimeToNumber(process.hrtime(tpStart)); console.log('The integer which has largest number of divisors is', finalRes.value); console.log('The largest number of divisor is', finalRes.numDiv); console.log('Time elapsed =', timeElapsed); } catch (error) { console.error(error); } }; if (isMainThread) { await mainFunc(); } else { workerFunc(); } ================================================ FILE: js-nodejs/exerex-userhash-problem.js ================================================ /* USERNAME HASH PROBLEM This is an app using expressJS framework to serve "The Username Hash Service". The app provides 2 APIs: GET /?name= Returns the and the hashed value from GET /history Returns request history. Each is in one line. The Hash Task is consider CPU consuming. You should wait for a long time to get result (may upto 24 seconds). Now, let's get started: - Run the app. Open the web browser and visit http://localhost:8081/?name=JohnnyTeo - While waiting for result, try visiting http://localhost:8081/history Notice that /history is blocked until completion of hash result calculation. The reason is Javascript by default runs in a single thread. This thread is busy calculating hash result from request /?name=JohnnyTeo so it cannot serve the next request /history Your task is making /history "non-blocking" i.e. app can serve /history while doing hashing job. P/S: The problem idea is inspired by a great article at: https://www.digitalocean.com/community/tutorials/how-to-use-multithreading-in-node-js */ import * as mylib from './mylib.js'; import express from 'express'; import { getHash, splitStrInToChunks } from './exerex-userhash-util.js'; const PORT = 8081; const app = express(); const getSuperHash = plainText => { const numChunks = 8; const lstChunks = splitStrInToChunks(numChunks, plainText); const lstHashes = lstChunks.map(chunk => getHash(2**21, chunk)); const finalHash = getHash(1, lstHashes.join('')); return finalHash; }; const userNameHistory = []; app.get('/history', async (req, res) => { const html = userNameHistory.join('
') || '<Empty history>'; res.status(200).send(html).end(); }); app.get('/', (req, res) => { const userName = req.query.name; if (!userName) { res.status(400).end(); return; } userNameHistory.push(userName); const tpStart = process.hrtime(); // GET USERNAME HASH const userNameHash = getSuperHash(userName); const timeElapsed = mylib.hrtimeToNumber(process.hrtime(tpStart)); console.log(`userName = ${userName}; Time elapsed = ${timeElapsed}`); const html = userName + '
' + userNameHash; res.status(200).setHeader('Content-Type', 'text/html').send(html).end(); }); console.log('Server is listening on port', PORT); const server = app.listen(PORT); ================================================ FILE: js-nodejs/exerex-userhash-solve01.js ================================================ /* USERNAME HASH PROBLEM To make /history "non-blocking", run app in 2 threads: - The main thread: Serves I/O HTTP requests - The worker thread: Does the Hash Task When users visit http://localhost:8081/?name=JohnnyTeo, the app delegates the Hash Task to the worker thread so it can serve the next requests. The worker thread receives and does the Hash Task and returns result to main thread. */ import * as mylib from './mylib.js'; import { isMainThread, workerData, parentPort } from 'worker_threads'; import express from 'express'; import { getHash, splitStrInToChunks } from './exerex-userhash-util.js'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- const getSuperHashByWorker = plainText => { const numChunks = 8; const lstChunks = splitStrInToChunks(numChunks, plainText); const lstHashes = lstChunks.map(chunk => getHash(2**21, chunk)); const finalHash = getHash(1, lstHashes.join('')); return finalHash; }; const workerFunc = () => { try { const plainText = workerData; const hash = getSuperHashByWorker(plainText); parentPort.postMessage(hash); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const getSuperHashByMain = async plainText => { let finalHash = ''; const [worker, prom] = mylib.createThread(new URL(import.meta.url), plainText); worker.on('message', hash => finalHash = hash); await prom; return finalHash; }; const mainFunc = () => { const PORT = 8081; const app = express(); const userNameHistory = []; app.get('/history', async (req, res) => { const html = userNameHistory.join('
') || '<Empty history>'; res.status(200).send(html).end(); }); app.get('/', async (req, res) => { const userName = req.query.name; if (!userName) { res.status(400).end(); return; } userNameHistory.push(userName); const tpStart = process.hrtime(); // GET USERNAME HASH const userNameHash = await getSuperHashByMain(userName); const timeElapsed = mylib.hrtimeToNumber(process.hrtime(tpStart)); console.log(`userName = ${userName}; Time elapsed = ${timeElapsed}`); const html = userName + '
' + userNameHash; res.status(200).setHeader('Content-Type', 'text/html').send(html).end(); }); console.log('Server is listening on port', PORT); const server = app.listen(PORT); }; if (isMainThread) { try { mainFunc(); } catch (error) { console.error(error); } } else { workerFunc(); } ================================================ FILE: js-nodejs/exerex-userhash-solve02-faster.js ================================================ /* USERNAME HASH PROBLEM Although the problem is solved in previous solution, the calculation speed keeps too slow. Analyze the Hash Task and you realize that this task can be divided into multiple individual sub tasks. Each sub task is corresponding to hashing one chunk. Hence, multithreading makes sense: Each thread does each sub task in parallel. */ import * as mylib from './mylib.js'; import { isMainThread, workerData, parentPort } from 'worker_threads'; import express from 'express'; import { getHash, splitStrInToChunks } from './exerex-userhash-util.js'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- export const workerFunc = () => { try { const [idx, chunk] = workerData; const chunkHash = getHash(2**21, chunk); parentPort.postMessage([idx, chunkHash]); } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const getSuperHashByMain = async (plainText) => { const numChunks = 8; // It is also the number of threads const lstChunks = splitStrInToChunks(numChunks, plainText); const lstHashes = new Array(numChunks); const lstWorkerProm = []; for (let i = 0; i < numChunks; ++i) { const chunk = lstChunks[i]; const [worker, prom] = mylib.createThread(new URL(import.meta.url), [i, chunk]); worker.on('message', ([idx, chunkHash]) => lstHashes[idx] = chunkHash); lstWorkerProm.push(prom); } await Promise.all(lstWorkerProm); const finalHash = getHash(1, lstHashes.join('')); return finalHash; }; const mainFunc = () => { const PORT = 8081; const app = express(); const userNameHistory = []; app.get('/history', async (req, res) => { const html = userNameHistory.join('
') || '<Empty history>'; res.status(200).send(html).end(); }); app.get('/', async (req, res) => { const userName = req.query.name; if (!userName) { res.status(400).end(); return; } userNameHistory.push(userName); const tpStart = process.hrtime(); // GET USERNAME HASH const userNameHash = await getSuperHashByMain(userName); const timeElapsed = mylib.hrtimeToNumber(process.hrtime(tpStart)); console.log(`userName = ${userName}; Time elapsed = ${timeElapsed}`); const html = userName + '
' + userNameHash; res.status(200).setHeader('Content-Type', 'text/html').send(html).end(); }); console.log('Server is listening on port', PORT); const server = app.listen(PORT); }; if (isMainThread) { try { mainFunc(); } catch (error) { console.error(error); } } else { workerFunc(); } ================================================ FILE: js-nodejs/exerex-userhash-solve03-faster.js ================================================ /* USERNAME HASH PROBLEM Each time users send requests to hash, threads are created. Constantly taking requests and creating new threads is a matter of concern. By using Execution Service/Thread Pool, threads can be reused for next tasks/next requests (i.e. no more thread creation). */ import * as mylib from './mylib.js'; import { isMainThread } from 'worker_threads'; import { Piscina } from 'piscina'; import express from 'express'; import { getHash, splitStrInToChunks } from './exerex-userhash-util.js'; //--------------------------------------------- // WORKER THREAD SECTION //--------------------------------------------- export const workerFunc = ({idx, chunk}) => { try { const chunkHash = getHash(2**21, chunk); return chunkHash; } catch (error) { console.error(error); } }; //--------------------------------------------- // MAIN THREAD SECTION //--------------------------------------------- const getSuperHashByMain = async (execService, plainText) => { const numChunks = 8; // It is also the number of threads const lstChunks = splitStrInToChunks(numChunks, plainText); const lstWorkerProm = []; for (let i = 0; i < numChunks; ++i) { const chunk = lstChunks[i]; lstWorkerProm.push(execService.run({ idx: i, chunk: chunk }, { name: 'workerFunc' })); } const lstHashes = await Promise.all(lstWorkerProm); const finalHash = getHash(1, lstHashes.join('')); return finalHash; }; const mainFunc = () => { const PORT = 8081; const app = express(); const userNameHistory = []; const execService = new Piscina({ filename: new URL(import.meta.url).href, minThreads: 8, maxThreads: 8 }); app.get('/history', async (req, res) => { const html = userNameHistory.join('
') || '<Empty history>'; res.status(200).send(html).end(); }); app.get('/', async (req, res) => { const userName = req.query.name; if (!userName) { res.status(400).end(); return; } userNameHistory.push(userName); const tpStart = process.hrtime(); // GET USERNAME HASH const userNameHash = await getSuperHashByMain(execService, userName); const timeElapsed = mylib.hrtimeToNumber(process.hrtime(tpStart)); console.log(`userName = ${userName}; Time elapsed = ${timeElapsed}`); const html = userName + '
' + userNameHash; res.status(200).setHeader('Content-Type', 'text/html').send(html).end(); }); console.log('Server is listening on port', PORT); const server = app.listen(PORT); }; if (isMainThread) { try { mainFunc(); } catch (error) { console.error(error); } } ================================================ FILE: js-nodejs/exerex-userhash-util.js ================================================ const { createHash } = await import('node:crypto'); export const getHash = (numLoops, str) => { for (let i = numLoops; i > 0; --i) { str = createHash('sha256').update(str).digest('hex'); } return str; }; export const splitStrInToChunks = (numChunks, str) => { const lstChunks = []; const quotient = Math.floor(str.length / numChunks); const remainder = str.length % numChunks; for (let i = 0; i < numChunks; ++i) { const chunk = str.substring( i * quotient + Math.min(i, remainder), (i+1) * quotient + Math.min(i+1, remainder) ); lstChunks.push(chunk); } return lstChunks; }; ================================================ FILE: js-nodejs/mylib.js ================================================ import * as http from 'http'; import { Worker } from 'worker_threads'; export const sleep = timems => new Promise(resolve => setTimeout(resolve, timems)); export const hrtimeToNumber = hrtime => (hrtime[0] + (hrtime[1] / 1e9)).toFixed(6); export const createThread = (filename, input_args) => { const worker = new Worker(filename, { workerData: input_args }); const prom = new Promise((resolve, reject) => { // worker.on('message', msg => resolve(msg)); worker.on('error', error => reject([worker, error])); worker.on('exit', code => code !== 0 ? reject([worker, code]) : resolve(worker)); }); return [worker, prom]; }; export const makeHttpGet = requestUrl => new Promise((resolve, reject) => { const req = http.get(requestUrl, res => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { resolve(body); }); res.on('error', error => reject(error)); }); req.end(); }); ================================================ FILE: js-nodejs/mylib_mutex.js ================================================ /****************************************************** * * File name: mylib_mutex.js * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The mutex implementation in Javascript ES2019 * * Warning: This mutex shall be used for truly-multithreading conditions (e.g. NodeJS Worker) * ******************************************************/ export class Mutex { #sabuff; #sa; constructor(sharedArrayBuffer) { if (!sharedArrayBuffer) { this.#sabuff = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT); } else { Mutex.#requireValidStateSAB(sharedArrayBuffer); this.#sabuff = sharedArrayBuffer; } this.#sa = new Int32Array(this.#sabuff ); } getSAB() { return this.#sabuff; } static #requireValidStateSAB(sharedArrayBuffer) { if (! sharedArrayBuffer ) { throw 'sharedArrayBuffer is null or undefined'; } if (! (sharedArrayBuffer instanceof SharedArrayBuffer)) { throw 'Illegal type of sharedArrayBuffer'; } if (sharedArrayBuffer.byteLength < Int32Array.BYTES_PER_ELEMENT) { throw `Illegal sharedArrayBuffer: byteLength=${sharedArrayBuffer.byteLength} < Int32Array.BYTES_PER_ELEMENT`; } } static #requireValidStateSA(sharedArray) { if (! sharedArray ) { throw 'sharedArray is null or undefined'; } if (! (sharedArray instanceof Int32Array) ) { throw 'Illegal type of sharedArray'; } if (sharedArray.byteLength < Int32Array.BYTES_PER_ELEMENT) { throw `Illegal sharedArray: byteLength=${sharedArray.byteLength} < Int32Array.BYTES_PER_ELEMENT`; } const sab = sharedArray.buffer; this.#requireValidStateSAB(sab); } #requireValidState() { Mutex.#requireValidStateSA(this.#sa); } acquire() { this.#requireValidState(); while (true) { if (Atomics.load(this.#sa, 0) > 0) { while (Atomics.wait(this.#sa, 0, 0) !== 'ok'); } let oldCounter = Atomics.add(this.#sa, 0, 1); if (oldCounter >= 1) { Atomics.sub(this.#sa, 0, 1); continue; } // Atomics.notify(this.#sa, 0, 1); return; } } release() { this.#requireValidState(); if (Atomics.load(this.#sa, 0) > 0) { Atomics.sub(this.#sa, 0, 1); } Atomics.notify(this.#sa, 0, 1); } runExclusive(callbackFunc) { this.#requireValidState(); if (typeof callbackFunc !== 'function') { throw 'Illegal type of callbackFunc'; } let res = undefined; this.acquire(); try { res = callbackFunc(); } finally { this.release(); } return res; } } //--------------------------------------------- // TESTING AREA //--------------------------------------------- // import { Worker, isMainThread, workerData } from 'worker_threads'; // const sleep = timems => new Promise(resolve => setTimeout(resolve, timems)); // const createThread = (filename, input_args) => { // const worker = new Worker(filename, { workerData: input_args }); // const prom = new Promise((resolve, reject) => { // // worker.on('message', msg => resolve(msg)); // worker.on('error', error => reject([worker, error])); // worker.on('exit', code => code !== 0 ? reject([worker, code]) : resolve(worker)); // }); // return [worker, prom]; // }; // const workerFunc = async () => { // try { // const [sab, msg] = workerData; // const mutex = new Mutex(sab); // await sleep(1000); // mutex.acquire(); // // await sleep(20); // console.log(msg); // mutex.release(); // } catch (error) { // console.error(error); // } // }; // const mainFunc = async () => { // try { // const mutex = new Mutex(); // const lstProm = []; // console.log('Begin creating worker thread...'); // for (let i = 0; i < 200; ++i) { // const [worker, prom] = createThread( // new URL(import.meta.url), // [ // mutex.getSAB(), // 'foo' + i // ]); // lstProm.push(prom); // } // await Promise.all(lstProm); // console.log('Exit app'); // } catch (error) { // console.error(error); // } // }; // if (isMainThread) { // await mainFunc(); // } else { // await workerFunc(); // } ================================================ FILE: js-nodejs/package.json ================================================ { "type": "module", "dependencies": { "@types/node": "^18.7.2", "async-mutex": "^0.3.2", "express": "^4.18.1", "piscina": "^3.2.0" } } ================================================ FILE: notes-articles.md ================================================ # ARTICLES AND LEARNING NOTES ## DESCRIPTION Some articles and learning notes taken from my research.   ---   ## CONTENT ### VOLATILE VS ATOMIC There are two important concepts in multithreading environment: - Atomicity. - Visibility. The volatile keyword eradicates visibility problems, but it does not deal with atomicity. Consider this snippet in a concurrent environment: ```code boolean isStopped = false; while (!isStopped) { // Do some kind of work } ``` The idea here is that some thread could change the value of ```isStopped``` from false to true in order to indicate to the subsequent loop that it is time to stop looping. Intuitively, there is no problem. Logically if another thread makes ```isStopped``` equal to true, then the loop must terminate. The reality is that the loop will likely never terminate even if another thread makes ```isStopped``` equal to true. The reason for this is not intuitive, but consider that modern processors have multiple cores and that each core has multiple registers and multiple levels of cache memory that are **not accessible to other processors**. In other words, values that are cached in one processor's local memory are **not visisble** to threads executing on a different processor. Herein lies one of the central problems with concurrency: visibility. The Java Memory Model makes no guarantees whatsoever about when changes that are made to a variable in one thread may become visible to other threads. In order to guarantee that updates are visisble as soon as they are made, you must synchronize. The ```volatile``` keyword is a weak form of synchronization. While it does nothing for mutual exclusion or atomicity, it does provide a guarantee that changes made to a variable in one thread will become visible to other threads as soon as it is made. Because individual reads and writes to variables that are not 8-bytes are atomic in Java, declaring variables ```volatile``` provides an easy mechanism for providing visibility in situations where there are no other atomicity or mutual exclusion requirements.   Take the following example: ```code Thread A: i = i + 1 Thread B: i = 1000 ``` No matter how you define ```i```, a different thread reading the value just when the above line is executed might get ```i```, or ```i + 1```, because the operation is not atomically. Explanation: - Assume ```i = 0``` - Thread A reads ```i```, calculates ```i + 1```, which is ```1``` - Thread B sets ```i = 1000``` and returns - Thread A now sets ```i``` to the result of the operation, which is ```i = 1``` Atomics like AtomicInteger ensure, that such operations happen atomically. So the above issue cannot happen, ```i``` would either be ```1000``` or ```1001``` once both threads are finished.   Notably, an operation that requires more than one read/write, such as ```i++```, which is equivalent to ```i = i + 1```, which does one read and one write, **is not atomic**, since another thread may write to ```i``` between the read and the write. The ```Atomic``` classes, like ```AtomicInteger``` and ```AtomicReference```, provide a wider variety of operations atomically, specifically including increment for ```AtomicInteger```.   Volatile only ensures that the access is atomically, while Atomics ensure that the operation is atomically.   References: -     ================================================ FILE: notes-demos-exercises.md ================================================ # NOTES OF DEMOS AND EXERCISES ## DESCRIPTION This file contains descriptions/notes of demo and exercise in the repo.   ## TABLE OF CONTENTS I am sorry that generated table of contents contains too many uppercase stuff... - [NOTES OF DEMOS AND EXERCISES](#notes-of-demos-and-exercises) - [DESCRIPTION](#description) - [TABLE OF CONTENTS](#table-of-contents) - [DEMOSTRATIONS](#demostrations) - [DEMO 00 - INTRODUCTION TO MULTITHREADING](#demo-00---introduction-to-multithreading) - [DEMO 01 - HELLO](#demo-01---hello) - [DEMO 02 - THREAD JOINS](#demo-02---thread-joins) - [DEMO 03 - PASSING ARGUMENTS](#demo-03---passing-arguments) - [DEMO 04 - SLEEP](#demo-04---sleep) - [DEMO 05 - GETTING THREAD'S ID](#demo-05---getting-threads-id) - [DEMO 06 - LIST OF MULTIPLE THREADS](#demo-06---list-of-multiple-threads) - [DEMO 07 - FORCING A THREAD TO TERMINATE](#demo-07---forcing-a-thread-to-terminate) - [DEMO 08 - GETTING RETURNED VALUES FROM THREADS](#demo-08---getting-returned-values-from-threads) - [DEMO 09 - THREAD DETACHING](#demo-09---thread-detaching) - [DEMO 10 - THREAD YIELDING](#demo-10---thread-yielding) - [DEMO 11 - EXECUTOR SERVICES AND THREAD POOLS](#demo-11---executor-services-and-thread-pools) - [DEMO 12A - RACE CONDITIONS](#demo-12a---race-conditions) - [DEMO 12B - DATA RACES](#demo-12b---data-races) - [DEMO 12C - RACE CONDITIONS AND DATA RACES](#demo-12c---race-conditions-and-data-races) - [IMPORTANT NOTES](#important-notes) - [DEMO 13 - MUTEXES](#demo-13---mutexes) - [DEMO 14 - SYNCHRONIZED BLOCKS](#demo-14---synchronized-blocks) - [DEMO 15 - DEADLOCK](#demo-15---deadlock) - [Version A](#version-a) - [Version B](#version-b) - [DEMO 16 - MONITORS](#demo-16---monitors) - [DEMO 17 - REENTRANT LOCKS (RECURSIVE MUTEXES)](#demo-17---reentrant-locks-recursive-mutexes) - [DEMO 18 - BARRIERS AND LATCHES](#demo-18---barriers-and-latches) - [DEMO 19 - READ-WRITE LOCKS](#demo-19---read-write-locks) - [DEMO 20A - SEMAPHORES](#demo-20a---semaphores) - [Version A01](#version-a01) - [Version A02](#version-a02) - [Version A03](#version-a03) - [DEMO 20B - SEMAPHORES](#demo-20b---semaphores) - [DEMO 21 - CONDITION VARIABLES](#demo-21---condition-variables) - [DEMO 22 - BLOCKING QUEUES](#demo-22---blocking-queues) - [DEMO 23 - THREAD-LOCAL STORAGE](#demo-23---thread-local-storage) - [DEMO 24 & 25 - THE VOLATILE KEYWORD AND ATOMIC ACCESS](#demo-24--25---the-volatile-keyword-and-atomic-access) - [EXERCISES](#exercises) - [EX01 - MAXIMUM NUMBER OF DIVISORS](#ex01---maximum-number-of-divisors) - [Version A](#version-a-1) - [Version B](#version-b-1) - [Version C](#version-c) - [EX02 - THE PRODUCER-CONSUMER PROBLEM](#ex02---the-producer-consumer-problem) - [EX03 - THE READERS-WRITERS PROBLEM](#ex03---the-readers-writers-problem) - [Problem statement](#problem-statement) - [Problem variations](#problem-variations) - [Second readers-writers problem](#second-readers-writers-problem) - [Third readers-writers problem](#third-readers-writers-problem) - [EX04 - THE DINING PHILOSOPHERS PROBLEM](#ex04---the-dining-philosophers-problem) - [Problem statement](#problem-statement-1) - [Solution](#solution) - [EX05 - MATRIX PRODUCTION](#ex05---matrix-production) - [Version A: Matrix-vector multiplication](#version-a-matrix-vector-multiplication) - [Version B: Matrix-matrix production (dot product)](#version-b-matrix-matrix-production-dot-product) - [EX06 - BLOCKING QUEUE IMPLEMENTATION](#ex06---blocking-queue-implementation) - [EX07 - THE DATA SERVER PROBLEM](#ex07---the-data-server-problem) - [EX08 - EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION](#ex08---executor-service--thread-pool-implementation)   ---   ## DEMOSTRATIONS ### DEMO 00 - INTRODUCTION TO MULTITHREADING Just run the code several times and see results: The results are not the same!!! This is because threads execute concurrently. The operating system shall care the order of thread execution. Depend on current state, the coressponding result varies.   ### DEMO 01 - HELLO You learn how to create a thread. That's all.   ### DEMO 02 - THREAD JOINS When I say: "Thread X waits for thread Y to join". It means thread X shall wait for thread Y to complete, then thread X continues its execution.   ### DEMO 03 - PASSING ARGUMENTS You learn how to pass arguments to a thread: - Passing various data types of variables. - Passing some special types (such as C++ references).   ### DEMO 04 - SLEEP Making a thread sleep for a while. Note that thread sleep is: - Useful, when you want to wait for something to be ready. - Awful, when performance is important, you may waste a lot of resources while thread is asleep.   ### DEMO 05 - GETTING THREAD'S ID Each thread has its own identification. This demo helps you learn how to get the thread's id.   ### DEMO 06 - LIST OF MULTIPLE THREADS Handling a list of multiple threads. Be careful when you pass arguments to threads due to variable reference mechanism. In C/C++ you can forget this warning because variables are usually passed by values. For an example: ```code function doTask(i) { } for (int i = 0; i < 3; ++i) { th = new Thread(doTask(i)); th.start(); } ``` There is only one variable `i` and its reference is passed into thread `th`. In the end, all 3 threads will receive `i = 3` as the parameter. How to solve this problem? Just create new variables. ```code function doTask(i) { } for (int i = 0; i < 3; ++i) { arg = i; th = new Thread(doTask(arg)); th.start(); } ```   ### DEMO 07 - FORCING A THREAD TO TERMINATE Forcing a thread to terminate aka. "killing the thread". Sometimes, we want to force a thread to terminate (for convenient). However, to be careful, the thread should terminate by itself, not by external factors. Assume that a thread is using resource or locking a mutex, and then it is suddenly killed by external factors, so the harmful results are: - Resource may not be disposed/freed. - Mutex is not unlocked, which is strongly possible to leads to the deadlock.   ### DEMO 08 - GETTING RETURNED VALUES FROM THREADS You learn how to return value from a thread, and how to use that value for future tasks. Please note that if you do not use a synchronization mechanism (e.g. thread join, mutex...) then the result may be incorrect. To be clear, let's see this: ```code result = 0; function doTask() { do something for a while; result = 9; } th = new Thread(doTask); th.start(); print(result); ``` We are not sure that result printed is `9`, because at the time `print(result)` executes, `th` does not complete yet (`result = 9` are not executed). So, we need to wait for `th` to complete before printing the result. ```code result = 0; function doTask() { do something for a while; result = 9; } th = new Thread(doTask); th.start(); th.join(); // wait for th to complete to make sure result is set print(result); ```   ### DEMO 09 - THREAD DETACHING When a thread is created, one of its attributes defines whether it is joinable or detached. Only threads that are created as joinable can be joined. If a thread is created as detached, it can never be joined.   ### DEMO 10 - THREAD YIELDING Yield is an action that occurs in a computer program during multithreading, of forcing a processor to relinquish control of the current running thread, and sending it to the end of the running queue, of the same scheduling priority.   ### DEMO 11 - EXECUTOR SERVICES AND THREAD POOLS You learn how to use the executor services (and thread pools) and how they works. From Wikipedia: > A thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. An executor service (or a thread-pool executor) includes: - A thread pool, and - Methods to manage this thread pool: - `submit()`: Push tasks to thread pool - `shutdown()`: Stop/join all threads in pool - ...   ### DEMO 12A - RACE CONDITIONS A race condition or race hazard is the condition of an electronics, software, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events. This program illustrates race condition: Each time you run the program, the results displayed are different.   ### DEMO 12B - DATA RACES Data race specifically refers to the non-synchronized conflicting "memory accesses" (or actions, or operations) to the same memory location. I use a problem statement for illustration: From range [ 1..N ], count numbers of integers which are divisible by 2 or by 3. For an example of N = 8, the integers that match the requirements are 2, 3, 6 and 8. Hence, the result is four numbers. Of course, you can easily solve the problem by a single loop. However, we need to go deeper. There is another solution using "marking array". - Let `a` be array of boolean values. Initialize all elements in `a` by `false`. - For `i` in range `1` to `N`, if `i` is divisible by `2` or by `3`, then assign `a[i] = true`. - Finally, the result is now counting number of `true` in `a`. With `N = 8`, `a[2], a[3], a[4], a[6], a[8]` are marked by `true`.   About the source code, there are two versions: - Version 01 uses traditional single threading to let you get started. - Version 02 uses multithreading. - Thread `markDiv2` will mark `true` for all numbers divisible by 2. - Thread `markDiv3` will mark `true` for all numbers divisible by 3. - **The rule of threading tell us that `a[6]` might be accessed by both threads at the same time ==> DATA RACE**. However, in the end, `a[6] = true` is obvious, so the final result is still correct ==> not race condition.   ### DEMO 12C - RACE CONDITIONS AND DATA RACES Many people are confused about race conditions and data races. - There is a case that race condition appears without data race. That is demo version A. - There is also a case that data race appears without race condition. That is demo version B. *Small note: Looking from a deeper perspective, demo version A still causes data race (that is... output console terminal, hahaha).* Ususally, race condition happens together with data race. A race condition often occurs when two or more threads need to perform operations on the same memory area (data race) but the results of computations depends on the order in which these operations are performed. Concurrent accesses to shared resources can lead to unexpected or erroneous behavior, so parts of the program where the shared resource is accessed need to be protected in ways that avoid the concurrent access. This protected section is the **critical section** or **critical region**.     ### IMPORTANT NOTES   Multithreading makes things run in parallel/concurrency. Therefore we need techniques that handle the control flow to: - make sure the app runs correctly, and - avoid race conditions. There are several techniques, which are divided into two types: synchronization and non-synchronization. | | SYNCHRONIZATION | NON-SYNCHRONIZATION | | ----------- | --------------- | ------------------- | | Description | To block threads until a condition is satisfy | Not to block threads | | Other names | Blocking | Non-blocking, lock-free | | Techniques | - Low-level: Mutex, semaphore, condition variable
- High-level: Synchronized block, blocking queue, barrier and latch | Atomic, thread-local storage | | Pros | - Give you in-depth controls
- Cooperate among threads | - App's performance may be better (compared to synchronization)
- Avoid deadlock | | Cons | - Hard to control in complex synchronization
- May be dangerous (when deadlock appears) | Usually too simple |   Please note that we cannot replace sync techniques by non-sync techniques and vice versa. Each technique has its use cases, strengths and weaknesses. In practical: - We mostly use synchronized blocks, blocking queues, atomic operations and mutexes. - We usually combine sync and non-sync techniques.   `------------------ End of important notes ------------------`     ### DEMO 13 - MUTEXES The mutex is the synchronization primitive used to prevent race conditions. If a resource meets race conditions (i.e. it is access by more than one thread), we create a mutex associating with it: - Lock the mutex before accessing the resource. - Release the mutex after accessing the resource. The code block between (lock-release) mutex action shall be executed by one thread at a time.   ### DEMO 14 - SYNCHRONIZED BLOCKS Synchronized blocks are the blocks of code which prevent executions of multiple threads, that means only one thread can execute a synchronized block at a time although multiple threads are running this block. A synchronized block can be made by using a mutex: - Lock the mutex at the begin of the block. - Unlock the mutex at the end of the block. In some programming languages, synchronized blocks are supported by default, such as Java and C#.   ### DEMO 15 - DEADLOCK #### Version A A simple demo of deadlock: Forgetting to release mutex.   #### Version B There are 2 workers "foo" and "bar". They try to access resource A and B (which are protected by mutResourceA and mutResourceB). Scenario: ```text foo(): synchronized: access resource A synchronized: access resource B bar(): synchronized: access resource B synchronized: access resource A ``` After foo accessing A and bar accessing B, foo and bar might wait other together ==> Deadlock occurs.   ### DEMO 16 - MONITORS Monitor: Concurrent programming meets object-oriented programming. - When concurrent programming became a big deal, object-oriented programming too. - People started to think about ways to make concurrent programming more structured. A monitor is a thread-safe class, object, or module that wraps around a mutex in order to safely allow access to a method or variable by more than one thread.   ### DEMO 17 - REENTRANT LOCKS (RECURSIVE MUTEXES) The reason for using reentrant lock is to avoid a deadlock due to e.g. recursion. A reentrant lock is a synchronization primitive that may be acquired multiple times by the same thread. Internally, it uses the concepts of "owning thread" and "recursion level" in addition to the locked/unlocked state used by primitive locks. In the locked state, some thread owns the lock; in the unlocked state, no thread owns it.   ### DEMO 18 - BARRIERS AND LATCHES In cases where you must wait for a number of tasks to be completed before an overall task can proceed, barrier synchronization can be used. There are two types of barriers: - Cyclic barrier: A general, *reusable barrier*. - Count down latch: There is no possibility to increase or reset the counter, which makes the latch a *single-use barrier*.   ### DEMO 19 - READ-WRITE LOCKS In many situations, data is read more often than it is modified or written. In these cases, you can allow threads to read concurrently while holding the lock and allow only one thread to hold the lock when data is modified. A multiple-reader single-writer lock (or read-write lock) does this. A read-write lock is acquired either for reading or writing, and then is released. The thread that acquires the read-write lock must be the one that releases it.   ### DEMO 20A - SEMAPHORES #### Version A01 In an exam, each candidate is given a couple of 2 scratch papers. Write a program to illustrate this scenario. The program will combine 2 scratch papers into one test package, concurrenly.   #### Version A02 The problem in version 01 is: - When "makeOnePaper" produces too fast, there are a lot of pending papers... This version 02 solves the problem: - Use a semaphore to restrict "makeOnePaper": Only make papers when a package is finished.   #### Version A03 The problem in this version is DEADLOCK, due to a mistake of semaphore synchronization.   ### DEMO 20B - SEMAPHORES A car is manufactured at each stop on a conveyor belt in a car factory. A car is constructed from thefollowing parts: chassis, tires. Thus there are 2 tasks in manufacturing a car. However, 4 tires cannot be added until the chassis is placed on the belt. There are: - 2 production lines (i.e. 2 threads) of making tires. - 1 production line (i.e. 1 thread) of making chassis. Write a program to illustrate this scenario.   ### DEMO 21 - CONDITION VARIABLES Condition variables are synchronization primitives that enable threads to wait until a particular condition occurs. Condition variables are user-mode objects that cannot be shared across processes.   ### DEMO 22 - BLOCKING QUEUES A blocking queue is a queue that blocks when you: - try to dequeue from it and the queue is empty, or... - try to enqueue items to it and the queue is already full. "Synchronous queue" is usually a synonym of "blocking queue". In Java, a synchronous queue has zero capacity (i.e. it does not store any value at all).   ### DEMO 23 - THREAD-LOCAL STORAGE In some cases, shared resources could be used individually for each thread. Every thread has its own copy of the shared resources. Therefore, race condition disappears. ```text BEFORE: X = 8 (X is shared resource) | --------------------- | | | v v v ThreadA ThreadB ThreadC AFTER: --------------------- | | | v v v ThreadA ThreadB ThreadC X = 8 X = 8 X = 8 ``` Thread-local storage helps you to **avoid synchronization**, because synchronization might be dangerous and hard to handle. Application of thread-local storage: - Counter: Each thread does its own counting job. - Security: The random functions often use an initialization seed. When multiple threads calling a random function, results may be the same for some threads. This may lead to security issues. Using synchronization of course solves this problem, but it is too overhead. In this case, using thread-local storage is great. Each thread use an individual random function, which has a different random seed. In the demo code, by using thread-local storage, each thread has its own counter. So, the counter in one thread is completely independent of each other.   ### DEMO 24 & 25 - THE VOLATILE KEYWORD AND ATOMIC ACCESS Please read article "Volatile vs Atomic" in [notes-articles.md](notes-articles.md) for better understanding.   ---   ## EXERCISES ### EX01 - MAXIMUM NUMBER OF DIVISORS Problem statement: Find the integer in the range 1 to 100000 that has the largest number of divisors.   #### Version A The solution without multithreading.   #### Version B This source code file contains the solution using multithreading. There are 2 phases: - Phase 1: - Each worker finds result on a specific range. - This phase uses multiple threads. - Phase 2: - Based on multiple results from workers, main function gets the final result with maximum numDiv. - This phase uses a single thread (i.e. main function).   #### Version C The difference between version C and version B is: - Each worker finds result on a specific range, and then updates final result itself. - So, main function does nothing.   ### EX02 - THE PRODUCER-CONSUMER PROBLEM The producer–consumer problem (also known as the bounded-buffer problem) is a classic example of a multi-process synchronization problem. The first version of which was proposed by Edsger W. Dijkstra in 1965. In the producer-consumer problem, there is one producer that is producing something and there is one consumer that is consuming the products produced by the producer. The producers and consumers share the same memory buffer that is of fixed-size. The job of the producer is to generate the data, put it into the buffer, and again start generating data. While the job of the consumer is to consume the data from the buffer. In the later formulation of the problem, Dijkstra proposed multiple producers and consumers sharing a finite collection of buffers. **What are the problems here?** - The producer and consumer should not access the buffer at the same time. - The producer should produce data only when the buffer is not full. - If the buffer is full, then the producer shouldn't be allowed to put any data into the buffer. - The consumer should consume data only when the buffer is not empty. - If the buffer is empty, then the consumer shouldn't be allowed to take any data from the buffer.   ### EX03 - THE READERS-WRITERS PROBLEM #### Problem statement Consider a situation where we have a file shared between many people. If one of the people tries editing the file, no other person should be reading or writing at the same time, otherwise changes will not be visible to him/her. However if some person is reading the file, then others may read it at the same time. Precisely in Computer Science we call this situation as the readers-writers problem. **What are the problems here?** - One set of data is shared among a number of processes. - Once a writer is ready, it performs its write. Only one writer may write at a time. - If a process is writing, no other process can read it. - If at least one reader is reading, no other process can write.   #### Problem variations ##### Second readers-writers problem The first solution is suboptimal, because it is possible that a reader R1 might have the lock, a writer W be waiting for the lock, and then a reader R2 requests access. It would be unfair for R2 to jump in immediately, ahead of W; if that happened often enough, W would STARVE. Instead, W should start as soon as possible. This is the motivation for the second readers–writers problem, in which the constraint is added that no writer, once added to the queue, shall be kept waiting longer than absolutely necessary. This is also called writers-preference. ##### Third readers-writers problem In fact, the solutions implied by both problem statements can result in starvation - the first one may starve writers in the queue, and the second one may starve readers. Therefore, the third readers–writers problem is sometimes proposed, which adds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time. Solution: - The idea is using a semaphore "serviceQueue" to preserve ordering of requests (signaling must be FIFO).   ### EX04 - THE DINING PHILOSOPHERS PROBLEM #### Problem statement The dining philosophers problem states that there are 5 philosophers sharing a circular table and they eat and think alternatively. There is a bowl of rice for each of the philosophers and 5 chopsticks. A philosopher needs both their right and left chopstick to eat. A hungry philosopher may only eat if there are both chopsticks available. Otherwise a philosopher puts down their chopstick and begin thinking again.   #### Solution A solution of the dining philosophers problem is to use a semaphore to represent a chopstick. A chopstick can be picked up by executing a wait operation on the semaphore and released by executing a signal semaphore. The structure of a random philosopher ```i``` is given as follows: ```pseudocode while true do wait( chopstick[i] ); wait( chopstick[ (i+1) % 5] ); EATING THE RICE signal( chopstick[i] ); signal( chopstick[ (i+1) % 5] ); THINKING ``` **What are the problems here?** - Deadlock. - Starvation. The above solution makes sure that no two neighboring philosophers can eat at the same time. But this solution can lead to a deadlock. This may happen if all the philosophers pick their left chopstick simultaneously. Then none of them can eat and deadlock occurs. Some of the ways to avoid deadlock are as follows: - An even philosopher should pick the right chopstick and then the left chopstick while an odd philosopher should pick the left chopstick and then the right chopstick. - A philosopher should only be allowed to pick their chopstick if both are available at the same time.   ### EX05 - MATRIX PRODUCTION #### Version A: Matrix-vector multiplication For an example: Matrix A: ```text | 1 2 3 | | 4 5 6 | | 7 8 9 | ``` Vector b: ```text | 3 | | -1 | | 0 | ``` The multiplication of A and b is the vector: ```text | 1 | | 7 | | 13 | ``` **Solution:** - Separate matrix A into list of rows. - For each row, calculate scalar product with vector b. - We can process each row individually. Therefore multithreading will get the job done.   #### Version B: Matrix-matrix production (dot product) For an example: Matrix A: ```text | 1 3 5 | | 2 4 6 | ``` Matrix B: ```text | 1 0 1 0 | | 0 1 0 1 | | 1 0 0 -2 | ``` The result of dot(A, B) is the matrix: ```text | 6 3 1 -7 | | 8 4 2 -8 | ```   ### EX06 - BLOCKING QUEUE IMPLEMENTATION Blocking queues strongly relate to the producer-consumer problem: - The `enqueue` method is corresponding to the `produce` action, which creates an object and push it into the rear of the queue. - The `dequeue` method is corresponding to the `consume` action, which removes an object from the front of the queue. There are many methods to implement the producer-consumer problem and this is similar to implementation of the blocking queues.   ### EX07 - THE DATA SERVER PROBLEM The internal data server of a company performs two main tasks for a request: - check_auth_user() - process_files() The pseudo code is: ```pseudo function process_request(): if check_auth_user(), then: list_file_data = process_files() return list_file_data function check_auth_user(): check username, permissions, encryption... return (true or false) function process_files(): read file data from disk do some stuff... write log return file data ```   **The problem:** Usually, two tasks might take a long time. How can we improve the performance? Suppose this server serves internally for employees in company. We can assume that `check_auth_user()` usually returns true, so instead of waiting for a long time of `check_auth_user()`, we can run `process_files()` in parallel. ```text BEFORE: Main thread -------------------> -------------------> check_auth_user() process_files() AFTER: Thread join Synchronize Main thread ------------------->-----------------> check_auth_user() ^ | Child thread ------------------->--------- process_files() ```   **The most important & interesting things** is here: **The first thing:** - Checking for authorization is CPU bound (and maybe network bandwidth bound). - Processing files is disk bound. By running `check_auth_user()` and `process_files()` in parallel, we can increase the performance. **The second thing:** Function `process_files()` not only **reads files** but also **writes logs**. After reading files, we can return file data to user immediately. *The "writting logs" tasks could perform later*. ```text Synchronize ------------------------|----------------------------> Time CPU Check auth user | Do other tasks Disk Read file | Write log User is authorized and files are loaded ``` Let's have a look at the code. I hope you could learn something good.   ### EX08 - EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION To implement an executor service, you need to focus on the worker function (the function that is executed by threads in the pool). For an idle thread: 1. Pick a task (a job) in the queue. 2. Do the task. Step 1 requires synchronization (by a blocking queue, a queue with mutex/synchronized block/condition variable...). It looks simple, but if we need to expand features for our thread pool, things start to get complicated. You may take care of synchronization everywhere: - When a task is dequeued. - When a task is done. - When all tasks are done. - When users want to shutdown thread pool. - ... ================================================ FILE: old/cpppthread-reentrant-lock-a.cpp ================================================ /* REENTRANT LOCK (RECURSIVE MUTEX) The function "getFactorial" will cause deadlock. */ #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int getFactorial(int n) { if (n <= 0) return 1; pthread_mutex_lock(&mut); int result = n * getFactorial(n - 1); pthread_mutex_unlock(&mut); return result; } void* routine(void* arg) { int n = *(int*)arg; int factorial = getFactorial(n); cout << "Factorial of " << n << " is " << factorial << endl; pthread_exit(nullptr); return nullptr; } int main() { int n = 5; pthread_t tid; int ret = 0; ret = pthread_create(&tid, nullptr, routine, &n); ret = pthread_join(tid, nullptr); pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: old/cpppthread-reentrant-lock-b.cpp ================================================ /* REENTRANT LOCK (RECURSIVE MUTEX) */ #include #include using namespace std; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; int getFactorial(int n) { if (n <= 0) return 1; pthread_mutex_lock(&mut); int result = n * getFactorial(n - 1); pthread_mutex_unlock(&mut); return result; } void* routine(void* arg) { int n = *(int*)arg; int factorial = getFactorial(n); cout << "Factorial of " << n << " is " << factorial << endl; pthread_exit(nullptr); return nullptr; } int main() { int n = 5; pthread_t tid; int ret = 0; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mut, &attr); ret = pthread_create(&tid, nullptr, routine, &n); ret = pthread_join(tid, nullptr); pthread_mutexattr_destroy(&attr); pthread_mutex_destroy(&mut); return 0; } ================================================ FILE: old/cppstd-data-race.cpp ================================================ #include #include #include #include #include "mytool-time.hpp" using namespace std; void writeToFile(string fileName, string programName) { ofstream ofs; ofs.open(fileName, ios::app); if (ofs.fail()) { return; } ofs << programName << endl; ofs.close(); } int main(int argc, char* argv[]) { if (argc < 3) { cout << "Please run program with 2 arguments to specify:" << endl; cout << "\tArgument 1: Waiting seconds (positive integer)" << endl; cout << "\tArgument 2: Program name (string)" << endl; return 0; } const string fileName = "tmp-output.txt"; const int waitingSeconds = std::stoi(argv[1]); const string programName = string(argv[2]); cout << "This program name is " << programName << endl; cout << "Please run this program twice to achieve 'data race'" << endl; auto tpFutureWakeUp = mytool::getTimePointFutureFloor( std::chrono::system_clock::now(), waitingSeconds ); cout << "Program will sleep until " << mytool::getTimePointStr(tpFutureWakeUp) << endl; std::this_thread::sleep_until(tpFutureWakeUp); cout << "Writing to the file " << fileName << "..." << endl; writeToFile(fileName, programName); return 0; } ================================================ FILE: old/cppstd-reentrant-lock-a.cpp ================================================ /* REENTRANT LOCK (RECURSIVE MUTEX) The function "getFactorial" will cause deadlock. */ #include #include #include using namespace std; std::mutex mut; int getFactorial(int n) { if (n <= 0) return 1; mut.lock(); int result = n * getFactorial(n - 1); mut.unlock(); return result; } void routine(int n) { int factorial = getFactorial(n); cout << "Factorial of " << n << " is " << factorial << endl; } int main() { int n = 5; auto th = std::thread(routine, n); th.join(); return 0; } ================================================ FILE: old/cppstd-reentrant-lock-b.cpp ================================================ /* REENTRANT LOCK (RECURSIVE MUTEX) */ #include #include #include using namespace std; std::recursive_mutex mut; int getFactorial(int n) { if (n <= 0) return 1; mut.lock(); int result = n * getFactorial(n - 1); mut.unlock(); return result; } void routine(int n) { int factorial = getFactorial(n); cout << "Factorial of " << n << " is " << factorial << endl; } int main() { int n = 5; auto th = std::thread(routine, n); th.join(); return 0; } ================================================ FILE: python/.gitignore ================================================ __pycache__/ ================================================ FILE: python/demo00.py ================================================ ''' INTRODUCTION TO MULTITHREADING You should try running this app several times and see results. ''' import threading def do_task(): for _ in range(300): print('B', end='') th = threading.Thread(target=do_task) th.start() for _ in range(300): print('A', end='') th.join() print() ================================================ FILE: python/demo01_hello.py ================================================ ''' HELLO WORLD VERSION MULTITHREADING ''' import threading def do_task(): print('Hello from example thread') th = threading.Thread(target=do_task) th.start() print('Hello from main thread') ================================================ FILE: python/demo01ex_name.py ================================================ ''' HELLO WORLD VERSION MULTITHREADING Getting thread's name ''' import threading def do_task(): print(f'My name is {threading.current_thread().name}') th_foo = threading.Thread(target=do_task, name='foo') th_bar = threading.Thread(target=do_task, name='bar') th_foo.start() th_bar.start() ================================================ FILE: python/demo02a_join.py ================================================ ''' THREAD JOINS ''' import threading def do_heavy_task(): # Do a heavy task, which takes a little time for _ in range(0, 2 * 10**8): pass print('Done!') th = threading.Thread(target=do_heavy_task) th.start() th.join() print('Good bye!') ================================================ FILE: python/demo02b_join.py ================================================ ''' THREAD JOINS ''' import threading th_foo = threading.Thread(target=lambda:print('foo')) th_bar = threading.Thread(target=lambda:print('bar')) th_foo.start() th_bar.start() # th_foo.join() # th_bar.join() ''' We do not need to call th_foo.join() and th_bar.join(). The reason is main thread will wait for the completion of all threads before app exits. ''' ================================================ FILE: python/demo03a_pass_arg.py ================================================ ''' PASSING ARGUMENTS Version A: Using the Thread's constructor ''' import threading def do_task(a: int, b: float, c: str): print(f'{a} {b} {c}') th_foo = threading.Thread(target=do_task, args=(1, 2, 'red')) th_bar = threading.Thread(target=do_task, args=(3, 4, 'blue')) th_foo.start() th_bar.start() ================================================ FILE: python/demo03b_pass_arg.py ================================================ ''' PASSING ARGUMENTS Version B: Using lambdas ''' import threading def do_task(a: int, b: float, c: str): print(f'{a} {b} {c}') th_foo = threading.Thread(target=lambda:do_task(1, 2, 'red')) th_bar = threading.Thread(target=lambda:do_task(3, 4, 'blue')) th_foo.start() th_bar.start() ================================================ FILE: python/demo04_sleep.py ================================================ ''' SLEEP ''' import time import threading def do_task(): print('foo is sleeping') time.sleep(3) print('foo wakes up') th_foo = threading.Thread(target=do_task) th_foo.start() th_foo.join() print('Good bye') ================================================ FILE: python/demo05_id.py ================================================ ''' GETTING THREAD'S ID ''' import time import threading def do_task(): time.sleep(1) tid = threading.get_ident() tid_native = threading.get_native_id() print('id of current thread:', tid) print('native id of current thread from operating system:', tid_native) th_foo = threading.Thread(target=do_task) th_bar = threading.Thread(target=do_task) th_foo.start() th_bar.start() print("foo's id:", th_foo.ident) print("foo's native id:", th_foo.native_id) print("bar's id:", th_bar.ident) print("bar's native id:", th_bar.native_id) ================================================ FILE: python/demo06_list_threads.py ================================================ ''' LIST OF MULTIPLE THREADS ''' import time import threading def do_task(index): time.sleep(0.5) print(index, end='') NUM_THREADS = 5 lstth = [] for i in range(NUM_THREADS): lstth.append(threading.Thread( target=do_task, args=(i,) )) for th in lstth: th.start() ================================================ FILE: python/demo07_terminate.py ================================================ ''' FORCING A THREAD TO TERMINATE (i.e. killing the thread) Using a flag to notify the thread ''' import time import threading flag_stop = False def do_task(): while True: if flag_stop: break print('Running...') time.sleep(1) th = threading.Thread(target=do_task) th.start() time.sleep(3) flag_stop = True ================================================ FILE: python/demo08a_return_value.py ================================================ ''' GETTING RETURNED VALUES FROM THREADS ''' import threading def double_value(value): return value * 2 res = {} th_foo = threading.Thread( target=lambda: res.update({ 'foo': double_value(5) }) ) th_bar = threading.Thread( target=lambda: res.update({ 'bar': double_value(80) }) ) th_foo.start() th_bar.start() # Wait until th_foo and th_far finish th_foo.join() th_bar.join() print(res['foo']) print(res['bar']) ================================================ FILE: python/demo08b_return_value.py ================================================ ''' GETTING RETURNED VALUES FROM THREADS ''' import threading def double_value(result: dict, name: str, value): result[name] = value * 2 res = {} th_foo = threading.Thread( target=double_value, args=(res, 'foo', 5) ) th_bar = threading.Thread( target=double_value, args=(res, 'bar', 80) ) th_foo.start() th_bar.start() # Wait until th_foo and th_far finish th_foo.join() th_bar.join() print(res['foo']) print(res['bar']) ================================================ FILE: python/demo09_detach.py ================================================ ''' THREAD DETACHING ''' import time import threading def do_task(): print('foo is starting...') time.sleep(2) print('foo is exiting...') th_foo = threading.Thread(target=do_task, daemon=True) th_foo.start() # If I comment this statement, # th_foo will be forced into terminating with main thread time.sleep(3) print('Main thread is exiting') ================================================ FILE: python/demo10_yield.py ================================================ ''' THREAD YIELDING I think that thread yielding does not make sense in Python. No demo available. ''' ================================================ FILE: python/demo11a_exec_service.py ================================================ ''' EXECUTOR SERVICES AND THREAD POOLS Version A: The executor service (of which thread pool) containing a single thread ''' from concurrent.futures import ThreadPoolExecutor def do_task(): print('Hello the Executor Service') executor = ThreadPoolExecutor(max_workers=1) executor.submit(lambda: print('Hello World')) executor.submit(do_task) executor.shutdown(wait=True) ================================================ FILE: python/demo11b_exec_service.py ================================================ ''' EXECUTOR SERVICES AND THREAD POOLS Version B: The executor service containing multiple threads ''' import time from concurrent.futures import ThreadPoolExecutor def do_task(name: str): print(f'Task {name} is starting') time.sleep(3) print(f'Task {name} is completed') NUM_TASKS = 5 executor = ThreadPoolExecutor(max_workers=2) for i in range(NUM_TASKS): task_name = chr(i + 65) executor.submit(do_task, task_name) executor.shutdown(wait=True) ================================================ FILE: python/demo11c01_exec_service.py ================================================ ''' EXECUTOR SERVICES AND THREAD POOLS Version C01: The executor service and Future objects ''' from concurrent.futures import ThreadPoolExecutor def get_squared(x): return x * x executor = ThreadPoolExecutor(max_workers=1) future = executor.submit(get_squared, 7) # print(future.done()) print(future.result()) executor.shutdown(wait=True) ================================================ FILE: python/demo11c02_exec_service.py ================================================ ''' EXECUTOR SERVICES AND THREAD POOLS Version C02: The executor service and Future objects ''' import time from concurrent.futures import ThreadPoolExecutor def get_squared(x): time.sleep(3) return x * x executor = ThreadPoolExecutor(max_workers=1) future = executor.submit(get_squared, 7) print('Calculating...') print(future.result()) executor.shutdown(wait=True) ================================================ FILE: python/demo12a_race_condition.py ================================================ ''' RACE CONDITIONS ''' import time import threading def do_task(index: int): time.sleep(1) print(index, end='') NUM_THREADS = 4 lstth = [] for i in range(NUM_THREADS): lstth.append(threading.Thread( target=do_task, args=(i,) )) for th in lstth: th.start() ================================================ FILE: python/demo12b01_data_race_single.py ================================================ ''' DATA RACES Version 01: Without multithreading ''' def get_result(n: int): a = [False] * (n + 1) for i in range(1, n + 1): if i % 2 == 0 or i % 3 == 0: a[i] = True res = a.count(True) return res N = 8 result = get_result(N) print('Number of integers that are divisible by 2 or 3 is:', result) ================================================ FILE: python/demo12b02_data_race_multi.py ================================================ ''' DATA RACES Version 02: Multithreading ''' import threading def count_div_2(a: list, n: int): for i in range(2, n + 1, 2): a[i] = True def count_div_3(a: list, n: int): for i in range(3, n + 1, 3): a[i] = True N = 8 A = [False] * (N + 1) th_div_2 = threading.Thread(target=count_div_2, args=(A, N)) th_div_3 = threading.Thread(target=count_div_3, args=(A, N)) th_div_2.start() th_div_3.start() th_div_2.join() th_div_3.join() result = A.count(True) print('Number of integers that are divisible by 2 or 3 is:', result) ================================================ FILE: python/demo12c01_race_cond_data_race.py ================================================ ''' RACE CONDITIONS AND DATA RACES ''' import time import threading counter = 0 def do_task(): global counter for _ in range(1000): temp = counter + 1 time.sleep(0.0001) counter = temp lstth = [threading.Thread(target=do_task) for _ in range(32)] for th in lstth: th.start() for th in lstth: th.join() print('counter =', counter) # We are NOT sure that counter = 32000 ================================================ FILE: python/demo12c02_race_cond_data_race.py ================================================ ''' RACE CONDITIONS AND DATA RACES ''' import time import threading counter = 0 def do_task_a(): global counter time.sleep(1) while counter < 10: counter += 1 print('A won !!!') def do_task_b(): global counter time.sleep(1) while counter > -10: counter -= 1 print('B won !!!') threading.Thread(target=do_task_a).start() threading.Thread(target=do_task_b).start() ================================================ FILE: python/demo13a_mutex.py ================================================ ''' MUTEXES In Python, Lock objects can be used as mutexes ''' import time import threading mutex = threading.Lock() counter = 0 def do_task(): global counter mutex.acquire() for _ in range(1000): temp = counter + 1 time.sleep(0.0001) counter = temp mutex.release() NUM_THREADS = 32 lstth = [threading.Thread(target=do_task) for _ in range(NUM_THREADS)] for th in lstth: th.start() for th in lstth: th.join() print('counter =', counter) # We are sure that counter = 32000 ================================================ FILE: python/demo14_synchronized_block.py ================================================ ''' SYNCHRONIZED BLOCKS ''' import time import threading mutex = threading.Lock() counter = 0 def do_task(): global counter with mutex: for _ in range(1000): temp = counter + 1 time.sleep(0.0001) counter = temp NUM_THREADS = 32 lstth = [threading.Thread(target=do_task) for _ in range(NUM_THREADS)] for th in lstth: th.start() for th in lstth: th.join() print('counter =', counter) # We are sure that counter = 32000 ================================================ FILE: python/demo15a_deadlock.py ================================================ ''' DEADLOCK Version A ''' import threading mutex = threading.Lock() def do_task(name: str): mutex.acquire() print(f'{name} acquired resource') # mutex.release() # Forget this statement ==> deadlock th_foo = threading.Thread(target=do_task, args=('foo',)) th_bar = threading.Thread(target=do_task, args=('bar',)) th_foo.start() th_bar.start() th_foo.join() th_bar.join() print('You will never see this statement due to deadlock!') ================================================ FILE: python/demo15b_deadlock.py ================================================ ''' DEADLOCK Version B ''' import time import threading mutex_a = threading.Lock() mutex_b = threading.Lock() def foo(): with mutex_a: print('foo acquired resource A') time.sleep(1) with mutex_b: print('foo acquired resource B') def bar(): with mutex_b: print('bar acquired resource B') time.sleep(1) with mutex_a: print('bar acquired resource A') th_foo = threading.Thread(target=foo) th_bar = threading.Thread(target=bar) th_foo.start() th_bar.start() th_foo.join() th_bar.join() print('You will never see this statement due to deadlock!') ================================================ FILE: python/demo16_monitor.py ================================================ ''' MONITORS Implementation of a monitor for managing a counter ''' import time import threading class Monitor: def __init__(self, res: dict, field_name: str): self.__lock = threading.Lock() self.__res = res self.__field_name = field_name def increase_counter(self): with self.__lock: tmp = self.__res[self.__field_name] + 1 time.sleep(0.0001) self.__res[self.__field_name] = tmp def do_task(mon: Monitor): for _ in range(1000): mon.increase_counter() result = { 'data': 0 } monitor = Monitor(result, 'data') NUM_THREADS = 32 lstth = [threading.Thread(target=do_task, args=(monitor,)) for _ in range(NUM_THREADS)] for th in lstth: th.start() for th in lstth: th.join() print('counter =', result['data']) # We are sure that counter = 32000 ================================================ FILE: python/demo17a_reentrant_lock.py ================================================ ''' REENTRANT LOCKS (RECURSIVE MUTEXES) Version A: Introduction to reentrant locks ''' import threading lock = threading.Lock() def do_task(): with lock: print('First time acquiring the resource') with lock: print('Second time acquiring the resource') th = threading.Thread(target=do_task) th.start() # The thread th shall meet deadlock. # So, you will never get output "Second time the acquiring resource". ================================================ FILE: python/demo17b_reentrant_lock.py ================================================ ''' REENTRANT LOCKS (RECURSIVE MUTEXES) Version B: Solving the problem from version A ''' import threading lock = threading.RLock() def do_task(): with lock: print('First time acquiring the resource') with lock: print('Second time acquiring the resource') th = threading.Thread(target=do_task) th.start() th.join() ================================================ FILE: python/demo17c_reentrant_lock.py ================================================ ''' REENTRANT LOCKS (RECURSIVE MUTEXES) Version C: A multithreaded app example ''' import time import threading lock = threading.RLock() def do_task(name: str): time.sleep(1) with lock: print(f'First time {name} acquiring the resource') with lock: print(f'Second time {name} acquiring the resource') NUM_THREADS = 3 for i in range(NUM_THREADS): threading.Thread(target=do_task, args=(chr(i + 65),)).start() ================================================ FILE: python/demo18a01_barrier.py ================================================ ''' BARRIERS AND LATCHES Version A: Barriers ''' import time import threading sync_point = threading.Barrier(parties=3) def process_request(user_name: str, wait_time: int): time.sleep(wait_time) print(f'Get request from {user_name}') sync_point.wait() print(f'Process request for {user_name}') sync_point.wait() print(f'Done {user_name}') lstarg = [ ('lorem', 1), ('ipsum', 2), ('dolor', 3) ] _ = [ threading.Thread(target=process_request, args=arg).start() for arg in lstarg ] ================================================ FILE: python/demo18a02_barrier.py ================================================ ''' BARRIERS AND LATCHES Version A: Barriers ''' import time import threading sync_point = threading.Barrier(parties=2) def process_request(user_name: str, wait_time: int): time.sleep(wait_time) print(f'Get request from {user_name}') sync_point.wait() print(f'Process request for {user_name}') sync_point.wait() print(f'Done {user_name}') lstarg = [ ('lorem', 1), ('ipsum', 3), ('dolor', 3), ('amet', 10) ] _ = [ threading.Thread(target=process_request, args=arg) for arg in lstarg ] # Thread with user_name = "amet" shall be FREEZED ================================================ FILE: python/demo18a03_barrier.py ================================================ ''' BARRIERS AND LATCHES Version A: Barriers ''' import time import threading sync_point_a = threading.Barrier(parties=2) sync_point_b = threading.Barrier(parties=2) def process_request(user_name: str, wait_time: int): time.sleep(wait_time) print(f'Get request from {user_name}') sync_point_a.wait() print(f'Process request for {user_name}') sync_point_b.wait() print(f'Done {user_name}') lstarg = [ ('lorem', 1), ('ipsum', 3), ('dolor', 3), ('amet', 10) ] _ = [ threading.Thread(target=process_request, args=arg) for arg in lstarg ] ================================================ FILE: python/demo18b01_latch.py ================================================ ''' BARRIERS AND LATCHES Version B: Count-down latches Count-down latches in Python are not supported by default. So, I use mylib_latch for this demonstration. ''' import time import threading from mylib_latch import CountDownLatch def process_request(user_name: str, wait_time: int): time.sleep(wait_time) print(f'Get request from {user_name}') sync_point.count_down() sync_point.wait() print(f'Done {user_name}') lstarg = [ ('lorem', 1), ('ipsum', 2), ('dolor', 3) ] sync_point = CountDownLatch(count=3) _ = [ threading.Thread(target=process_request, args=arg).start() for arg in lstarg ] ================================================ FILE: python/demo18b02_latch.py ================================================ ''' BARRIERS AND LATCHES Version B: Count-down latches Main thread waits for 3 child threads to get enough data to progress. Count-down latches in Python are not supported by default. So, I use mylib_latch for this demonstration. ''' import time import threading from mylib_latch import CountDownLatch def do_task(message: str, wait_time: int): time.sleep(wait_time) print(message) sync_point.count_down() time.sleep(8) print('Cleanup') lstarg = [ ('Send request to egg.net to get data', 6), ('Send request to foo.org to get data', 2), ('Send request to bar.com to get data', 4) ] sync_point = CountDownLatch(count=len(lstarg)) _ = [ threading.Thread(target=do_task, args=arg).start() for arg in lstarg ] sync_point.wait() print('\nNow we have enough data to progress to next step\n') ================================================ FILE: python/demo19_read_write_lock.py ================================================ ''' READ-WRITE LOCKS Read-write locks in Python are not supported by default. So, I use mylib_rwlock for this demonstration. ''' import time import random import threading from mylib_rwlock import ReadWriteLock rwlock = ReadWriteLock() resource = 0 def read_func(wait_time: int): time.sleep(wait_time) with rwlock.readlock(): print(f'read: {resource}') def write_func(wait_time: int): global resource time.sleep(wait_time) with rwlock.writelock(): resource = random.randint(0, 99) print(f'write: {resource}') NUM_THREADS_READ = 10 NUM_THREADS_WRITE = 4 TIME_WAIT_MAX = 2 lstth_read = [ threading.Thread(target=read_func, args=(random.randint(0, TIME_WAIT_MAX),)) for _ in range(NUM_THREADS_READ) ] lstth_write = [ threading.Thread(target=write_func, args=(random.randint(0, TIME_WAIT_MAX),)) for _ in range(NUM_THREADS_WRITE) ] for th in lstth_read: th.start() for th in lstth_write: th.start() ================================================ FILE: python/demo20a01_semaphore.py ================================================ ''' SEMAPHORES Version A: Paper sheets and packages ''' import time import threading sem_package = threading.Semaphore(0) def make_one_sheet(): for _ in range(4): print('Make 1 sheet') time.sleep(1) sem_package.release() def combine_one_package(): for _ in range(4): sem_package.acquire() sem_package.acquire() print('Combine 2 sheets into 1 package') threading.Thread(target=make_one_sheet).start() threading.Thread(target=make_one_sheet).start() threading.Thread(target=combine_one_package).start() ================================================ FILE: python/demo20a02_semaphore.py ================================================ ''' SEMAPHORES Version A: Paper sheets and packages ''' import time import threading sem_package = threading.Semaphore(0) sem_sheet = threading.Semaphore(2) def make_one_sheet(): for _ in range(4): sem_sheet.acquire() print('Make 1 sheet') sem_package.release() def combine_one_package(): for _ in range(4): sem_package.acquire() sem_package.acquire() print('Combine 2 sheets into 1 package') time.sleep(2) sem_sheet.release() sem_sheet.release() threading.Thread(target=make_one_sheet).start() threading.Thread(target=make_one_sheet).start() threading.Thread(target=combine_one_package).start() ================================================ FILE: python/demo20a03_semaphore_deadlock.py ================================================ ''' SEMAPHORES Version A: Paper sheets and packages ''' import time import threading sem_package = threading.Semaphore(0) sem_sheet = threading.Semaphore(2) def make_one_sheet(): for _ in range(4): sem_sheet.acquire() print('Make 1 sheet') sem_package.release() def combine_one_package(): for _ in range(4): sem_package.acquire() sem_package.acquire() print('Combine 2 sheets into 1 package') time.sleep(2) sem_sheet.release() # sem_sheet.release() # Missing one statement: sem_sheet.release() ==> deadlock threading.Thread(target=make_one_sheet).start() threading.Thread(target=make_one_sheet).start() threading.Thread(target=combine_one_package).start() ================================================ FILE: python/demo20b_semaphore.py ================================================ ''' SEMAPHORES Version B: Tires and chassis ''' import time import threading sem_tire = threading.Semaphore(4) sem_chassis = threading.Semaphore(0) def make_tire(): for _ in range(8): sem_tire.acquire() print('Make 1 tire') time.sleep(1) sem_chassis.release() def make_chassis(): for _ in range(4): sem_chassis.acquire() sem_chassis.acquire() sem_chassis.acquire() sem_chassis.acquire() print('Make 1 chassis') time.sleep(3) sem_tire.release() sem_tire.release() sem_tire.release() sem_tire.release() threading.Thread(target=make_tire).start() threading.Thread(target=make_tire).start() threading.Thread(target=make_chassis).start() ================================================ FILE: python/demo21a01_condition_variable.py ================================================ ''' CONDITION VARIABLES ''' import time import threading condition_var = threading.Condition() def foo(): print('foo is waiting...') with condition_var: condition_var.wait() print('foo resumed') def bar(): time.sleep(3) with condition_var: condition_var.notify() threading.Thread(target=foo).start() threading.Thread(target=bar).start() ================================================ FILE: python/demo21a02_condition_variable.py ================================================ ''' CONDITION VARIABLES ''' import time import threading condition_var = threading.Condition() def foo(): print('foo is waiting...') with condition_var: condition_var.wait() print('foo resumed') def bar(): for _ in range(3): time.sleep(2) with condition_var: condition_var.notify() _ = [ threading.Thread(target=foo).start() for _ in range(3) ] threading.Thread(target=bar).start() ================================================ FILE: python/demo21a03_condition_variable.py ================================================ ''' CONDITION VARIABLES ''' import time import threading condition_var = threading.Condition() def foo(): print('foo is waiting...') with condition_var: condition_var.wait() print('foo resumed') def bar(): time.sleep(3) with condition_var: # Notify all waiting threads condition_var.notify_all() _ = [ threading.Thread(target=foo).start() for _ in range(3) ] threading.Thread(target=bar).start() ================================================ FILE: python/demo21b_condition_variable.py ================================================ ''' CONDITION VARIABLES ''' import threading condition_var = threading.Condition() counter = 0 COUNT_HALT_01 = 3 COUNT_HALT_02 = 6 COUNT_DONE = 10 def foo(): ''' Write numbers 1-3 and 8-10 as permitted by egg() ''' global counter while True: with condition_var: condition_var.wait() counter += 1 print(f'foo counter = {counter}') if counter >= COUNT_DONE: return def bar(): ''' Write numbers 4-7 ''' global counter while True: with condition_var: if counter < COUNT_HALT_01 or counter > COUNT_HALT_02: # Signal to free waiting thread by freeing the mutex # Note: foo() is now permitted to modify "counter" condition_var.notify() else: counter += 1 print(f'egg counter = {counter}') if counter >= COUNT_DONE: return threading.Thread(target=foo).start() threading.Thread(target=bar).start() ================================================ FILE: python/demo22a_blocking_queue.py ================================================ ''' BLOCKING QUEUES Version A: A slow producer and a fast consumer ''' import time from queue import Queue import threading def producer(q: Queue): time.sleep(2) q.put('Alice') time.sleep(2) q.put('likes') time.sleep(2) q.put('singing') def consumer(q: Queue): for _ in range(3): print('\nWaiting for data...') data = q.get() print(f' {data}') blkq = Queue() threading.Thread(target=producer, args=(blkq,)).start() threading.Thread(target=consumer, args=(blkq,)).start() ================================================ FILE: python/demo22b_blocking_queue.py ================================================ ''' BLOCKING QUEUES Version B: A fast producer and a slow consumer ''' import time from queue import Queue import threading def producer(q: Queue): q.put('Alice') q.put('likes') # Due to reaching the maximum capacity = 2, when executing q.put('singing'), # this thread is going to sleep until the queue removes an element. q.put('singing') def consumer(q: Queue): time.sleep(2) for _ in range(3): print('\nWaiting for data...') data = q.get() print(f' {data}') blkq = Queue(maxsize=2) # blocking queue with capacity = 2 threading.Thread(target=producer, args=(blkq,)).start() threading.Thread(target=consumer, args=(blkq,)).start() ================================================ FILE: python/demo23a_thread_local.py ================================================ ''' THREAD-LOCAL STORAGE ''' import time import threading data = threading.local() def print_local_value(): print(data.value) def do_task_apple(): data.value = 'APPLE' time.sleep(2) print_local_value() def do_task_banana(): data.value = 'BANANA' time.sleep(2) print_local_value() threading.Thread(target=do_task_apple).start() threading.Thread(target=do_task_banana).start() ================================================ FILE: python/demo23b_thread_local.py ================================================ ''' THREAD-LOCAL STORAGE Avoiding synchronization using thread-local storage ''' import time import threading data = threading.local() def do_task(t: int): time.sleep(1) data.counter = 0 for _ in range(1000): data.counter += 1 print(f'Thread {t} gives counter = {data.counter}') NUM_THREADS = 3 for i in range(NUM_THREADS): threading.Thread(target=do_task, args=(i,)).start() # By using thread-local storage, each thread has its own counter. # So, the counter in one thread is completely independent of each other. # Thread-local storage helps us to AVOID SYNCHRONIZATION. ================================================ FILE: python/demo24_volatile.py ================================================ ''' THE VOLATILE KEYWORD The "volatile" keyword in Python are not supported by default. ''' ================================================ FILE: python/demo25_atomic.py ================================================ ''' ATOMIC ACCESS The atomic operation syntax in Python is not supported by default. ''' ================================================ FILE: python/demoex_event.py ================================================ ''' EVENT OBJECTS ''' import time import threading # Event to notify the speakers ev = threading.Event() running = True def func_speaker(name: str): while True: ev.wait() if not running: return print(f'{name}: ring ring ring') def func_clock(): global running for i in range(89, -1, -1): minute = i // 60 second = i % 60 print(f'{minute:02d}:{second:02d}') if i % 30 == 0: ev.set() # let speakers do speak 'ring ring ring' ev.clear() # reset internal flag to reuse the event in the future time.sleep(0.2) running = False ev.set() threading.Thread(target=func_speaker, args=('ham',)).start() threading.Thread(target=func_speaker, args=('egg',)).start() threading.Thread(target=func_clock).start() ================================================ FILE: python/demoex_timer.py ================================================ ''' TIMER OBJECTS ''' import time import threading def func_time_out(): print('Time out!!!') threading.Timer(10, func_time_out).start() for i in range(9, -1, -1): print(i) time.sleep(1) ================================================ FILE: python/exer01a_max_div.py ================================================ ''' MAXIMUM NUMBER OF DIVISORS ''' import time RANGE_START = 1 RANGE_END = 30000 res_value = 0 res_numdiv = 0 # number of divisors of result tp_start = time.time() for i in range(RANGE_START, RANGE_END + 1): numdiv = 0 for j in range(1, i // 2): if i % j == 0: numdiv += 1 if res_numdiv < numdiv: res_numdiv = numdiv res_value = i time_elapsed = time.time() - tp_start print('The integer which has largest number of divisors is', res_value) print('The largest number of divisor is', res_numdiv) print('Time elapsed =', time_elapsed) ================================================ FILE: python/exer01b_max_div.py ================================================ ''' MAXIMUM NUMBER OF DIVISORS ''' import time import threading lk = threading.Lock() def prepare_arg(rng_start: int, rng_end: int, num_threads: int) -> list[dict]: rng_block = (rng_end - rng_start + 1) // num_threads rng_a = rng_start lst_arg = [] for i in range(num_threads): rng_b = rng_a + rng_block - 1 if i < num_threads - 1 else rng_end lst_arg.append({ 'start': rng_a, 'end': rng_b }) rng_a += rng_block return lst_arg def do_task(arg: dict, lst_res: list[dict]): res_value = 0 res_numdiv = 0 for i in range(arg['start'], arg['end'] + 1): numdiv = 0 for j in range(1, i // 2): if i % j == 0: numdiv += 1 if res_numdiv < numdiv: res_numdiv = numdiv res_value = i with lk: lst_res.append({ 'value': res_value, 'numdiv': res_numdiv }) ''' BETTER WAY (avoiding synchronization of lst_res): - Initialize lst_res with null objects. Of course, the number of objects is NUM_THREADS. - In thread function: lst_res[thread_index] = { 'value': res_value, 'numdiv': res_numdiv } ''' ########################################################## RANGE_START = 1 RANGE_END = 30000 NUM_THREADS = 8 lst_worker_arg = prepare_arg(RANGE_START, RANGE_END, NUM_THREADS) lst_worker_res = [] lstth = [ threading.Thread( target=do_task, args=(arg, lst_worker_res) ) for arg in lst_worker_arg ] tp_start = time.time() for th in lstth: th.start() for th in lstth: th.join() final_res = sorted(lst_worker_res, key=lambda res: res['numdiv'])[-1] time_elapsed = time.time() - tp_start print('The integer which has largest number of divisors is', final_res['value']) print('The largest number of divisor is', final_res['numdiv']) print('Time elapsed =', time_elapsed) ================================================ FILE: python/exer01c_max_div.py ================================================ ''' MAXIMUM NUMBER OF DIVISORS ''' import time import threading lk = threading.Lock() final_res = { 'value': 0, 'numdiv': 0 } def prepare_arg(rng_start: int, rng_end: int, num_threads: int) -> list[dict]: rng_block = (rng_end - rng_start + 1) // num_threads rng_a = rng_start lst_arg = [] for i in range(num_threads): rng_b = rng_a + rng_block - 1 if i < num_threads - 1 else rng_end lst_arg.append({ 'start': rng_a, 'end': rng_b }) rng_a += rng_block return lst_arg def do_task(arg: dict): res_value = 0 res_numdiv = 0 for i in range(arg['start'], arg['end'] + 1): numdiv = 0 for j in range(1, i // 2): if i % j == 0: numdiv += 1 if res_numdiv < numdiv: res_numdiv = numdiv res_value = i with lk: if final_res['numdiv'] < res_numdiv: final_res['numdiv'] = res_numdiv final_res['value'] = res_value ########################################################## RANGE_START = 1 RANGE_END = 30000 NUM_THREADS = 8 lst_worker_arg = prepare_arg(RANGE_START, RANGE_END, NUM_THREADS) lstth = [ threading.Thread( target=do_task, args=(arg,) ) for arg in lst_worker_arg ] tp_start = time.time() for th in lstth: th.start() for th in lstth: th.join() time_elapsed = time.time() - tp_start print('The integer which has largest number of divisors is', final_res['value']) print('The largest number of divisor is', final_res['numdiv']) print('Time elapsed =', time_elapsed) ================================================ FILE: python/exer02a01_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A01: 1 slow producer, 1 fast consumer ''' import time from queue import Queue import threading def producer(q: Queue): i = 1 while True: q.put(i) time.sleep(1) i += 1 def consumer(q: Queue): while True: data = q.get() print('Consumer', data) blkq = Queue() threading.Thread(target=producer, args=(blkq,)).start() threading.Thread(target=consumer, args=(blkq,)).start() ================================================ FILE: python/exer02a02_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A02: 2 slow producers, 1 fast consumer ''' import time from queue import Queue import threading def producer(q: Queue): i = 1 while True: q.put(i) time.sleep(1) i += 1 def consumer(q: Queue): while True: data = q.get() print('Consumer', data) blkq = Queue() threading.Thread(target=producer, args=(blkq,)).start() threading.Thread(target=producer, args=(blkq,)).start() threading.Thread(target=consumer, args=(blkq,)).start() ================================================ FILE: python/exer02a03_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A03: 1 slow producer, 2 fast consumers ''' import time from queue import Queue import threading def producer(q: Queue): i = 1 while True: q.put(i) time.sleep(1) i += 1 def consumer(name: str, q: Queue): while True: data = q.get() print(f'Consumer {name}: {data}') blkq = Queue() threading.Thread(target=producer, args=(blkq,)).start() threading.Thread(target=consumer, args=('foo', blkq)).start() threading.Thread(target=consumer, args=('bar', blkq)).start() ================================================ FILE: python/exer02a04_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE A: USING BLOCKING QUEUES Version A04: Multiple fast producers, multiple slow consumers ''' import time from queue import Queue import threading def producer(q: Queue, start_value: int): time.sleep(1) i = 1 while True: q.put(i + start_value) i += 1 def consumer(q: Queue): while True: data = q.get() print('Consumer', data) time.sleep(1) blkq = Queue(maxsize=5) NUM_PRODUCERS = 3 NUM_CONSUMERS = 2 for i in range(NUM_PRODUCERS): threading.Thread(target=producer, args=(blkq, i * 1000)).start() for _ in range(NUM_CONSUMERS): threading.Thread(target=consumer, args=(blkq,)).start() ================================================ FILE: python/exer02b01_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B01: 1 slow producer, 1 fast consumer ''' import time import threading def producer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list ): i = 1 while True: sem_empty.acquire() q.append(i) time.sleep(1) sem_fill.release() i += 1 def consumer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list ): while True: sem_fill.acquire() data = q.pop(0) print('Consumer', data) sem_empty.release() s_fill = threading.Semaphore(0) # item produced s_empty = threading.Semaphore(1) # remaining space in queue que = [] threading.Thread(target=producer, args=(s_fill, s_empty, que)).start() threading.Thread(target=consumer, args=(s_fill, s_empty, que)).start() ================================================ FILE: python/exer02b02_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B02: 2 slow producers, 1 fast consumer ''' import time import threading def producer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list, start_value: int ): i = 1 while True: sem_empty.acquire() q.append(i + start_value) time.sleep(1) sem_fill.release() i += 1 def consumer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list ): while True: sem_fill.acquire() data = q.pop(0) print('Consumer', data) sem_empty.release() s_fill = threading.Semaphore(0) # item produced s_empty = threading.Semaphore(1) # remaining space in queue que = [] threading.Thread(target=producer, args=(s_fill, s_empty, que, 0)).start() threading.Thread(target=producer, args=(s_fill, s_empty, que, 1000)).start() threading.Thread(target=consumer, args=(s_fill, s_empty, que)).start() ================================================ FILE: python/exer02b03_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B03: 2 fast producers, 1 slow consumer ''' import time import threading def producer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list, start_value: int ): i = 1 while True: sem_empty.acquire() q.append(i + start_value) sem_fill.release() i += 1 def consumer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list ): while True: sem_fill.acquire() data = q.pop(0) print('Consumer', data) time.sleep(1) sem_empty.release() s_fill = threading.Semaphore(0) # item produced s_empty = threading.Semaphore(1) # remaining space in queue que = [] threading.Thread(target=producer, args=(s_fill, s_empty, que, 0)).start() threading.Thread(target=producer, args=(s_fill, s_empty, que, 1000)).start() threading.Thread(target=consumer, args=(s_fill, s_empty, que)).start() ================================================ FILE: python/exer02b04_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE B: USING SEMAPHORES Version B04: Multiple fast producers, multiple slow consumers ''' import time import threading def producer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list, start_value: int ): time.sleep(1) i = 1 while True: sem_empty.acquire() q.append(i + start_value) sem_fill.release() i += 1 def consumer( sem_fill: threading.Semaphore, sem_empty: threading.Semaphore, q: list ): while True: sem_fill.acquire() data = q.pop(0) print('Consumer', data) time.sleep(1) sem_empty.release() s_fill = threading.Semaphore(0) # item produced s_empty = threading.Semaphore(1) # remaining space in queue que = [] NUM_PRODUCERS = 3 NUM_CONSUMERS = 2 for i in range(NUM_PRODUCERS): threading.Thread(target=producer, args=(s_fill, s_empty, que, i * 1000)).start() for _ in range(NUM_CONSUMERS): threading.Thread(target=consumer, args=(s_fill, s_empty, que)).start() ================================================ FILE: python/exer02c_producer_consumer.py ================================================ ''' THE PRODUCER-CONSUMER PROBLEM SOLUTION TYPE C: USING CONDITION VARIABLES & MONITORS Multiple fast producers, multiple slow consumers ''' import time import threading class Monitor: def __init__(self, max_queue_size: int, q: list): self.__q = q self.__max_queue_size = max_queue_size self.__lk = threading.Lock() self.__cond_full = threading.Condition(self.__lk) self.__cond_empty = threading.Condition(self.__lk) def add(self, item): with self.__lk: while len(self.__q) == self.__max_queue_size: self.__cond_full.wait() self.__q.append(item) if len(self.__q) == 1: self.__cond_empty.notify() def remove(self): with self.__lk: while len(self.__q) == 0: self.__cond_empty.wait() item = self.__q.pop(0) if len(self.__q) == self.__max_queue_size - 1: self.__cond_full.notify() return item def producer(mon: Monitor, start_value: int): time.sleep(1) i = 1 while True: mon.add(i + start_value) i += 1 def consumer(mon: Monitor): while True: data = mon.remove() print('Consumer', data) time.sleep(1) MAX_QUEUE_SIZE = 6 NUM_PRODUCERS = 3 NUM_CONSUMERS = 2 q = [] monitor = Monitor(MAX_QUEUE_SIZE, q) for i in range(NUM_PRODUCERS): threading.Thread(target=producer, args=(monitor, i * 1000)).start() for _ in range(NUM_CONSUMERS): threading.Thread(target=consumer, args=(monitor,)).start() ================================================ FILE: python/exer03a_readers_writers.py ================================================ ''' THE READERS-WRITERS PROBLEM Solution for the first readers-writers problem ''' import random import time import threading class GlobalData: def __init__(self): self.resource = 0 self.reader_count = 0 self.lk_resource = threading.Lock() self.lk_reader_count = threading.Lock() def do_task_writer(g: GlobalData, delay_time: int): time.sleep(delay_time) with g.lk_resource: g.resource = random.randint(0, 99) print('Write', g.resource) def do_task_reader(g: GlobalData, delay_time: int): time.sleep(delay_time) # Increase reader count with g.lk_reader_count: g.reader_count += 1 if g.reader_count == 1: g.lk_resource.acquire() # Do the reading print('Read', g.resource) # Decrease reader count with g.lk_reader_count: g.reader_count -= 1 if g.reader_count == 0: g.lk_resource.release() gbl_data = GlobalData() NUM_READERS = 8 NUM_WRITERS = 6 for _ in range(NUM_READERS): threading.Thread(target=do_task_reader, args=(gbl_data, random.randint(0, 2))).start() for _ in range(NUM_WRITERS): threading.Thread(target=do_task_writer, args=(gbl_data, random.randint(0, 2))).start() ================================================ FILE: python/exer03b_readers_writers.py ================================================ ''' THE READERS-WRITERS PROBLEM Solution for the third readers-writers problem ''' import random import time import threading class GlobalData: def __init__(self): self.resource = 0 self.reader_count = 0 self.lk_resource = threading.Lock() self.lk_reader_count = threading.Lock() self.lk_service_queue = threading.Lock() def do_task_writer(g: GlobalData, delay_time: int): time.sleep(delay_time) with g.lk_service_queue: g.lk_resource.acquire() g.resource = random.randint(0, 99) print('Write', g.resource) g.lk_resource.release() def do_task_reader(g: GlobalData, delay_time: int): time.sleep(delay_time) with g.lk_service_queue: # Increase reader count with g.lk_reader_count: g.reader_count += 1 if g.reader_count == 1: g.lk_resource.acquire() # Do the reading print('Read', g.resource) # Decrease reader count with g.lk_reader_count: g.reader_count -= 1 if g.reader_count == 0: g.lk_resource.release() gbl_data = GlobalData() NUM_READERS = 8 NUM_WRITERS = 6 for _ in range(NUM_READERS): threading.Thread(target=do_task_reader, args=(gbl_data, random.randint(0, 2))).start() for _ in range(NUM_WRITERS): threading.Thread(target=do_task_writer, args=(gbl_data, random.randint(0, 2))).start() ================================================ FILE: python/exer04_dining_philosophers.py ================================================ ''' THE DINING PHILOSOPHERS PROBLEM ''' import time import threading def do_task_philosopher(chstk: list, n_philo: int, id_philo: int): time.sleep(1) with chstk[id_philo]: with chstk[(id_philo + 1) % n_philo]: print(f'Philosopher #{id_philo} is eating the rice') NUM_PHILOSOPHERS = 5 chopstick = [threading.Lock() for _ in range(NUM_PHILOSOPHERS)] for i in range(NUM_PHILOSOPHERS): threading.Thread(target=do_task_philosopher, args=(chopstick, NUM_PHILOSOPHERS, i)).start() ================================================ FILE: python/exer05a_product_matrix_vector.py ================================================ ''' MATRIX-VECTOR MULTIPLICATION ''' import threading def get_scalar_product(u: list, v: list): s = sum(a * b for a, b in zip(u, v)) return s def scalar_thfunc(u: list, v: list, res: list, idx_res: int): scalar_prod = get_scalar_product(u, v) res[idx_res] = scalar_prod def get_product(mat: list[list], vec: list) -> list: # Assume that size of mat and vec are both eligible size_row_mat = len(mat) # size_col_mat = len(mat[0]) # size_vec = len(vec) res = [0] * size_row_mat lstth = [] for i in range(size_row_mat): lstth.append(threading.Thread(target=scalar_thfunc, args=(mat[i], vec, res, i))) for th in lstth: th.start() for th in lstth: th.join() return res A = [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ] b = [ 3, -1, 0 ] result = get_product(A, b) print(result) ================================================ FILE: python/exer05b_product_matrix_vector.py ================================================ ''' MATRIX-MATRIX MULTIPLICATION (DOT PRODUCT) ''' import threading def get_scalar_product(u: list, v: list): s = sum(a * b for a, b in zip(u, v)) return s def scalar_thfunc(u: list, v: list, res: list, idx_res: int): scalar_prod = get_scalar_product(u, v) res[idx_res] = scalar_prod def get_transpose_matrix(mat: list[list]) -> list[list]: num_row = len(mat) num_col = len(mat[0]) res = [[0] * num_row for _ in range(num_col)] for i in range(num_row): for j in range(num_col): res[j][i] = mat[i][j] return res def get_str_matrix(mat: list[list]): return '\n'.join( ' '.join(f'{val:>5}' for val in row) for row in mat ) def get_product(mata: list[list], matb: list[list]) -> list[list]: # Assume that size of mat and vec are both eligible size_row_a = len(mata) size_col_b = len(matb[0]) res = [[0] * size_col_b for _ in range(size_row_a)] matbt = get_transpose_matrix(matb) lstth = [] for i in range(size_row_a): for j in range(size_col_b): lstth.append( threading.Thread(target=scalar_thfunc, args=(mata[i], matbt[j], res[i], j)) ) for th in lstth: th.start() for th in lstth: th.join() return res A = [ [ 1, 3, 5 ], [ 2, 4, 6 ] ] B = [ [ 1, 0, 1, 0 ], [ 0, 1, 0, 1 ], [ 1, 0, 0, -2 ] ] result = get_product(A, B) print(get_str_matrix(result)) ================================================ FILE: python/exer06a_blocking_queue.py ================================================ ''' BLOCKING QUEUE IMPLEMENTATION Version A: Synchronous queues ''' import time import threading class SynchronousQueue: def __init__(self): self.__sem_put = threading.Semaphore(1) self.__sem_take = threading.Semaphore(0) self.__element = None def put(self, value): self.__sem_put.acquire() self.__element = value self.__sem_take.release() def take(self): self.__sem_take.acquire() result = self.__element self.__sem_put.release() return result def producer(syncq: SynchronousQueue): arr = [ 'lorem', 'ipsum', 'dolor' ] for data in arr: print(f'Producer: {data}') syncq.put(data) print(f'Producer: {data} \t\t\t[done]') def consumer(syncq: SynchronousQueue): time.sleep(5) for _ in range(3): value = syncq.take() print(f'\tConsumer: {value}') syncqueue = SynchronousQueue() threading.Thread(target=producer, args=(syncqueue,)).start() threading.Thread(target=consumer, args=(syncqueue,)).start() ================================================ FILE: python/exer06b01_blocking_queue.py ================================================ ''' BLOCKING QUEUE IMPLEMENTATION Version B01: General blocking queues Underlying mechanism: Semaphores ''' import time import threading class BlockingQueue: def __init__(self, capacity: int): if capacity <= 0: raise ValueError('capacity must be a positive integer') # self.__capacity = capacity self.__sem_remain = threading.Semaphore(capacity) self.__sem_fill = threading.Semaphore(0) self.__lk = threading.Lock() self.__q = [] # queue def put(self, value): self.__sem_remain.acquire() with self.__lk: self.__q.append(value) self.__sem_fill.release() def take(self): self.__sem_fill.acquire() with self.__lk: result = self.__q.pop(0) self.__sem_remain.release() return result def producer(q: BlockingQueue): arr = [ 'nice', 'to', 'meet', 'you' ] for data in arr: print(f'Producer: {data}') q.put(data) print(f'Producer: {data} \t\t\t[done]') def consumer(q: BlockingQueue): time.sleep(5) for i in range(4): data = q.take() print(f'\tConsumer: {data}') if i == 0: time.sleep(5) blkqueue = BlockingQueue(2) # capacity = 2 threading.Thread(target=producer, args=(blkqueue,)).start() threading.Thread(target=consumer, args=(blkqueue,)).start() ================================================ FILE: python/exer06b02_blocking_queue.py ================================================ ''' BLOCKING QUEUE IMPLEMENTATION Version B02: General blocking queues Underlying mechanism: Condition variables ''' import time import threading class BlockingQueue: def __init__(self, capacity: int): if capacity <= 0: raise ValueError('capacity must be a positive integer') self.__capacity = capacity self.__lk = threading.Lock() self.__cond_empty = threading.Condition(self.__lk) self.__cond_full = threading.Condition(self.__lk) self.__q = [] # queue def put(self, value): with self.__lk: while len(self.__q) >= self.__capacity: self.__cond_full.wait() self.__q.append(value) self.__cond_empty.notify() def take(self): result = None with self.__lk: while len(self.__q) == 0: self.__cond_empty.wait() result = self.__q.pop(0) self.__cond_full.notify() return result def producer(q: BlockingQueue): arr = [ 'nice', 'to', 'meet', 'you' ] for value in arr: print(f'Producer: {value}') q.put(value) print(f'Producer: {value} \t\t\t[done]') def consumer(q: BlockingQueue): time.sleep(5) for i in range(4): value = q.take() print(f'\tConsumer: {value}') if i == 0: time.sleep(5) blkqueue = BlockingQueue(2) # capacity = 2 threading.Thread(target=producer, args=(blkqueue,)).start() threading.Thread(target=consumer, args=(blkqueue,)).start() ================================================ FILE: python/exer07a_data_server.py ================================================ ''' THE DATA SERVER PROBLEM Version A: Solving the problem using a condition variable ''' from dataclasses import dataclass import time import threading @dataclass class Counter: value: int = 0 cond: threading.Condition = threading.Condition() def check_auth_user(): print('[ Auth ] Start') # Send request to authenticator, check permissions, encrypt, decrypt... time.sleep(20) print('[ Auth ] Done') def process_files(lst_file_name: list[str], counter: Counter): for file_name in lst_file_name: # Read file print('[ ReadFile ] Start', file_name) time.sleep(10) print('[ ReadFile ] Done ', file_name) with counter.cond: counter.value -= 1 counter.cond.notify() # Write log into disk time.sleep(5) print('[ WriteLog ]') def process_request(): lst_file_name = [ 'foo.html', 'bar.json' ] counter = Counter(value=len(lst_file_name)) # The server checks auth user while reading files, concurrently threading.Thread(target=process_files, args=(lst_file_name, counter)).start() check_auth_user() # The server waits for completion of loading files with counter.cond: while counter.value > 0: counter.cond.wait(10) # timeout = 10 seconds print('\nNow user is authorized and files are loaded') print('Do other tasks...\n') process_request() ================================================ FILE: python/exer07b_data_server.py ================================================ ''' THE DATA SERVER PROBLEM Version B: Solving the problem using a semaphore ''' import time import threading def check_auth_user(): print('[ Auth ] Start') # Send request to authenticator, check permissions, encrypt, decrypt... time.sleep(20) print('[ Auth ] Done') def process_files(lst_file_name: list[str], sem: threading.Semaphore): for file_name in lst_file_name: # Read file print('[ ReadFile ] Start', file_name) time.sleep(10) print('[ ReadFile ] Done ', file_name) sem.release() # Write log into disk time.sleep(5) print('[ WriteLog ]') def process_request(): lst_file_name = [ 'foo.html', 'bar.json' ] sem = threading.Semaphore(value=0) # The server checks auth user while reading files, concurrently threading.Thread(target=process_files, args=(lst_file_name, sem)).start() check_auth_user() # The server waits for completion of loading files for _ in range(len(lst_file_name)): sem.acquire() print('\nNow user is authorized and files are loaded') print('Do other tasks...\n') process_request() ================================================ FILE: python/exer07c_data_server.py ================================================ ''' THE DATA SERVER PROBLEM Version C: Solving the problem using a count-down latch ''' import time import threading from mylib_latch import CountDownLatch def check_auth_user(): print('[ Auth ] Start') # Send request to authenticator, check permissions, encrypt, decrypt... time.sleep(20) print('[ Auth ] Done') def process_files(lst_file_name: list[str], latch: CountDownLatch): for file_name in lst_file_name: # Read file print('[ ReadFile ] Start', file_name) time.sleep(10) print('[ ReadFile ] Done ', file_name) latch.count_down() # Write log into disk time.sleep(5) print('[ WriteLog ]') def process_request(): lst_file_name = [ 'foo.html', 'bar.json' ] latch = CountDownLatch(len(lst_file_name)) # The server checks auth user while reading files, concurrently threading.Thread(target=process_files, args=(lst_file_name, latch)).start() check_auth_user() # The server waits for completion of loading files latch.wait() print('\nNow user is authorized and files are loaded') print('Do other tasks...\n') process_request() ================================================ FILE: python/exer07d_data_server.py ================================================ ''' THE DATA SERVER PROBLEM Version D: Solving the problem using a blocking queue ''' import time import threading from queue import Queue def check_auth_user(): print('[ Auth ] Start') # Send request to authenticator, check permissions, encrypt, decrypt... time.sleep(20) print('[ Auth ] Done') def process_files(lst_file_name: list[str], blkq: Queue): for file_name in lst_file_name: # Read file print('[ ReadFile ] Start', file_name) time.sleep(10) print('[ ReadFile ] Done ', file_name) blkq.put(file_name) # You may put file data here # Write log into disk time.sleep(5) print('[ WriteLog ]') def process_request(): lst_file_name = [ 'foo.html', 'bar.json' ] blkq = Queue() # The server checks auth user while reading files, concurrently threading.Thread(target=process_files, args=(lst_file_name, blkq)).start() check_auth_user() # The server waits for completion of loading files for _ in range(len(lst_file_name)): blkq.get() print('\nNow user is authorized and files are loaded') print('Do other tasks...\n') process_request() ================================================ FILE: python/exer08_exec_service_itask.py ================================================ from abc import ABC, abstractmethod class ITask(ABC): @abstractmethod def run(self): pass ================================================ FILE: python/exer08_exec_service_main.py ================================================ ''' EXECUTOR SERVICE & THREAD POOL IMPLEMENTATION ''' import time from exer08_exec_service_itask import ITask from exer08_exec_service_v0a import MyExecServiceV0A from exer08_exec_service_v0b import MyExecServiceV0B from exer08_exec_service_v1a import MyExecServiceV1A from exer08_exec_service_v1b import MyExecServiceV1B from exer08_exec_service_v2a import MyExecServiceV2A from exer08_exec_service_v2b import MyExecServiceV2B class MyTask(ITask): def __init__(self, task_id: str): self.id = task_id def run(self): print(f'Task {self.id} is starting') time.sleep(3) print(f'Task {self.id} is completed') NUM_THREADS = 2 NUM_TASKS = 5 exec_service = MyExecServiceV0A(NUM_THREADS) lsttask = [MyTask(chr(i + 65)) for i in range(NUM_TASKS)] for task in lsttask: exec_service.submit(task) print('All tasks are submitted') exec_service.wait_task_done() print('All tasks are completed') exec_service.shutdown() ================================================ FILE: python/exer08_exec_service_v0a.py ================================================ ''' MY EXECUTOR SERVICE Version 0A: The easiest executor service - It uses a blocking queue as underlying mechanism. ''' import time from queue import Queue import threading from exer08_exec_service_itask import ITask class MyExecServiceV0A: class EmptyTask(ITask): def run(self): pass def __init__(self, num_threads: int): self.__num_threads = num_threads self.__lstth = [] self.__task_pending = Queue() for _ in range(self.__num_threads): self.__lstth.append( threading.Thread(target=MyExecServiceV0A.__thread_worker_func, args=(self,)) ) for th in self.__lstth: th.start() def submit(self, task: ITask): self.__task_pending.put_nowait(task) def wait_task_done(self): # This ExecService is too simple, # so there is no implementation for waitTaskDone() time.sleep(11) # fake behaviour def shutdown(self): # This ExecService is too simple, # so there is no implementation for shutdown() print('No implementation for shutdown().') print('You need to exit the app manually.') @staticmethod def __thread_worker_func(selfptr: 'MyExecServiceV0A'): task_pending = selfptr.__task_pending while True: # WAIT FOR AN AVAILABLE PENDING TASK task = task_pending.get() # DO THE TASK task.run() ================================================ FILE: python/exer08_exec_service_v0b.py ================================================ ''' MY EXECUTOR SERVICE Version 0B: The easiest executor service - It uses a blocking queue as underlying mechanism. - It supports waitTaskDone() and shutdown(). ''' import time from queue import Queue import threading from exer08_exec_service_itask import ITask class MyExecServiceV0B: class EmptyTask(ITask): def run(self): pass def __init__(self, num_threads: int): self.__num_threads = num_threads self.__lstth = [] self.__task_pending = Queue() self.__counter_task_running = 0 self.__force_thread_shutdown = False for _ in range(self.__num_threads): self.__lstth.append( threading.Thread(target=MyExecServiceV0B.__thread_worker_func, args=(self,)) ) for th in self.__lstth: th.start() def submit(self, task: ITask): self.__task_pending.put_nowait(task) def wait_task_done(self): # This ExecService is too simple, # so there is no good implementation for waitTaskDone() while self.__task_pending.qsize() > 0 or self.__counter_task_running > 0: time.sleep(1) def shutdown(self): self.__force_thread_shutdown = True # Wait until task_pending is empty self.__task_pending.join() # Invoke blocked threads by adding "empty" tasks for _ in range(self.__num_threads): self.__task_pending.put(self.EmptyTask()) _ = [th.join() for th in self.__lstth] self.__num_threads = 0 self.__lstth.clear() @staticmethod def __thread_worker_func(selfptr: 'MyExecServiceV0B'): task_pending = selfptr.__task_pending while True: # WAIT FOR AN AVAILABLE PENDING TASK task = task_pending.get() # If shutdown() was called, then exit the function if selfptr.__force_thread_shutdown: break # DO THE TASK selfptr.__counter_task_running += 1 task.run() task_pending.task_done() selfptr.__counter_task_running -= 1 ================================================ FILE: python/exer08_exec_service_v1a.py ================================================ ''' MY EXECUTOR SERVICE Version 1A: Simple executor service - Method "waitTaskDone" invokes thread sleeps in loop (which can cause performance problems). ''' import time import threading from exer08_exec_service_itask import ITask class MyExecServiceV1A: def __init__(self, num_threads: int): # self.shutdown() self.__num_threads = num_threads self.__lstth = [] self.__task_pending = [] self.__lk_task_pending = threading.Lock() self.__cond_task_pending = threading.Condition(self.__lk_task_pending) self.__force_thread_shutdown = False with self.__lk_task_pending: self.__counter_task_running = 0 for _ in range(self.__num_threads): self.__lstth.append( threading.Thread(target=MyExecServiceV1A.__thread_worker_func, args=(self,)) ) for th in self.__lstth: th.start() def submit(self, task: ITask): with self.__lk_task_pending: self.__task_pending.append(task) self.__cond_task_pending.notify() def wait_task_done(self): done = False while True: with self.__lk_task_pending: if len(self.__task_pending) == 0 and self.__counter_task_running == 0: done = True if done: break time.sleep(1) def shutdown(self): if not hasattr(self, f'_{self.__class__.__name__}__lstth'): return self.__force_thread_shutdown = True with self.__lk_task_pending: self.__task_pending.clear() self.__cond_task_pending.notify_all() _ = [th.join() for th in self.__lstth] self.__num_threads = 0 self.__lstth.clear() @staticmethod def __thread_worker_func(selfptr: 'MyExecServiceV1A'): task_pending = selfptr.__task_pending lk_task_pending = selfptr.__lk_task_pending cond_task_pending = selfptr.__cond_task_pending while True: with lk_task_pending: # WAIT FOR AN AVAILABLE PENDING TASK while len(task_pending) == 0 and not selfptr.__force_thread_shutdown: cond_task_pending.wait() if selfptr.__force_thread_shutdown: # lk_task_pending.release() break # GET THE TASK FROM THE PENDING QUEUE task = task_pending.pop(0) selfptr.__counter_task_running += 1 # DO THE TASK task.run() with lk_task_pending: selfptr.__counter_task_running -= 1 ================================================ FILE: python/exer08_exec_service_v1b.py ================================================ ''' MY EXECUTOR SERVICE Version 1B: Simple executor service - Method "waitTaskDone" uses a condition variable to synchronize. ''' import threading from exer08_exec_service_itask import ITask class MyExecServiceV1B: def __init__(self, num_threads: int): # self.shutdown() self.__num_threads = num_threads self.__lstth = [] self.__task_pending = [] self.__lk_task_pending = threading.Lock() self.__cond_task_pending = threading.Condition(self.__lk_task_pending) self.__counter_task_running = 0 self.__lk_task_running = threading.Lock() self.__cond_task_running = threading.Condition(self.__lk_task_running) self.__force_thread_shutdown = False for _ in range(self.__num_threads): self.__lstth.append( threading.Thread(target=MyExecServiceV1B.__thread_worker_func, args=(self,)) ) for th in self.__lstth: th.start() def submit(self, task: ITask): with self.__lk_task_pending: self.__task_pending.append(task) self.__cond_task_pending.notify() def wait_task_done(self): while True: with self.__lk_task_pending: if len(self.__task_pending) == 0: with self.__lk_task_running: while self.__counter_task_running > 0: self.__cond_task_running.wait() # no pending task and no running task break def shutdown(self): if not hasattr(self, f'_{self.__class__.__name__}__lstth'): return self.__force_thread_shutdown = True with self.__lk_task_pending: self.__task_pending.clear() self.__cond_task_pending.notify_all() _ = [th.join() for th in self.__lstth] self.__num_threads = 0 self.__lstth.clear() @staticmethod def __thread_worker_func(selfptr: 'MyExecServiceV1B'): task_pending = selfptr.__task_pending lk_task_pending = selfptr.__lk_task_pending cond_task_pending = selfptr.__cond_task_pending lk_task_running = selfptr.__lk_task_running cond_task_running = selfptr.__cond_task_running while True: with lk_task_pending: # WAIT FOR AN AVAILABLE PENDING TASK while len(task_pending) == 0 and not selfptr.__force_thread_shutdown: cond_task_pending.wait() if selfptr.__force_thread_shutdown: # lk_task_pending.release() break # GET THE TASK FROM THE PENDING QUEUE task = task_pending.pop(0) selfptr.__counter_task_running += 1 # DO THE TASK task.run() with lk_task_running: selfptr.__counter_task_running -= 1 if selfptr.__counter_task_running == 0: cond_task_running.notify() ================================================ FILE: python/exer08_exec_service_v2a.py ================================================ ''' MY EXECUTOR SERVICE Version 2A: The executor service storing running tasks - Method "waitTaskDone" uses a semaphore to synchronize. ''' import threading from exer08_exec_service_itask import ITask class MyExecServiceV2A: def __init__(self, num_threads: int): # self.shutdown() self.__num_threads = num_threads self.__lstth = [] self.__task_pending = [] self.__lk_task_pending = threading.Lock() self.__cond_task_pending = threading.Condition(self.__lk_task_pending) self.__task_running = [] self.__lk_task_running = threading.Lock() self.__counter_task_running = threading.Semaphore(0) self.__force_thread_shutdown = False for _ in range(self.__num_threads): self.__lstth.append( threading.Thread(target=MyExecServiceV2A.__thread_worker_func, args=(self,)) ) for th in self.__lstth: th.start() def submit(self, task: ITask): with self.__lk_task_pending: self.__task_pending.append(task) self.__cond_task_pending.notify() def wait_task_done(self): while True: self.__counter_task_running.acquire() with self.__lk_task_pending, self.__lk_task_running: if len(self.__task_pending) == 0 and len(self.__task_running) == 0: break def shutdown(self): if not hasattr(self, f'_{self.__class__.__name__}__lstth'): return self.__force_thread_shutdown = True with self.__lk_task_pending: self.__task_pending.clear() self.__cond_task_pending.notify_all() _ = [th.join() for th in self.__lstth] self.__num_threads = 0 self.__lstth.clear() @staticmethod def __thread_worker_func(selfptr: 'MyExecServiceV2A'): task_pending = selfptr.__task_pending lk_task_pending = selfptr.__lk_task_pending cond_task_pending = selfptr.__cond_task_pending task_running = selfptr.__task_running lk_task_running = selfptr.__lk_task_running counter_task_running = selfptr.__counter_task_running while True: with lk_task_pending: # WAIT FOR AN AVAILABLE PENDING TASK while len(task_pending) == 0 and not selfptr.__force_thread_shutdown: cond_task_pending.wait() if selfptr.__force_thread_shutdown: # lk_task_pending.release() break # GET THE TASK FROM THE PENDING QUEUE task = task_pending.pop(0) # PUSH IT TO THE RUNNING QUEUE with lk_task_running: task_running.append(task) # DO THE TASK task.run() # REMOVE IT FROM THE RUNNING QUEUE with lk_task_running: task_running.remove(task) counter_task_running.release() ================================================ FILE: python/exer08_exec_service_v2b.py ================================================ ''' MY EXECUTOR SERVICE Version 2B: The executor service storing running tasks - Method "waitTaskDone" uses a condition variable to synchronize. ''' import threading from exer08_exec_service_itask import ITask class MyExecServiceV2B: def __init__(self, num_threads: int): # self.shutdown() self.__num_threads = num_threads self.__lstth = [] self.__task_pending = [] self.__lk_task_pending = threading.Lock() self.__cond_task_pending = threading.Condition(self.__lk_task_pending) self.__task_running = [] self.__lk_task_running = threading.Lock() self.__cond_task_running = threading.Condition(self.__lk_task_running) self.__force_thread_shutdown = False for _ in range(self.__num_threads): self.__lstth.append( threading.Thread(target=MyExecServiceV2B.__thread_worker_func, args=(self,)) ) for th in self.__lstth: th.start() def submit(self, task: ITask): with self.__lk_task_pending: self.__task_pending.append(task) self.__cond_task_pending.notify() def wait_task_done(self): while True: with self.__lk_task_pending: if len(self.__task_pending) == 0: with self.__lk_task_running: while len(self.__task_running) > 0: self.__cond_task_running.wait() # no pending task and no running task break def shutdown(self): if not hasattr(self, f'_{self.__class__.__name__}__lstth'): return self.__force_thread_shutdown = True with self.__lk_task_pending: self.__task_pending.clear() self.__cond_task_pending.notify_all() _ = [th.join() for th in self.__lstth] self.__num_threads = 0 self.__lstth.clear() @staticmethod def __thread_worker_func(selfptr: 'MyExecServiceV2B'): task_pending = selfptr.__task_pending lk_task_pending = selfptr.__lk_task_pending cond_task_pending = selfptr.__cond_task_pending task_running = selfptr.__task_running lk_task_running = selfptr.__lk_task_running cond_task_running = selfptr.__cond_task_running while True: with lk_task_pending: # WAIT FOR AN AVAILABLE PENDING TASK while len(task_pending) == 0 and not selfptr.__force_thread_shutdown: cond_task_pending.wait() if selfptr.__force_thread_shutdown: # lk_task_pending.release() break # GET THE TASK FROM THE PENDING QUEUE task = task_pending.pop(0) # PUSH IT TO THE RUNNING QUEUE with lk_task_running: task_running.append(task) # DO THE TASK task.run() # REMOVE IT FROM THE RUNNING QUEUE with lk_task_running: task_running.remove(task) cond_task_running.notify() ================================================ FILE: python/mylib_latch.py ================================================ ''' /****************************************************** * * File name: mylib_latch.py * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The count-down latch implementation in Python 3 * ******************************************************/ ''' import threading class CountDownLatch: def __init__(self, count: int): if count < 0: raise ValueError('count must be a non-negative integer') self.__count = count self.__cond = threading.Condition() def get_count(self) -> int: return self.__count def count_down(self): with self.__cond: if self.__count <= 0: return self.__count -= 1 if self.__count <= 0: self.__cond.notify_all() def wait(self): with self.__cond: self.__cond.wait_for(lambda : self.__count <= 0) ================================================ FILE: python/mylib_rwlock.py ================================================ ''' /****************************************************** * * File name: mylib_rwlock.py * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The read-write lock implementation in Python 3 * Underlying mechanism: The Mutex * ******************************************************/ ''' import threading class ReadWriteLock: def __init__(self): self.__lk_service_queue = threading.Lock() self.__lk_resource = threading.Lock() self.__lk_reader_count = threading.Lock() self.__reader_count = 0 self.__rlock = self.ReadLock(self) self.__wlock = self.WriteLock(self) def get_reader_count(self) -> int: return self.__reader_count def acquire_write(self): with self.__lk_service_queue: self.__lk_resource.acquire() def release_write(self): self.__lk_resource.release() def acquire_read(self): with self.__lk_service_queue: with self.__lk_reader_count: self.__reader_count += 1 if self.__reader_count == 1: self.__lk_resource.acquire() def release_read(self): with self.__lk_reader_count: self.__reader_count -= 1 if self.__reader_count == 0: self.__lk_resource.release() def readlock(self) -> 'ReadWriteLock.ReadLock': return self.__rlock def writelock(self) -> 'ReadWriteLock.WriteLock': return self.__wlock class ReadLock: def __init__(self, owner: 'ReadWriteLock'): self.__owner = owner def __enter__(self): self.__owner.acquire_read() def __exit__(self, exc_type, exc_value, exc_traceback): self.__owner.release_read() class WriteLock: def __init__(self, owner: 'ReadWriteLock'): self.__owner = owner def __enter__(self): self.__owner.acquire_write() def __exit__(self, exc_type, exc_value, exc_traceback): self.__owner.release_write() ================================================ FILE: python/mylib_rwlock2.py ================================================ ''' /****************************************************** * * File name: mylib_rwlock2.py * * Author: Name: Thanh Nguyen * Email: thanh.it1995(at)gmail(dot)com * * License: 3-Clause BSD License * * Description: The read-write lock implementation in Python 3 * Underlying mechanism: The Condition Variable * ******************************************************/ ''' import threading class ReadWriteLock: def __init__(self): self.__cond = threading.Condition() self.__reader_count = 0 self.__writer = False self.__rlock = self.ReadLock(self) self.__wlock = self.WriteLock(self) def get_reader_count(self) -> int: return self.__reader_count def acquire_write(self): with self.__cond: self.__cond.wait_for(lambda: not self.__writer and self.__reader_count <= 0) self.__writer = True def release_write(self): with self.__cond: self.__writer = False self.__cond.notify_all() def acquire_read(self): with self.__cond: self.__cond.wait_for(lambda: not self.__writer) self.__reader_count += 1 def release_read(self): with self.__cond: self.__reader_count -= 1 if self.__reader_count <= 0: self.__cond.notify_all() def readlock(self) -> 'ReadWriteLock.ReadLock': return self.__rlock def writelock(self) -> 'ReadWriteLock.WriteLock': return self.__wlock class ReadLock: def __init__(self, owner: 'ReadWriteLock'): self.__owner = owner def __enter__(self): self.__owner.acquire_read() def __exit__(self, exc_type, exc_value, exc_traceback): self.__owner.release_read() class WriteLock: def __init__(self, owner: 'ReadWriteLock'): self.__owner = owner def __enter__(self): self.__owner.acquire_write() def __exit__(self, exc_type, exc_value, exc_traceback): self.__owner.release_write() ================================================ FILE: references.md ================================================ # REFERENCES ## General - [Columbia University, W4118 Operating Systems I (Junfeng Yang), lecture 8 (threads)](http://www.cs.columbia.edu/~junfeng/12sp-w4118/lectures/l08-thread.pdf) - - - - - - - -   ## Synchronization - Mutex: - - Reentrant lock: - - Barrier: - - Read/write lock: - - - Comparison: - - -   ## Classic problems - - - -