├── .gitignore ├── 020 Hello World.cpp ├── 030 Basic Multithreading.cpp ├── 040 When is Multithreading Useful.txt ├── 050 Shared Data.cpp ├── 060 Mutexes.cpp ├── 065 Function Arguments.cpp ├── 070 Lock Guards.cpp ├── 080 Threads with Callable Objects.cpp ├── 090 Calculating Pi.cpp ├── 100 Promises and Futures.cpp ├── 110 Promises and Exceptions.cpp ├── 120 Packaged Tasks.cpp ├── 130 Waiting for Threads.cpp ├── 140 Condition Variables.cpp ├── 150 Checking Condition Shared Resources.cpp ├── 170 Using Methods in Threads.cpp ├── 180 Containers and Thread Safety.cpp ├── 190 Producer Consumer.cpp ├── 200 A Blocking Queue.cpp ├── 210 Async.cpp ├── 220 Hardware Concurrency.cpp ├── 230 Launching Lots of Threads.cpp ├── 240 A Thread Pool.cpp ├── 250 Distributing Work Between Cores.cpp ├── 260 Timing Code.cpp ├── LICENSE ├── README ├── blocking_queue.h └── blocking_queue_demo.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | prog 3 | ./prog 4 | 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | -------------------------------------------------------------------------------- /020 Hello World.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | cout << "Hello world" << endl; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /030 Basic Multithreading.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | /* 8 | * When is multithreading useful? 9 | * 10 | * 1. When you're waiting for something external and want to execute code meanwhile. 11 | * (asynchronous execution) 12 | * 13 | * Example: pinging remote servers 14 | * Example: drawing graphics while also processing user input 15 | * 16 | * 2. Distributing processing across multiple cores 17 | * 18 | * Example: calculating pi to lots of digits 19 | * 20 | */ 21 | 22 | void work() 23 | { 24 | for (int i = 0; i < 10; i++) 25 | { 26 | this_thread::sleep_for(chrono::milliseconds(500)); 27 | cout << "Loop " << i << endl; 28 | } 29 | } 30 | 31 | int main() 32 | { 33 | thread t1(work); 34 | thread t2(work); 35 | 36 | t1.join(); 37 | t2.join(); 38 | 39 | return 0; 40 | } -------------------------------------------------------------------------------- /040 When is Multithreading Useful.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * When is multithreading useful? 3 | * 4 | * 1. When you're waiting for something external and want to execute code meanwhile. 5 | * (asynchronous execution) 6 | * 7 | * Example: pinging remote servers 8 | * Example: drawing graphics while also processing user input 9 | * 10 | * 2. Distributing processing across multiple cores 11 | * 12 | * Example: calculating pi to lots of digits 13 | * 14 | */ 15 | 16 | -------------------------------------------------------------------------------- /050 Shared Data.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | atomic count = 0; 11 | const int ITERATIONS = 1E6; 12 | 13 | thread t1([&count](){ 14 | for(int i = 0; i < ITERATIONS; i++) 15 | { 16 | ++count; 17 | } 18 | }); 19 | 20 | thread t2([&count](){ 21 | for(int i = 0; i < ITERATIONS; i++) 22 | { 23 | ++count; 24 | } 25 | }); 26 | 27 | t1.join(); 28 | t2.join(); 29 | 30 | cout << count << endl; 31 | 32 | return 0; 33 | } -------------------------------------------------------------------------------- /060 Mutexes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main() 10 | { 11 | int count = 0; 12 | const int ITERATIONS = 1E6; 13 | 14 | mutex mtx; 15 | 16 | auto func = [&]() { 17 | for (int i = 0; i < ITERATIONS; i++) 18 | { 19 | mtx.lock(); 20 | ++count; 21 | mtx.unlock(); 22 | } 23 | }; 24 | 25 | thread t1(func); 26 | thread t2(func); 27 | 28 | t1.join(); 29 | t2.join(); 30 | 31 | cout << count << endl; 32 | 33 | return 0; 34 | } -------------------------------------------------------------------------------- /065 Function Arguments.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void work(int &count, mutex &mtx) 10 | { 11 | for (int i = 0; i < 1E6; i++) 12 | { 13 | mtx.lock(); 14 | ++count; 15 | mtx.unlock(); 16 | } 17 | } 18 | 19 | int main() 20 | { 21 | int count = 0; 22 | 23 | mutex mtx; 24 | 25 | thread t1(work, ref(count), ref(mtx)); 26 | thread t2(work, ref(count), ref(mtx)); 27 | 28 | t1.join(); 29 | t2.join(); 30 | 31 | cout << count << endl; 32 | 33 | return 0; 34 | } -------------------------------------------------------------------------------- /070 Lock Guards.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void work(int &count, mutex &mtx) 10 | { 11 | for (int i = 0; i < 1E6; i++) 12 | { 13 | lock_guard guard(mtx); 14 | ++count; 15 | } 16 | } 17 | 18 | int main() 19 | { 20 | int count = 0; 21 | 22 | mutex mtx; 23 | 24 | thread t1(work, ref(count), ref(mtx)); 25 | thread t2(work, ref(count), ref(mtx)); 26 | 27 | t1.join(); 28 | t2.join(); 29 | 30 | cout << count << endl; 31 | 32 | return 0; 33 | } -------------------------------------------------------------------------------- /080 Threads with Callable Objects.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | class App 10 | { 11 | private: 12 | int count = 0; 13 | mutex mtx; 14 | 15 | public: 16 | void operator()() 17 | { 18 | for (int i = 0; i < 1E6; i++) 19 | { 20 | const lock_guard guard(mtx); 21 | ++count; 22 | } 23 | } 24 | 25 | int getCount() 26 | { 27 | return count; 28 | } 29 | }; 30 | 31 | int main() 32 | { 33 | App app; 34 | 35 | thread t1(ref(app)); 36 | thread t2(ref(app)); 37 | 38 | t1.join(); 39 | t2.join(); 40 | 41 | cout << app.getCount() << endl; 42 | 43 | return 0; 44 | } -------------------------------------------------------------------------------- /090 Calculating Pi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | double calculate_pi(int terms) 8 | { 9 | double sum = 0.0; 10 | 11 | for(int i = 0; i < terms; i++) 12 | { 13 | int sign = pow(-1, i); 14 | double term = 1.0/(i * 2 + 1); 15 | sum += sign * term; 16 | } 17 | 18 | return sum * 4; 19 | } 20 | 21 | int main() 22 | { 23 | cout << setprecision(15) << calculate_pi(1E8) << endl; 24 | return 0; 25 | } -------------------------------------------------------------------------------- /100 Promises and Futures.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | double calculate_pi(int terms) 10 | { 11 | double sum = 0.0; 12 | 13 | for(int i = 0; i < terms; i++) 14 | { 15 | int sign = pow(-1, i); 16 | double term = 1.0/(i * 2 + 1); 17 | sum += sign * term; 18 | } 19 | 20 | return sum * 4; 21 | } 22 | 23 | int main() 24 | { 25 | promise promise; 26 | 27 | auto do_calculation = [&](int terms){ 28 | auto result = calculate_pi(terms); 29 | 30 | promise.set_value(result); 31 | }; 32 | 33 | thread t1(do_calculation, 1E8); 34 | 35 | future future = promise.get_future(); 36 | 37 | cout << setprecision(15) << future.get() << endl; 38 | 39 | t1.join(); 40 | return 0; 41 | } -------------------------------------------------------------------------------- /110 Promises and Exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | double calculate_pi(int terms) 11 | { 12 | double sum = 0.0; 13 | 14 | if (terms < 1) 15 | { 16 | throw runtime_error("Terms cannot be less than 1"); 17 | } 18 | 19 | for (int i = 0; i < terms; i++) 20 | { 21 | int sign = pow(-1, i); 22 | double term = 1.0 / (i * 2 + 1); 23 | sum += sign * term; 24 | } 25 | 26 | return sum * 4; 27 | } 28 | 29 | int main() 30 | { 31 | promise promise; 32 | 33 | auto do_calculation = [&](int terms) { 34 | try 35 | { 36 | auto result = calculate_pi(terms); 37 | promise.set_value(result); 38 | } 39 | catch (...) 40 | { 41 | promise.set_exception(current_exception()); 42 | } 43 | }; 44 | 45 | thread t1(do_calculation, 1E6); 46 | 47 | future future = promise.get_future(); 48 | 49 | try 50 | { 51 | cout << setprecision(15) << future.get() << endl; 52 | } 53 | catch (const exception &e) 54 | { 55 | cout << e.what() << endl; 56 | } 57 | 58 | t1.join(); 59 | return 0; 60 | } -------------------------------------------------------------------------------- /120 Packaged Tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | double calculate_pi(int terms) 10 | { 11 | double sum = 0.0; 12 | 13 | if (terms < 1) 14 | { 15 | throw runtime_error("Terms cannot be less than 1"); 16 | } 17 | 18 | for (int i = 0; i < terms; i++) 19 | { 20 | int sign = pow(-1, i); 21 | double term = 1.0 / (i * 2 + 1); 22 | sum += sign * term; 23 | } 24 | 25 | return sum * 4; 26 | } 27 | 28 | int main() 29 | { 30 | packaged_task task1(calculate_pi); 31 | 32 | future future1 = task1.get_future(); 33 | 34 | thread t1(move(task1), 0); 35 | 36 | try 37 | { 38 | double result = future1.get(); 39 | cout << setprecision(15) << result << endl; 40 | } 41 | catch (exception &e) 42 | { 43 | cout << "ERROR! " << e.what() << endl; 44 | } 45 | 46 | t1.join(); 47 | 48 | return 0; 49 | } -------------------------------------------------------------------------------- /130 Waiting for Threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main() 8 | { 9 | atomic ready = false; 10 | 11 | thread t1([&](){ 12 | this_thread::sleep_for(chrono::milliseconds(2000)); 13 | ready = true; 14 | }); 15 | 16 | t1.join(); 17 | 18 | while(!ready) 19 | { 20 | this_thread::sleep_for(chrono::milliseconds(100)); 21 | } 22 | 23 | cout << "ready " << ready << endl; 24 | 25 | return 0; 26 | } -------------------------------------------------------------------------------- /140 Condition Variables.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | condition_variable condition; 11 | mutex mtx; 12 | bool ready = false; 13 | 14 | thread t1([&](){ 15 | this_thread::sleep_for(chrono::milliseconds(2000)); 16 | unique_lock lock(mtx); 17 | ready = true; 18 | lock.unlock(); 19 | condition.notify_one(); 20 | }); 21 | 22 | unique_lock lock(mtx); 23 | 24 | while(!ready) 25 | { 26 | condition.wait(lock); 27 | } 28 | 29 | cout << "ready " << ready << endl; 30 | 31 | t1.join(); 32 | return 0; 33 | } -------------------------------------------------------------------------------- /150 Checking Condition Shared Resources.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | condition_variable condition; 11 | mutex mtx; 12 | bool ready = false; 13 | 14 | thread t1([&]() { 15 | this_thread::sleep_for(chrono::milliseconds(2000)); 16 | 17 | cout << "t1 acquiring lock" << endl; 18 | unique_lock lock(mtx); 19 | cout << "t1 acquired lock" << endl; 20 | ready = true; 21 | lock.unlock(); 22 | cout << "t1 released lock; notifying" << endl; 23 | condition.notify_one(); 24 | }); 25 | 26 | cout << "main thread acquiring lock" << endl; 27 | unique_lock lock(mtx); 28 | 29 | cout << "main thread acquired lock; waiting" << endl; 30 | condition.wait(lock, [&](){ return ready; }); 31 | 32 | cout << "main thread finished waiting" << endl; 33 | cout << "ready " << ready << endl; 34 | 35 | t1.join(); 36 | return 0; 37 | } -------------------------------------------------------------------------------- /170 Using Methods in Threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | template 7 | class blocking_queue 8 | { 9 | public: 10 | void push(E e) 11 | { 12 | cout << "push" << endl; 13 | } 14 | 15 | void pop() 16 | { 17 | cout << "pop" << endl; 18 | } 19 | }; 20 | 21 | int main() 22 | { 23 | blocking_queue qu; 24 | 25 | thread t1(&blocking_queue::push, &qu, 7); 26 | thread t2(&blocking_queue::pop, &qu); 27 | 28 | t1.join(); 29 | t2.join(); 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /180 Containers and Thread Safety.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | template 8 | class blocking_queue 9 | { 10 | private: 11 | int _max_size; 12 | queue _queue; 13 | 14 | 15 | public: 16 | blocking_queue(int max_size): _max_size(max_size) 17 | { 18 | 19 | } 20 | 21 | void push(E e) 22 | { 23 | _queue.push(e); 24 | } 25 | 26 | E pop() 27 | { 28 | E item = _queue.front(); 29 | _queue.pop(); 30 | return item; 31 | } 32 | }; 33 | 34 | int main() 35 | { 36 | blocking_queue qu(5); 37 | 38 | thread t1([&](){ 39 | for(int i = 0; i < 10; i++) 40 | { 41 | qu.push(i); 42 | } 43 | }); 44 | 45 | thread t2([&](){ 46 | for(int i = 0; i < 10; i++) 47 | { 48 | auto item = qu.pop(); 49 | cout << "consumed " << item << endl; 50 | } 51 | }); 52 | 53 | t1.join(); 54 | t2.join(); 55 | 56 | return 0; 57 | } -------------------------------------------------------------------------------- /190 Producer Consumer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | template 10 | class blocking_queue 11 | { 12 | private: 13 | mutex _mtx; 14 | condition_variable _cond; 15 | int _max_size; 16 | queue _queue; 17 | 18 | 19 | public: 20 | blocking_queue(int max_size): _max_size(max_size) 21 | { 22 | 23 | } 24 | 25 | void push(E e) 26 | { 27 | unique_lock lock(_mtx); 28 | 29 | _cond.wait(lock, [this](){ return _queue.size() < _max_size; }); 30 | 31 | _queue.push(e); 32 | 33 | lock.unlock(); 34 | _cond.notify_one(); 35 | } 36 | 37 | E pop() 38 | { 39 | unique_lock lock(_mtx); 40 | 41 | _cond.wait(lock, [this](){ return !_queue.empty(); }); 42 | 43 | E item = _queue.front(); 44 | _queue.pop(); 45 | 46 | lock.unlock(); 47 | _cond.notify_one(); 48 | 49 | return item; 50 | } 51 | 52 | int size() 53 | { 54 | return _queue.size(); 55 | } 56 | }; 57 | 58 | int main() 59 | { 60 | blocking_queue qu(3); 61 | 62 | thread t1([&](){ 63 | for(int i = 0; i < 10; i++) 64 | { 65 | cout << "pushing " << i << endl; 66 | cout << "queue size is " << qu.size() << endl; 67 | qu.push(i); 68 | } 69 | }); 70 | 71 | thread t2([&](){ 72 | for(int i = 0; i < 10; i++) 73 | { 74 | auto item = qu.pop(); 75 | cout << "consumed " << item << endl; 76 | } 77 | }); 78 | 79 | t1.join(); 80 | t2.join(); 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /200 A Blocking Queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | template 10 | class blocking_queue 11 | { 12 | private: 13 | mutex _mtx; 14 | condition_variable _cond; 15 | int _max_size; 16 | queue _queue; 17 | 18 | public: 19 | blocking_queue(int max_size): _max_size(max_size) 20 | { 21 | 22 | } 23 | 24 | void push(E e) 25 | { 26 | unique_lock lock(_mtx); 27 | 28 | _cond.wait(lock, [this](){ return _queue.size() < _max_size; }); 29 | 30 | _queue.push(e); 31 | 32 | lock.unlock(); 33 | _cond.notify_one(); 34 | } 35 | 36 | E front() 37 | { 38 | unique_lock lock(_mtx); 39 | _cond.wait(lock, [this](){ return !_queue.empty(); }); 40 | 41 | return _queue.front(); 42 | } 43 | 44 | void pop() 45 | { 46 | unique_lock lock(_mtx); 47 | 48 | _cond.wait(lock, [this](){ return !_queue.empty(); }); 49 | 50 | _queue.pop(); 51 | 52 | lock.unlock(); 53 | _cond.notify_one(); 54 | } 55 | 56 | int size() 57 | { 58 | lock_guard lock(_mtx); 59 | return _queue.size(); 60 | } 61 | }; 62 | 63 | int main() 64 | { 65 | blocking_queue qu(3); 66 | 67 | thread t1([&](){ 68 | for(int i = 0; i < 10; i++) 69 | { 70 | cout << "pushing " << i << endl; 71 | cout << "queue size is " << qu.size() << endl; 72 | qu.push(i); 73 | } 74 | }); 75 | 76 | thread t2([&](){ 77 | for(int i = 0; i < 10; i++) 78 | { 79 | auto item = qu.front(); 80 | qu.pop(); 81 | cout << "consumed " << item << endl; 82 | } 83 | }); 84 | 85 | t1.join(); 86 | t2.join(); 87 | 88 | return 0; 89 | } -------------------------------------------------------------------------------- /210 Async.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int work(int id) 8 | { 9 | for (int i = 0; i < 5; i++) 10 | { 11 | cout << "running " << id << endl; 12 | this_thread::sleep_for(chrono::milliseconds(1000)); 13 | } 14 | 15 | return id * 7; 16 | } 17 | 18 | int main() 19 | { 20 | future f1 = async(launch::async, work, 0); 21 | future f2 = async(launch::async, work, 1); 22 | 23 | cout << f1.get() << endl; 24 | cout << f2.get() << endl; 25 | return 0; 26 | } -------------------------------------------------------------------------------- /220 Hardware Concurrency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | 7 | int main() 8 | { 9 | cout << thread::hardware_concurrency() << endl; 10 | return 0; 11 | } -------------------------------------------------------------------------------- /230 Launching Lots of Threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | mutex g_mtx; 11 | 12 | int work(int id) 13 | { 14 | unique_lock lock(g_mtx); 15 | cout << "Starting " << id << endl; 16 | lock.unlock(); 17 | 18 | this_thread::sleep_for(chrono::seconds(3)); 19 | 20 | return id; 21 | } 22 | 23 | int main() 24 | { 25 | vector> futures; 26 | 27 | for(int i = 0; i < thread::hardware_concurrency(); i++) 28 | { 29 | shared_future f = async(launch::async, work, i); 30 | futures.push_back(f); 31 | } 32 | 33 | for(auto f: futures) 34 | { 35 | cout << "Returned: " << f.get() << endl; 36 | } 37 | 38 | return 0; 39 | } -------------------------------------------------------------------------------- /240 A Thread Pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | mutex g_mtx; 13 | 14 | template 15 | class blocking_queue 16 | { 17 | private: 18 | mutex _mtx; 19 | condition_variable _cond; 20 | int _max_size; 21 | queue _queue; 22 | 23 | public: 24 | blocking_queue(int max_size) : _max_size(max_size) 25 | { 26 | } 27 | 28 | void push(E e) 29 | { 30 | unique_lock lock(_mtx); 31 | 32 | _cond.wait(lock, [this]() { return _queue.size() < _max_size; }); 33 | 34 | _queue.push(e); 35 | 36 | lock.unlock(); 37 | _cond.notify_one(); 38 | } 39 | 40 | E front() 41 | { 42 | unique_lock lock(_mtx); 43 | _cond.wait(lock, [this]() { return !_queue.empty(); }); 44 | 45 | return _queue.front(); 46 | } 47 | 48 | void pop() 49 | { 50 | unique_lock lock(_mtx); 51 | 52 | _cond.wait(lock, [this]() { return !_queue.empty(); }); 53 | 54 | _queue.pop(); 55 | 56 | lock.unlock(); 57 | _cond.notify_one(); 58 | } 59 | 60 | int size() 61 | { 62 | lock_guard lock(_mtx); 63 | return _queue.size(); 64 | } 65 | }; 66 | 67 | int work(int id) 68 | { 69 | unique_lock lock(g_mtx); 70 | cout << "Starting " << id << endl; 71 | lock.unlock(); 72 | 73 | int seconds = int((5.0 * rand()) / RAND_MAX + 3); 74 | this_thread::sleep_for(chrono::seconds(seconds)); 75 | 76 | return id; 77 | } 78 | 79 | int main() 80 | { 81 | // Here the argument supplied to the queue needs to be 82 | // one less than the number of threads you want to launch. 83 | blocking_queue> futures(2); 84 | 85 | thread t([&]() { 86 | for (int i = 0; i < 20; i++) 87 | { 88 | shared_future f = async(launch::async, work, i); 89 | futures.push(f); 90 | } 91 | }); 92 | 93 | for (int i = 0; i < 20; i++) 94 | { 95 | shared_future f = futures.front(); 96 | int value = f.get(); 97 | futures.pop(); 98 | cout << "Returned: " << value << endl; 99 | } 100 | 101 | t.join(); 102 | 103 | return 0; 104 | } -------------------------------------------------------------------------------- /250 Distributing Work Between Cores.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | double calculate_pi(int terms, int start, int skip) 13 | { 14 | double sum = 0.0; 15 | 16 | for (int i = start; i < terms; i += skip) 17 | { 18 | int sign = pow(-1, i); 19 | double term = 1.0 / (i * 2 + 1); 20 | sum += sign * term; 21 | } 22 | 23 | return sum * 4; 24 | } 25 | 26 | int main() 27 | { 28 | vector> futures; 29 | 30 | const int CONCURRENCY = thread::hardware_concurrency(); 31 | 32 | for (int i = 0; i < CONCURRENCY; i++) 33 | { 34 | shared_future f = async(launch::async, calculate_pi, 1E7, i, CONCURRENCY); 35 | futures.push_back(f); 36 | } 37 | 38 | double sum = 0.0; 39 | 40 | for (auto f : futures) 41 | { 42 | sum += f.get(); 43 | } 44 | 45 | cout << setprecision(15) << "PI: " << M_PI << endl; 46 | cout << setprecision(15) << "Sum: " << sum << endl; 47 | 48 | return 0; 49 | } -------------------------------------------------------------------------------- /260 Timing Code.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | /* 13 | * Slow Leibniz approximation. 14 | */ 15 | double calculate_pi(int terms, int start, int skip) 16 | { 17 | double sum = 0.0; 18 | 19 | for (int i = start; i < terms; i += skip) 20 | { 21 | int sign = pow(-1, i); 22 | double term = 1.0 / (i * 2 + 1); 23 | sum += sign * term; 24 | } 25 | 26 | return sum * 4; 27 | } 28 | 29 | int main() 30 | { 31 | vector> futures; 32 | 33 | const int CONCURRENCY = thread::hardware_concurrency(); 34 | 35 | auto start = chrono::steady_clock::now(); 36 | 37 | for (int i = 0; i < CONCURRENCY; i++) 38 | { 39 | shared_future f = async(launch::async, calculate_pi, 1E8, i, CONCURRENCY); 40 | futures.push_back(f); 41 | } 42 | 43 | double sum = 0.0; 44 | 45 | for (auto f : futures) 46 | { 47 | sum += f.get(); 48 | } 49 | 50 | auto end = chrono::steady_clock::now(); 51 | 52 | auto duration = chrono::duration_cast(end - start).count(); 53 | 54 | cout << "Duration: " << duration << endl; 55 | cout << setprecision(15) << "PI: " << M_PI << endl; 56 | cout << setprecision(15) << "Sum: " << sum << endl; 57 | 58 | return 0; 59 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cave of Programming / John Purcell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Code for C++ Multithreading video course. 2 | 3 | blocking_queue.h additionally contains a blocking queue implementation. 4 | 5 | "240 A Thread Pool.cpp" uses an identical implementation to create 6 | a thread pool. 7 | 8 | ==== Introduction ==== 9 | 010 Introduction.mov 10 | 015 Where to Find Source Code 11 | 020 Hello World.mov 12 | 030 Basic Multithreading.mov 13 | 040 When is Multithreading Useful.mov 14 | 15 | ==== Locks ==== 16 | 050 Shared Data.mov 17 | 060 Mutexes.mov 18 | 065 Function Arguments.mov 19 | 070 Lock Guards.mov 20 | 080 Threads with Callable Objects.mov 21 | 22 | ==== Return Values ==== 23 | 090 Calculating Pi.mov 24 | 100 Promises and Futures.mov 25 | 110 Promises and Exceptions.mov 26 | 120 Packaged Tasks.mov 27 | 28 | ==== Signalling ==== 29 | 130 Waiting for Threads.mov 30 | 140 Condition Variables.mov 31 | 150 Checking Condition Shared Resources.mov 32 | 160 Blocking Queues.mov 33 | 170 Using Methods in Threads.mov 34 | 180 Containers and Thread Safety.mov 35 | 190 Producer Consumer.mov 36 | 200 A Blocking Queue.mov 37 | 38 | ==== Processing Work Efficiently ==== 39 | 210 Async.mov 40 | 220 Hardware Concurrency.mov 41 | 230 Launching Lots of Threads.mov 42 | 240 A Thread Pool.mov 43 | 250 Distributing Work Between Cores.mov 44 | 260 Timing Code.mov -------------------------------------------------------------------------------- /blocking_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef BLOCKING_QUEUE_INCLUDED 2 | #define BLOCKING_QUEUE_INCLUDED 3 | 4 | /** 5 | * A simple blocking queue. 6 | * 7 | * Use push() to add items, pop() to remove items, and front() 8 | * to get the item at the front of the queue. 9 | * 10 | * See blocking_queue_demo.cpp for example. 11 | * 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | template 19 | class blocking_queue 20 | { 21 | private: 22 | std::mutex _mtx; 23 | std::condition_variable _cond; 24 | int _max_size; 25 | std::queue _queue; 26 | 27 | public: 28 | blocking_queue(int max_size): _max_size(max_size) 29 | { 30 | 31 | } 32 | 33 | void push(E e) 34 | { 35 | std::unique_lock lock(_mtx); 36 | 37 | _cond.wait(lock, [this](){ return _queue.size() < _max_size; }); 38 | 39 | _queue.push(e); 40 | 41 | lock.unlock(); 42 | _cond.notify_one(); 43 | } 44 | 45 | E front() 46 | { 47 | std::unique_lock lock(_mtx); 48 | _cond.wait(lock, [this](){ return !_queue.empty(); }); 49 | 50 | return _queue.front(); 51 | } 52 | 53 | void pop() 54 | { 55 | std::unique_lock lock(_mtx); 56 | 57 | _cond.wait(lock, [this](){ return !_queue.empty(); }); 58 | 59 | _queue.pop(); 60 | 61 | lock.unlock(); 62 | _cond.notify_one(); 63 | } 64 | 65 | int size() 66 | { 67 | std::lock_guard lock(_mtx); 68 | return _queue.size(); 69 | } 70 | }; 71 | 72 | #endif -------------------------------------------------------------------------------- /blocking_queue_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "blocking_queue.h" 5 | 6 | /** 7 | * Blocking queue demo 8 | */ 9 | 10 | 11 | int main() 12 | { 13 | blocking_queue qu(3); 14 | 15 | std::thread t1([&](){ 16 | for(int i = 0; i < 10; i++) 17 | { 18 | std::cout << "pushing " << i << std::endl; 19 | std::cout << "queue size is " << qu.size() << std::endl; 20 | qu.push(i); 21 | } 22 | }); 23 | 24 | std::thread t2([&](){ 25 | for(int i = 0; i < 10; i++) 26 | { 27 | auto item = qu.front(); 28 | qu.pop(); 29 | std::cout << "consumed " << item << std::endl; 30 | std::this_thread::sleep_for(std::chrono::seconds(2)); 31 | } 32 | }); 33 | 34 | t1.join(); 35 | t2.join(); 36 | 37 | return 0; 38 | } --------------------------------------------------------------------------------