├── .gitignore ├── LICENSE.md ├── README.md ├── archives ├── README.md ├── concurrency-1.tar.gz ├── concurrency-2.tar.gz ├── concurrency-3.tar.gz ├── concurrency-4.tar.gz ├── concurrency-5.tar.gz └── concurrency-6.tar.gz ├── atomics ├── README.md ├── asm │ ├── abs_amd64.s │ ├── floor_amd64.s │ ├── main.go │ ├── print_amd64.s │ └── sqrt_amd64.s ├── atomic-implementation │ ├── atomic_amd64.s │ └── main.go ├── basic-counter │ └── main.go ├── benchmarks │ └── number_vs_value_test.go ├── race-fixed │ └── main.go ├── race │ └── main.go ├── registers.md └── value │ ├── calculator │ └── main.go │ ├── not-atomic │ └── main.go │ ├── panic │ └── main.go │ └── reader-writer │ └── main.go ├── channels ├── README.md ├── buffered-vs-unbuffered │ ├── benchmarks │ │ ├── channels_test.go │ │ └── files_test.go │ └── main.go ├── buffered │ ├── range │ │ └── main.go │ └── simple │ │ └── main.go ├── channel-length │ └── main.go ├── channel-of-channels │ └── main.go ├── channel-vs-atomics │ ├── channel_vs_atomics_test.go │ └── main.go ├── channel-vs-mutex │ ├── chan-cache │ │ └── main.go │ ├── critical │ │ ├── channel_cache_test.go │ │ ├── critical_test.go │ │ └── mutex_cache_test.go │ └── mutex-cache │ │ └── main.go ├── channel-vs-waitgroup │ ├── channel_vs_waitgroup_test.go │ └── main.go ├── closed │ └── main.go ├── comma-ok │ ├── select │ │ └── main.go │ └── simple │ │ └── main.go ├── conversion │ └── main.go ├── deadlock │ ├── nobody-reads │ │ └── main.go │ ├── nobody-writes │ │ └── main.go │ ├── unclosed-channel │ │ └── main.go │ └── write-on-closed-channel │ │ └── main.go ├── empty-struct │ └── main.go ├── fifo-queue │ └── main.go ├── invalid-read-write │ └── main.go ├── nil │ └── main.go ├── range │ └── main.go ├── read-from-closed │ └── main.go ├── read-only │ └── main.go ├── simple │ └── main.go ├── timers-and-tickers │ └── main.go ├── unblock-goroutines │ └── main.go └── write-only │ └── main.go ├── cover.jpg ├── go-routines ├── README.md ├── anonymous │ └── main.go ├── async-preemption │ └── main.go ├── busy-vs-responsible │ └── main.go ├── closures │ ├── anonymous-vs-named │ │ └── main.go │ ├── loops │ │ └── main.go │ └── passed-by-value │ │ └── main.go ├── consecutive-order-channels │ └── main.go ├── consecutive-order-waitgroups │ └── main.go ├── ctxswitch │ └── ctxswitch_test.go ├── goexit │ └── main.go ├── gomaxprocs │ └── main.go ├── gosched │ └── main.go ├── hanging │ └── main.go ├── io │ └── main.go ├── leaking │ └── main.go ├── many │ └── main.go ├── memory-stress │ └── main.go ├── nested │ └── main.go ├── netpoller │ └── main.go ├── no-leaks-done-channel │ └── main.go ├── panic │ └── main.go ├── preemptive-vs-nonpreemptive │ └── main.go ├── pure-concurrency │ └── main.go ├── race-condition │ ├── README.md │ ├── race-miss │ │ └── main.go │ └── read-write │ │ └── main.go ├── simple │ └── main.go ├── stack-overflow │ └── main.go ├── stack-size │ └── main.go └── tracing │ ├── basic │ └── main.go │ ├── http-calls │ └── main.go │ ├── io-operations │ └── main.go │ └── max-lrq-capacity │ └── main.go ├── go.mod ├── gomaxprocs ├── README.md ├── gomaxprocs-many-threads │ └── main.go └── gomaxprocs-numcpu │ └── main.go ├── intro ├── README.md ├── async-tasks │ └── main.go ├── fixed-async-tasks │ └── main.go ├── fork-join │ ├── channel-join-point │ │ └── main.go │ ├── no-join-point │ │ └── main.go │ └── wg-join-point │ │ └── main.go └── sync-tasks │ └── main.go ├── mutexes ├── README.md ├── atomic-mutex-mix │ └── main.go ├── basic-mutex │ └── main.go ├── cond │ ├── README.md │ ├── broadcast │ │ └── main.go │ ├── button │ │ └── main.go │ ├── cond-implementation │ │ ├── atomic │ │ │ └── main.go │ │ └── channel │ │ │ └── main.go │ ├── cond-vs-channel │ │ ├── benchmarks │ │ │ └── cond_vs_channel_test.go │ │ ├── channel-broadcast │ │ │ └── main.go │ │ └── channel-signal │ │ │ └── main.go │ ├── deadlock │ │ └── main.go │ ├── enqueue-dequeue │ │ └── main.go │ ├── livelocks │ │ └── main.go │ ├── multiple-unlocks │ │ └── main.go │ ├── reward-users │ │ ├── condition-lock │ │ │ └── main.go │ │ └── inefficient-lock │ │ │ └── main.go │ ├── shopping │ │ └── main.go │ ├── simple │ │ └── main.go │ ├── too-much-cpu │ │ └── main.go │ ├── too-much-sleep │ │ └── main.go │ └── wg-deadlock │ │ └── main.go ├── contexts │ ├── coarse-grained-context │ │ └── main.go │ ├── fine-grained-context │ │ └── main.go │ ├── multiple-gouroutines-readonly │ │ └── main.go │ ├── single-and-main-gouroutine │ │ └── main.go │ └── single-gouroutine │ │ └── main.go ├── crypto-reader │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── cmd │ │ ├── generator │ │ │ └── main.go │ │ └── reader │ │ │ └── main.go │ ├── crypto │ │ ├── file.go │ │ ├── file_test.go │ │ ├── reader.go │ │ └── reader_test.go │ ├── go.mod │ └── go.sum ├── dbcache │ ├── mutex-beautiful │ │ └── main.go │ ├── mutex-ugly │ │ └── main.go │ └── race │ │ └── main.go ├── deadlock-and-race │ └── main.go ├── deadlock │ └── main.go ├── deadlocks │ ├── circular-wait │ │ └── main.go │ ├── goexit │ │ └── main.go │ ├── hold-and-wait │ │ └── main.go │ ├── mutual-exclusion │ │ └── main.go │ └── no-preemption │ │ └── main.go ├── distributed-db │ ├── .gitignore │ ├── README.md │ ├── app │ │ └── app.go │ ├── clients │ │ └── http.go │ ├── controllers │ │ ├── delete.go │ │ ├── get.go │ │ ├── gossip.go │ │ ├── router.go │ │ ├── set.go │ │ ├── set_batch.go │ │ └── tokens.go │ ├── go.mod │ ├── main.go │ ├── models │ │ ├── cache.go │ │ ├── flags.go │ │ ├── nodes.go │ │ ├── requests.go │ │ ├── responses.go │ │ └── tokens.go │ ├── repositories │ │ └── cache.go │ ├── services │ │ └── cache.go │ └── workers │ │ ├── gossip.go │ │ └── streamer.go ├── exercises │ ├── 1 │ │ ├── exercise.go │ │ ├── exercise_test.go │ │ └── solution │ │ │ └── solution.go │ ├── 2 │ │ ├── exercise.go │ │ ├── exercise_test.go │ │ └── solution │ │ │ └── solution.go │ ├── 3 │ │ ├── exercise.go │ │ ├── exercise_test.go │ │ └── solution │ │ │ └── solution.go │ ├── 4 │ │ ├── exercise.go │ │ ├── exercise_test.go │ │ └── solution │ │ │ └── solution.go │ ├── 5 │ │ ├── exercise.go │ │ ├── exercise_test.go │ │ └── solution │ │ │ └── solution.go │ └── 6 │ │ ├── exercise.go │ │ ├── exercise_test.go │ │ └── solution │ │ └── solution.go ├── livelock │ └── main.go ├── lock-contention │ ├── basic │ │ └── main.go │ └── reduce-hold-time │ │ └── main.go ├── multiple-locks │ └── main.go ├── multiple-unlocks │ └── main.go ├── mutex-implementation │ ├── main.c │ └── main.go ├── mutex-vs-atomic │ ├── atomic_test.go │ ├── config_test.go │ └── mutex_test.go ├── mutex-vs-rwmutex │ ├── main.go │ └── mutex_vs_rwmutex_test.go ├── once │ ├── README.md │ ├── caching │ │ ├── bad │ │ │ ├── bad_test.go │ │ │ └── main.go │ │ ├── better │ │ │ ├── better_test.go │ │ │ └── main.go │ │ └── good │ │ │ ├── good_test.go │ │ │ └── main.go │ ├── deadlock │ │ └── main.go │ ├── inc-dec │ │ └── main.go │ ├── once-implementation │ │ └── main.go │ ├── race │ │ └── main.go │ ├── resync │ │ └── main.go │ └── simple │ │ └── main.go ├── pass-by-copy │ └── main.go ├── reentrant-lock │ └── main.go ├── rlocker-write │ └── main.go ├── rwmutex-exclusive │ └── main.go ├── rwmutex │ └── main.go ├── semaphore │ └── main.go ├── simple-counter │ ├── benchmarks │ │ └── wg-vs-sequential_test.go │ ├── norace-mutex │ │ └── main.go │ ├── norace-waitgroup │ │ └── main.go │ └── race │ │ └── main.go ├── starvation │ ├── basic │ │ └── main.go │ └── polite-greedy │ │ └── main.go ├── synclocker │ └── main.go ├── syncmap │ ├── README.md │ ├── basic │ │ └── main.go │ └── benchmarks │ │ ├── builtinmap_vs_syncmap_test.go │ │ └── rwmutex_vs_syncmap_test.go └── ugly-unlocks │ └── main.go ├── patterns ├── README.md ├── bridge-channel │ └── main.go ├── cancellation │ ├── cancellation.go │ ├── method-chaining │ │ └── main.go │ └── simple-functions │ │ └── main.go ├── circuit-breaker │ ├── go.mod │ ├── go.sum │ ├── http │ │ └── main.go │ └── simple │ │ └── main.go ├── codegen │ ├── main.go │ └── templates │ │ ├── fanin.go │ │ ├── repeat.go │ │ ├── repeatfn.go │ │ └── take.go ├── context │ ├── context-keys │ │ ├── collision │ │ │ ├── main.go │ │ │ └── mycontext │ │ │ │ └── mycontext.go │ │ └── private-keys │ │ │ ├── main.go │ │ │ └── mycontext │ │ │ └── mycontext.go │ ├── context-vs-done-channel │ │ ├── benchmarks │ │ │ └── benchmarks_test.go │ │ ├── channel │ │ │ └── main.go │ │ └── ctx │ │ │ └── main.go │ ├── http │ │ └── main.go │ ├── mycontext │ │ └── mycontext.go │ ├── nested │ │ └── main.go │ ├── simple-map-keys │ │ └── main.go │ └── simple │ │ └── main.go ├── daisy-chain │ ├── functions-chain │ │ └── main.go │ └── simple │ │ └── main.go ├── error-handling │ ├── concurrent │ │ ├── bad │ │ │ └── main.go │ │ ├── better │ │ │ └── main.go │ │ └── good │ │ │ └── main.go │ ├── error-basics │ │ ├── basic-wrap-unwrap │ │ │ └── main.go │ │ ├── complex-custom-error │ │ │ └── main.go │ │ ├── errors-is-as │ │ │ └── main.go │ │ ├── errors-wrap-unwrap │ │ │ └── main.go │ │ ├── propagation │ │ │ └── main.go │ │ └── simple-custom-error │ │ │ └── main.go │ ├── http │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app │ │ │ └── app.go │ │ ├── controllers │ │ │ ├── controllers.go │ │ │ ├── create_booking.go │ │ │ ├── get_booking.go │ │ │ ├── not_found.go │ │ │ └── routes.go │ │ ├── db │ │ │ └── db.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── logging │ │ │ ├── http.go │ │ │ └── logging.go │ │ ├── main.go │ │ ├── models │ │ │ ├── errors.go │ │ │ ├── models.go │ │ │ └── requests.go │ │ ├── repositories │ │ │ └── bookings.go │ │ ├── services │ │ │ └── bookings.go │ │ ├── transport │ │ │ └── encoding.go │ │ └── worker │ │ │ └── worker.go │ └── logging │ │ ├── errors.go │ │ ├── logging.go │ │ └── main.go ├── fan-in-out │ ├── efficient │ │ └── main.go │ ├── inefficient │ │ └── main.go │ └── main.go ├── fanin │ ├── cmd │ │ └── main.go │ └── fanin.go ├── fanout │ ├── cmd │ │ └── main.go │ └── fanout.go ├── generator │ ├── cmd │ │ └── main.go │ └── generator.go ├── generators │ ├── benchmarks │ │ └── typed_vs_generic_test.go │ ├── generators.go │ ├── repeat-fn │ │ └── main.go │ ├── repeat │ │ └── main.go │ ├── take │ │ └── main.go │ └── typed_generators.go ├── healing-unhealthy-goroutines │ ├── main.go │ └── or.go ├── heartbeats │ ├── 1-to-1-heartbeats │ │ └── main.go │ ├── broken │ │ └── main.go │ ├── interval-based-heartbeats │ │ └── main.go │ └── simple │ │ └── main.go ├── or-channel │ └── main.go ├── or-done-channel │ ├── or-done │ │ └── main.go │ └── simple-done │ │ └── main.go ├── ping-pong │ └── main.go ├── pipeline │ ├── channels │ │ └── main.go │ ├── cmd │ │ └── main.go │ ├── digest-tree │ │ ├── benchmarks_test.go │ │ ├── digestion │ │ │ ├── bounded_parallelism.go │ │ │ ├── digestion.go │ │ │ ├── parallel_digestion.go │ │ │ └── simple_digestion.go │ │ ├── files │ │ │ ├── file1.txt │ │ │ ├── file2.txt │ │ │ └── file3.txt │ │ └── main.go │ ├── pipeline.go │ ├── simple │ │ └── main.go │ └── stream-vs-batch │ │ ├── main.go │ │ └── stream_vs_batch_test.go ├── quit │ └── main.go ├── rate-limiting │ ├── finer-rate-limiting │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── multi-rate-limiting │ │ └── main.go │ ├── simple-rate-limiting │ │ └── main.go │ ├── single-rate-limiting │ │ └── main.go │ └── token-bucket │ │ └── main.go ├── replicated-requests │ └── main.go ├── search │ └── main.go ├── server-and-worker │ └── main.go ├── server │ └── main.go ├── tee-channel │ └── main.go ├── timers-tickers │ └── main.go └── worker │ └── main.go ├── pool ├── README.md ├── allocations │ └── main.go ├── benchmarks │ ├── big_objects_pool_vs_allocation_test.go │ ├── pool_vs_allocations_test.go │ └── write_gzip_test.go ├── concurrent │ └── main.go ├── connections │ ├── connections_test.go │ └── main.go ├── gc │ └── main.go ├── object-reuse │ └── main.go ├── queue │ └── main.go ├── server │ ├── server.go │ └── server_test.go └── simple │ └── main.go ├── presentations ├── 1_introduction-to-concurrency │ ├── Introduction to Concurrency.key │ ├── Introduction to Concurrency.pdf │ └── Introduction to Concurrency.pptx ├── 2_3_4_waitgroups │ ├── WaitGroups.key │ ├── WaitGroups.pdf │ └── WaitGroups.pptx ├── 5_6_atomics │ ├── Atomics.key │ ├── Atomics.pdf │ └── Atomics.pptx ├── 7_mutexes │ ├── Distributed Database.key │ ├── Mutexes.key │ ├── Mutexes.pdf │ └── Mutexes.pptx ├── Concurrency - DRAFT.key └── README.md ├── profiling ├── README.md └── basic-pprof │ └── main.go ├── s3 ├── README.md ├── app │ └── app.go ├── controllers │ ├── copy_objects.go │ ├── list.go │ ├── make_bucket.go │ ├── move_objects.go │ ├── remove_bucket.go │ └── remove_objects.go ├── go.mod └── main.go ├── select ├── README.md ├── default │ └── main.go ├── empty │ └── main.go ├── for-select │ └── main.go ├── multiple-channels-ready │ └── main.go ├── nested │ └── main.go ├── select-vs-switch │ └── main.go ├── simple │ └── main.go └── timeout │ └── main.go ├── testing ├── README.md ├── basic-benchmarking │ └── efficient_vs_inefficient_sum_test.go └── basic-testing │ └── sum_test.go ├── threads ├── README.md ├── c-multithreading │ └── main.c ├── cpu-hog │ └── main.go └── lock-os-threads │ ├── benchmarks │ └── benchmarks_test.go │ ├── cgo │ ├── main.go │ ├── work.c │ └── work.h │ └── go │ └── main.go └── waitgroups ├── README.md ├── basic └── main.go ├── benchmarks └── add1_vs_addmany_test.go ├── deadlock └── main.go ├── done-too-many-times └── main.go ├── goroutines-order ├── different-workloads │ ├── main.go │ └── server.go ├── preserve-order │ └── main.go └── simple │ └── main.go ├── limit-goroutines └── main.go ├── no-add └── main.go ├── passed-by-value └── main.go ├── rate-limiting ├── main.go └── server.go ├── waitgroup-implementation └── main.go ├── wg-reuse ├── loop │ └── main.go └── simple │ └── main.go ├── with-waitgroup └── main.go └── without-waitgroup └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | *_generated.go 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [Ștefan Cîrlig] 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 | -------------------------------------------------------------------------------- /archives/concurrency-1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/archives/concurrency-1.tar.gz -------------------------------------------------------------------------------- /archives/concurrency-2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/archives/concurrency-2.tar.gz -------------------------------------------------------------------------------- /archives/concurrency-3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/archives/concurrency-3.tar.gz -------------------------------------------------------------------------------- /archives/concurrency-4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/archives/concurrency-4.tar.gz -------------------------------------------------------------------------------- /archives/concurrency-5.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/archives/concurrency-5.tar.gz -------------------------------------------------------------------------------- /archives/concurrency-6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/archives/concurrency-6.tar.gz -------------------------------------------------------------------------------- /atomics/asm/abs_amd64.s: -------------------------------------------------------------------------------- 1 | // func Abs(x float64) float64 2 | TEXT ·Abs(SB),$0 3 | MOVQ $(1<<63), BX 4 | MOVQ BX, X0 5 | MOVSD x+0(FP), X1 6 | ANDNPD X1, X0 7 | MOVSD X0, ret+8(FP) 8 | RET 9 | -------------------------------------------------------------------------------- /atomics/asm/floor_amd64.s: -------------------------------------------------------------------------------- 1 | #define Big 0x4330000000000000 2 | 3 | // func Floor(x float64) float64 4 | TEXT ·Floor(SB),$0 5 | MOVQ x+0(FP), AX 6 | MOVQ $~(1<<63), DX 7 | ANDQ AX,DX 8 | SUBQ $1,DX 9 | MOVQ $(Big - 1), CX 10 | CMPQ DX,CX 11 | JAE isBig_floor 12 | MOVQ AX, X0 13 | CVTTSD2SQ X0, AX 14 | CVTSQ2SD AX, X1 15 | CMPSD X1, X0, 1 16 | MOVSD $(-1.0), X2 17 | ANDPD X2, X0 18 | ADDSD X1, X0 19 | MOVSD X0, ret+8(FP) 20 | RET 21 | isBig_floor: 22 | MOVQ AX, ret+8(FP) 23 | RET 24 | -------------------------------------------------------------------------------- /atomics/asm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // https://github.com/golang/go/blob/master/src/math/sqrt.go#L92 8 | // https://github.com/golang/go/blob/master/src/math/sqrt_asm.go#L12 9 | // https://github.com/golang/go/blob/master/src/math/sqrt_amd64.s#L8 10 | // Sqrt implementation is inside sqrt_amd64.s 11 | func Sqrt(float64) float64 12 | 13 | // https://github.com/golang/go/blob/master/src/math/floor.go#L13 14 | // https://github.com/golang/go/blob/master/src/math/floor_asm.go#L12 15 | // https://github.com/golang/go/blob/master/src/math/floor_amd64.s#L10 16 | // Floor implementation is inside floor_amd64.s 17 | func Floor(float64) float64 18 | 19 | // Abs implementation is inside abs_amd64.s 20 | func Abs(float64) float64 21 | 22 | // ASMPrintResult implementation is inside print_amd64.s 23 | func ASMPrintResult(float64, float64, float64) 24 | 25 | func printResult(absRes, sqrtRes, floorRes float64) { 26 | fmt.Println("abs result", absRes) 27 | fmt.Println("sqrt result", sqrtRes) 28 | fmt.Println("floor result", floorRes) 29 | } 30 | 31 | // to run this example run the following commands: 32 | // go build -o exec 33 | // ./exec 34 | func main() { 35 | ASMPrintResult(Abs(-12), Sqrt(25), Floor(2.56)) 36 | } 37 | -------------------------------------------------------------------------------- /atomics/asm/print_amd64.s: -------------------------------------------------------------------------------- 1 | TEXT ·ASMPrintResult(SB),$0 2 | RET ·printResult(SB) 3 | -------------------------------------------------------------------------------- /atomics/asm/sqrt_amd64.s: -------------------------------------------------------------------------------- 1 | // func Sqrt(x float64) float64 2 | TEXT ·Sqrt(SB),$0 3 | XORPS X0, X0 4 | SQRTSD x+0(FP), X0 5 | MOVSD X0, ret+8(FP) 6 | RET 7 | -------------------------------------------------------------------------------- /atomics/atomic-implementation/atomic_amd64.s: -------------------------------------------------------------------------------- 1 | TEXT ·StoreInt64(SB),$0-16 2 | MOVQ ptr+0(FP), BX 3 | MOVQ val+8(FP), AX 4 | XCHGQ AX, 0(BX) 5 | RET 6 | -------------------------------------------------------------------------------- /atomics/atomic-implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // taken from the source code 9 | // LoadInt64 10 | // https://github.com/golang/go/blob/master/src/sync/atomic/doc.go#L114 11 | // https://github.com/golang/go/blob/master/src/sync/atomic/asm.s#L61 12 | // https://github.com/golang/go/blob/master/src/runtime/internal/atomic/atomic_amd64.go#L28 13 | 14 | // StoreInt64 15 | // https://github.com/golang/go/blob/master/src/sync/atomic/doc.go#L132 16 | // https://github.com/golang/go/blob/master/src/runtime/internal/atomic/atomic_amd64.go#L101 17 | // https://github.com/golang/go/blob/master/src/runtime/internal/atomic/atomic_amd64.s#L171 18 | func StoreInt64(addr *int64, value int64) 19 | 20 | // to test this example, make sure to build it using the -race flag 21 | // go build -race -o exec 22 | // ./exec 23 | func main() { 24 | var count int64 25 | var wg sync.WaitGroup 26 | 27 | wg.Add(100) 28 | for i := 0; i < 100; i++ { 29 | go func(i int) { 30 | defer wg.Done() 31 | StoreInt64(&count, int64(i+1)) 32 | }(i) 33 | } 34 | 35 | wg.Wait() 36 | fmt.Println("count", count) 37 | } 38 | -------------------------------------------------------------------------------- /atomics/basic-counter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | // to test for race condition, run this example with the -race flag 11 | // go run -race main.go 12 | func main() { 13 | var count int64 = 0 14 | var wg sync.WaitGroup 15 | 16 | wg.Add(1) 17 | go func() { 18 | defer wg.Done() 19 | time.Sleep(10 * time.Millisecond) 20 | fmt.Println("count in go routine", atomic.LoadInt64(&count)) 21 | }() 22 | 23 | wg.Add(50) 24 | for i := 0; i < 50; i++ { 25 | go func() { 26 | defer wg.Done() 27 | time.Sleep(10 * time.Millisecond) 28 | atomic.AddInt64(&count, 1) 29 | }() 30 | } 31 | wg.Wait() 32 | fmt.Println("count in main", count) 33 | } 34 | -------------------------------------------------------------------------------- /atomics/benchmarks/number_vs_value_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | ) 8 | 9 | // to run the benchmarks make sure to cd into "benchmarks" and run 10 | // go test -bench=. 11 | func BenchmarkStoreInt64(b *testing.B) { 12 | var wg sync.WaitGroup 13 | var count int64 14 | wg.Add(b.N) 15 | for i := 0; i < b.N; i++ { 16 | go func(i int) { 17 | defer wg.Done() 18 | atomic.StoreInt64(&count, int64(i)) 19 | }(i) 20 | } 21 | wg.Wait() 22 | } 23 | 24 | // to run the benchmarks make sure to cd into "benchmarks" and run 25 | // go test -bench=. 26 | func BenchmarkStoreValue(b *testing.B) { 27 | var wg sync.WaitGroup 28 | var v atomic.Value 29 | wg.Add(b.N) 30 | for i := 0; i < b.N; i++ { 31 | go func(i int) { 32 | defer wg.Done() 33 | // In a real world it would take even more time 34 | // because you have to do type conversion 35 | // from interface{} to whatever you think it's stored there 36 | // something like the following 37 | // _ = v.Load().(int64) 38 | v.Store(int64(i)) 39 | }(i) 40 | } 41 | wg.Wait() 42 | } 43 | -------------------------------------------------------------------------------- /atomics/race-fixed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | func main() { 10 | var count int32 11 | var wg sync.WaitGroup 12 | wg.Add(5) 13 | go func() { 14 | defer wg.Done() 15 | atomic.StoreInt32(&count, 10) 16 | }() 17 | go func() { 18 | defer wg.Done() 19 | atomic.StoreInt32(&count, -15) 20 | }() 21 | go func() { 22 | defer wg.Done() 23 | atomic.StoreInt32(&count, 1) 24 | }() 25 | go func() { 26 | defer wg.Done() 27 | atomic.StoreInt32(&count, 0) 28 | }() 29 | go func() { 30 | defer wg.Done() 31 | atomic.StoreInt32(&count, 100) 32 | }() 33 | wg.Wait() 34 | 35 | fmt.Println("count", count) 36 | } 37 | -------------------------------------------------------------------------------- /atomics/race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // to test for race condition, run this example with the -race flag 9 | // go run -race main.go 10 | func main() { 11 | var count int32 12 | var wg sync.WaitGroup 13 | wg.Add(5) 14 | go func() { 15 | defer wg.Done() 16 | count += 10 17 | }() 18 | go func() { 19 | defer wg.Done() 20 | count -= 15 21 | }() 22 | go func() { 23 | defer wg.Done() 24 | count++ 25 | }() 26 | go func() { 27 | defer wg.Done() 28 | count = 0 29 | }() 30 | go func() { 31 | defer wg.Done() 32 | count = 100 33 | }() 34 | wg.Wait() 35 | 36 | fmt.Println("count", count) 37 | } 38 | -------------------------------------------------------------------------------- /atomics/value/not-atomic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | type student struct { 10 | grades map[string]int 11 | } 12 | 13 | // to test this program, make sure to run it using the -race flag 14 | // go run -race main.go 15 | func main() { 16 | var wg sync.WaitGroup 17 | var val atomic.Value 18 | val.Store(student{grades: map[string]int{}}) 19 | 20 | wg.Add(3) 21 | go func() { 22 | defer wg.Done() 23 | s := val.Load().(student) 24 | m := s.grades 25 | m["English"] = 10 26 | val.Store(student{grades: m}) 27 | //s.grades["English"] = 10 28 | }() 29 | go func() { 30 | defer wg.Done() 31 | s := val.Load().(student) 32 | m := s.grades 33 | m["Math"] = 8 34 | val.Store(student{grades: m}) 35 | //s.grades["Math"] = 8 36 | }() 37 | go func() { 38 | defer wg.Done() 39 | s := val.Load().(student) 40 | m := s.grades 41 | m["Physics"] = 7 42 | val.Store(student{grades: m}) 43 | //s.grades["Physics"] = 7 44 | }() 45 | 46 | wg.Wait() 47 | s := val.Load().(student) 48 | fmt.Println(s) 49 | } 50 | -------------------------------------------------------------------------------- /atomics/value/panic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | func main() { 8 | var v atomic.Value 9 | // this will panic, saying we can't pass nil 10 | // v.Store(nil) 11 | v.Store(1) 12 | // this will panic, saying types must be consistent 13 | // v.Store("1") 14 | } 15 | -------------------------------------------------------------------------------- /atomics/value/reader-writer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | type Config struct { 11 | a []int 12 | } 13 | 14 | // to test this program, make sure to run it using the -race flag 15 | // go run -race main.go 16 | func main() { 17 | var wg sync.WaitGroup 18 | var v atomic.Value 19 | // to avoid panics when we do type assertion 20 | v.Store(Config{a: []int{}}) 21 | 22 | // writer 23 | go func() { 24 | var i int 25 | for { 26 | i++ 27 | cfg := Config{ 28 | a: []int{i + 1, i + 2, i + 3, i + 4, i + 5}, 29 | } 30 | v.Store(cfg) 31 | } 32 | }() 33 | 34 | // reader 35 | wg.Add(5) 36 | for i := 0; i < 5; i++ { 37 | go func() { 38 | defer wg.Done() 39 | // we're gonna get a panic this way 40 | // cfg := v.Load().(Config) 41 | cfg, ok := v.Load().(Config) 42 | if !ok { 43 | log.Fatalf("received different type: %T", cfg) 44 | } 45 | fmt.Println("cfg", cfg) 46 | }() 47 | } 48 | wg.Wait() 49 | } 50 | -------------------------------------------------------------------------------- /channels/buffered-vs-unbuffered/benchmarks/files_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | // to run the benchmark cd into "patterns/queuing/buffered-writing" and run: 14 | // go test -bench=. 15 | func BenchmarkUnbufferedWrite(b *testing.B) { 16 | b.ReportAllocs() 17 | write(b, newTmpFile()) 18 | } 19 | 20 | // to run the benchmark cd into "patterns/queuing/buffered-writing" and run: 21 | // go test -bench=. 22 | func BenchmarkBufferedWrite(b *testing.B) { 23 | b.ReportAllocs() 24 | write(b, bufio.NewWriter(newTmpFile())) 25 | } 26 | 27 | func newTmpFile() *os.File { 28 | file, err := ioutil.TempFile("", "tmp") 29 | if err != nil { 30 | log.Fatalf("could not create temporary file: %v", err) 31 | } 32 | return file 33 | } 34 | 35 | func write(b *testing.B, w io.Writer) { 36 | done := make(chan struct{}) 37 | defer close(done) 38 | 39 | b.ResetTimer() 40 | for i := 0; i < b.N; i++ { 41 | _, err := w.Write([]byte(strconv.Itoa(i))) 42 | if err != nil { 43 | log.Fatalf("could not write to file: %v", err) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /channels/buffered-vs-unbuffered/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | // an unbuffered channel is a buffered channel with length 0 10 | // writes to a channel block if the channel if full 11 | // reads from a channel block if the channel is empty 12 | fmt.Println("buffered") 13 | buffered(2) // go routines will not block 14 | fmt.Println("un-buffered") 15 | buffered(0) // go routines will block 16 | time.Sleep(time.Second) 17 | fmt.Println("DONE") 18 | } 19 | 20 | func buffered(n int) { 21 | c := make(chan int, n) 22 | go func() { 23 | c <- 1 24 | fmt.Println("go routine 1") 25 | // will return immediately if chan is buffered 26 | }() 27 | go func() { 28 | c <- 2 29 | fmt.Println("go routine 2") 30 | // will return immediately if chan is buffered 31 | }() 32 | time.Sleep(time.Second) 33 | fmt.Println(<-c) 34 | time.Sleep(time.Second) 35 | fmt.Println(<-c) 36 | } 37 | -------------------------------------------------------------------------------- /channels/buffered/range/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int, 1) 10 | go write(ch) 11 | 12 | time.Sleep(2 * time.Second) 13 | for v := range ch { 14 | time.Sleep(1 * time.Second) 15 | // channel read/write executes faster than writing to STD OUT 16 | fmt.Println("read:", v) 17 | } 18 | } 19 | 20 | func write(ch chan int) { 21 | for i := 1; i < 4; i++ { 22 | fmt.Println("writing:", i) 23 | fmt.Println("---") 24 | ch <- i 25 | } 26 | close(ch) 27 | } 28 | -------------------------------------------------------------------------------- /channels/buffered/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | numbers := make(chan int, 2) 10 | go func() { 11 | // does not block 12 | fmt.Println("writing 1") 13 | numbers <- 1 14 | // does not block 15 | fmt.Println("writing 2") 16 | numbers <- 2 17 | // blocks after writing 18 | fmt.Println("writing 3") 19 | numbers <- 3 20 | // waiting to be unblocked 21 | fmt.Println("writing 4") 22 | numbers <- 4 23 | }() 24 | 25 | // wait for all writes inside the go routine till it blocks 26 | time.Sleep(2 * time.Second) 27 | 28 | fmt.Println("reading 1:", <-numbers) // does not block 29 | fmt.Println("reading 2:", <-numbers) // does not block 30 | fmt.Println("reading 3:", <-numbers) // does not block 31 | fmt.Println("reading 4:", <-numbers) // blocks (waits for channel write) 32 | } 33 | -------------------------------------------------------------------------------- /channels/channel-length/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | c := make(chan int, 100) 7 | for i := 0; i < 20; i++ { 8 | c <- i 9 | } 10 | fmt.Println("channel capacity", cap(c)) 11 | fmt.Println("channel length", len(c)) 12 | } 13 | -------------------------------------------------------------------------------- /channels/channel-vs-atomics/channel_vs_atomics_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkChannel(b *testing.B) { 10 | b.ReportAllocs() 11 | var value int32 12 | setChan := make(chan set) 13 | 14 | go func() { 15 | for { 16 | select { 17 | case op := <-setChan: 18 | value = op.value 19 | op.done <- struct{}{} 20 | } 21 | } 22 | }() 23 | var wg sync.WaitGroup 24 | for i := 0; i < b.N; i++ { 25 | wg.Add(1) 26 | go func(i int32) { 27 | defer wg.Done() 28 | op := set{ 29 | value: i, 30 | done: make(chan struct{}), 31 | } 32 | setChan <- op 33 | <-op.done 34 | }(int32(i)) 35 | } 36 | 37 | wg.Wait() 38 | // to avoid compile errors 39 | value = value 40 | } 41 | 42 | func BenchmarkAtomics(b *testing.B) { 43 | b.ReportAllocs() 44 | var value int32 45 | var wg sync.WaitGroup 46 | for i := 0; i < b.N; i++ { 47 | wg.Add(1) 48 | go func(i int32) { 49 | defer wg.Done() 50 | atomic.StoreInt32(&value, i) 51 | }(int32(i)) 52 | } 53 | wg.Wait() 54 | } 55 | -------------------------------------------------------------------------------- /channels/channel-vs-atomics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type set struct { 8 | value int32 9 | done chan struct{} 10 | } 11 | 12 | type get struct { 13 | value chan int32 14 | } 15 | 16 | // try running the example like this: go run -race main.go 17 | func main() { 18 | var value int32 19 | setChan := make(chan set) 20 | getChan := make(chan get) 21 | 22 | go func() { 23 | for { 24 | select { 25 | case op := <-getChan: 26 | op.value <- value 27 | case op := <-setChan: 28 | value = op.value 29 | op.done <- struct{}{} 30 | } 31 | } 32 | }() 33 | for i := 0; i < 1000; i++ { 34 | go func(i int32) { 35 | op := set{ 36 | value: i, 37 | done: make(chan struct{}), 38 | } 39 | setChan <- op 40 | <-op.done 41 | }(int32(i)) 42 | } 43 | 44 | op := get{value: make(chan int32)} 45 | getChan <- op 46 | fmt.Println("value:", <-op.value) 47 | } 48 | -------------------------------------------------------------------------------- /channels/channel-vs-mutex/critical/channel_cache_test.go: -------------------------------------------------------------------------------- 1 | package critical 2 | 3 | type set struct { 4 | key string 5 | value string 6 | done chan struct{} 7 | } 8 | 9 | type channelCache struct { 10 | cache map[string]string 11 | setChan chan set 12 | } 13 | 14 | func newChannelCache() *channelCache { 15 | cache := &channelCache{ 16 | cache: map[string]string{}, 17 | setChan: make(chan set), 18 | } 19 | go func() { 20 | for { 21 | select { 22 | case op := <-cache.setChan: 23 | cache.cache[op.key] = op.value 24 | op.done <- struct{}{} 25 | } 26 | } 27 | }() 28 | return cache 29 | } 30 | 31 | func (c channelCache) set(key, value string) { 32 | op := set{ 33 | key: key, 34 | value: value, 35 | done: make(chan struct{}), 36 | } 37 | c.setChan <- op 38 | <-op.done 39 | } 40 | -------------------------------------------------------------------------------- /channels/channel-vs-mutex/critical/critical_test.go: -------------------------------------------------------------------------------- 1 | package critical 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | // to run the benchmarks, cd into "critical" directory and run: 9 | // go test -bench=. 10 | func BenchmarkCriticalMutexCache(b *testing.B) { 11 | cache := newMutexCache() 12 | b.ReportAllocs() 13 | for i := 0; i < b.N; i++ { 14 | go cache.set("key", "value"+strconv.Itoa(i)) 15 | } 16 | } 17 | 18 | // to run the benchmarks, cd into "critical" directory and run: 19 | // go test -bench=. 20 | func BenchmarkCriticalChannelCache(b *testing.B) { 21 | cache := newChannelCache() 22 | b.ReportAllocs() 23 | for i := 0; i < b.N; i++ { 24 | go cache.set("key", "value"+strconv.Itoa(i)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /channels/channel-vs-mutex/critical/mutex_cache_test.go: -------------------------------------------------------------------------------- 1 | package critical 2 | 3 | import "sync" 4 | 5 | type mutexCache struct { 6 | cache map[string]string 7 | mu sync.Mutex 8 | } 9 | 10 | func newMutexCache() *mutexCache { 11 | return &mutexCache{ 12 | cache: map[string]string{}, 13 | } 14 | } 15 | 16 | func (c *mutexCache) set(key, value string) { 17 | c.mu.Lock() 18 | defer c.mu.Unlock() 19 | c.cache[key] = value 20 | } 21 | -------------------------------------------------------------------------------- /channels/channel-vs-mutex/mutex-cache/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // try running the example like: go run -race main.go 9 | func main() { 10 | c := newMutexCache() 11 | var wg sync.WaitGroup 12 | 13 | wg.Add(5) 14 | go func() { 15 | c.set("k", "v1") 16 | wg.Done() 17 | }() 18 | go func() { 19 | c.set("k", "v2") 20 | wg.Done() 21 | }() 22 | go func() { 23 | c.set("k", "v3") 24 | wg.Done() 25 | }() 26 | go func() { 27 | c.set("k", "v4") 28 | wg.Done() 29 | }() 30 | go func() { 31 | fmt.Println(c.get("k")) 32 | wg.Done() 33 | }() 34 | wg.Wait() 35 | c.set("kn", "vn") 36 | 37 | fmt.Println(c.get("k")) 38 | fmt.Println(c.get("kn")) 39 | } 40 | 41 | type mutexCache struct { 42 | cache map[string]string 43 | mu sync.RWMutex 44 | } 45 | 46 | func newMutexCache() mutexCache { 47 | return mutexCache{ 48 | cache: map[string]string{}, 49 | } 50 | } 51 | 52 | func (c *mutexCache) set(key, value string) { 53 | c.mu.Lock() 54 | defer c.mu.Unlock() 55 | c.cache[key] = value 56 | } 57 | 58 | func (c *mutexCache) get(key string) string { 59 | c.mu.RLock() 60 | defer c.mu.RUnlock() 61 | return c.cache[key] 62 | } 63 | -------------------------------------------------------------------------------- /channels/channel-vs-waitgroup/channel_vs_waitgroup_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // to run the benchmarks, cd into "channel-vs-waitgroup" directory and run: 10 | // go test -bench=. 11 | func BenchmarkWaitGroup(b *testing.B) { 12 | b.ReportAllocs() 13 | var wg sync.WaitGroup 14 | for i := 0; i < b.N; i++ { 15 | wg.Add(1) 16 | go func() { 17 | time.Sleep(2 * time.Nanosecond) 18 | wg.Done() 19 | }() 20 | } 21 | wg.Wait() 22 | } 23 | 24 | // to run the benchmarks, cd into "channel-vs-waitgroup" directory and run: 25 | // go test -bench=. 26 | func BenchmarkChannel(b *testing.B) { 27 | b.ReportAllocs() 28 | done := make(chan struct{}) 29 | for i := 0; i < b.N; i++ { 30 | go func() { 31 | time.Sleep(2 * time.Nanosecond) 32 | done <- struct{}{} 33 | }() 34 | } 35 | for i := 0; i < b.N; i++ { 36 | <-done 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /channels/channel-vs-waitgroup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | doneChan() 10 | doneWaitGroup() 11 | } 12 | 13 | func doneChan() { 14 | done := make(chan struct{}) 15 | go func() { 16 | fmt.Println("chan: go routine 1 is done") 17 | done <- struct{}{} 18 | }() 19 | go func() { 20 | fmt.Println("chan: go routine 2 is done") 21 | done <- struct{}{} 22 | }() 23 | for i := 0; i < 2; i++ { 24 | <-done 25 | } 26 | fmt.Println("chan: all go routines are done") 27 | } 28 | 29 | func doneWaitGroup() { 30 | var wg sync.WaitGroup 31 | wg.Add(2) 32 | go func() { 33 | fmt.Println("wait group: go routine 1 is done") 34 | wg.Done() 35 | }() 36 | go func() { 37 | fmt.Println("wait group: go routine 2 is done") 38 | wg.Done() 39 | }() 40 | wg.Wait() 41 | fmt.Println("wait group: all go routines are done") 42 | } 43 | -------------------------------------------------------------------------------- /channels/closed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | c := make(chan int) 7 | close(c) 8 | // this gets ignored 9 | <-c 10 | // this gets ignored as well 11 | for _ = range c { 12 | fmt.Println("I won't print") 13 | } 14 | // this results in an error 15 | c <- 1 16 | } 17 | -------------------------------------------------------------------------------- /channels/comma-ok/select/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | c := make(chan int) 11 | var wg sync.WaitGroup 12 | wg.Add(1) 13 | go func() { 14 | timeout := time.NewTimer(2 * time.Second) 15 | for { 16 | select { 17 | case <-timeout.C: 18 | fmt.Println("timeout") 19 | wg.Done() 20 | return 21 | case v, ok := <-c: 22 | if ok { 23 | fmt.Println("value:", v) 24 | } else { 25 | wg.Done() 26 | return 27 | } 28 | } 29 | } 30 | }() 31 | c <- 1 32 | time.Sleep(500 * time.Millisecond) 33 | close(c) 34 | wg.Wait() 35 | } 36 | -------------------------------------------------------------------------------- /channels/comma-ok/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan bool, 1) 7 | ch <- true 8 | val, ok := <-ch 9 | fmt.Println("val", val) 10 | fmt.Println("ok", ok) 11 | 12 | close(ch) 13 | val, ok = <-ch 14 | fmt.Println("val", val) 15 | fmt.Println("ok", ok) 16 | } 17 | -------------------------------------------------------------------------------- /channels/conversion/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | var receiveChan <-chan int 5 | var sendChan chan<- int 6 | intChan := make(chan int) 7 | 8 | // 2 directional channel gets converted into 1 directional channel 9 | receiveChan = intChan 10 | sendChan = intChan 11 | 12 | // to avoid compilation errors 13 | receiveChan = receiveChan 14 | sendChan = sendChan 15 | } 16 | -------------------------------------------------------------------------------- /channels/deadlock/nobody-reads/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | ch := make(chan int) 5 | ch <- 1 6 | } 7 | -------------------------------------------------------------------------------- /channels/deadlock/nobody-writes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | ch := make(chan int) 5 | <-ch 6 | } 7 | -------------------------------------------------------------------------------- /channels/deadlock/unclosed-channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int) 7 | for v := range ch { 8 | fmt.Println(v) 9 | } 10 | } 11 | 12 | func write(ch chan int) { 13 | ch <- 1 14 | ch <- 2 15 | ch <- 3 16 | // close(ch) must be called 17 | } 18 | -------------------------------------------------------------------------------- /channels/deadlock/write-on-closed-channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int) 7 | close(ch) 8 | <-ch 9 | <-ch 10 | fmt.Println("reads on closed channel are ignored") 11 | // results in panic 12 | ch <- 1 13 | } 14 | -------------------------------------------------------------------------------- /channels/empty-struct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | ) 7 | 8 | func main() { 9 | emptyStructChan := make(chan struct{}) 10 | intChan := make(chan int) 11 | go func() { 12 | emptyStructChan <- struct{}{} 13 | intChan <- 1 14 | }() 15 | fmt.Println(unsafe.Sizeof(<-emptyStructChan)) // 0 16 | fmt.Println(unsafe.Sizeof(<-intChan)) // 8 => 8*8=64 => int64 17 | } 18 | -------------------------------------------------------------------------------- /channels/fifo-queue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | queue := make(chan string, 2) 10 | people := []string{"John", "Jane", "Mike", "Steve", "Alex"} 11 | 12 | // people come and wait to be served 13 | go func() { 14 | defer close(queue) 15 | for _, p := range people { 16 | time.Sleep(time.Second) 17 | fmt.Println(p, "starts waiting") 18 | queue <- p 19 | } 20 | }() 21 | 22 | // the restaurant is trying to serve people waiting in the queue 23 | // the maximum capacity of serving people is 2 24 | // the restaurant serves people slower, then they arrive in the queue 25 | for person := range queue { 26 | time.Sleep(3 * time.Second) 27 | fmt.Println("serving:", person) 28 | } 29 | time.Sleep(time.Second) 30 | fmt.Println("closing the restaurant") 31 | } 32 | -------------------------------------------------------------------------------- /channels/invalid-read-write/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | writeStream := make(chan<- int) 5 | readStream := make(<-chan int) 6 | 7 | // both statement above will result in compilation errors 8 | //<-writeStream 9 | //readStream<- 1 10 | 11 | // to avoid compilation errors 12 | writeStream = writeStream 13 | readStream = readStream 14 | } 15 | -------------------------------------------------------------------------------- /channels/nil/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var c chan int 7 | // results in deadlock (blocks forever) 8 | <-c 9 | // results in deadlock (blocks forever) 10 | for _ = range c { 11 | fmt.Println("I won't print") 12 | } 13 | // results in deadlock (blocks forever) 14 | c <- 1 15 | // panics: close on nil channel 16 | close(c) 17 | } 18 | -------------------------------------------------------------------------------- /channels/range/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int) 7 | go write(ch) 8 | // range expects the producer 9 | // to close the outbound channel 10 | // otherwise it will result in deadlock 11 | // Note: the for loop does not 12 | // need an exit condition for channels 13 | // it just exits when the channel is closed 14 | for i := range ch { 15 | fmt.Println(i) 16 | } 17 | } 18 | 19 | func write(ch chan int) { 20 | for i := 0; i < 3; i++ { 21 | ch <- i 22 | } 23 | close(ch) 24 | } 25 | -------------------------------------------------------------------------------- /channels/read-from-closed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | intChan := make(chan int) 7 | close(intChan) 8 | 9 | value, ok := <-intChan 10 | fmt.Println("value:", value) 11 | fmt.Println("ok:", ok) 12 | 13 | value, ok = <-intChan 14 | fmt.Println("value:", value) 15 | fmt.Println("ok:", ok) 16 | 17 | value, ok = <-intChan 18 | fmt.Println("value:", value) 19 | fmt.Println("ok:", ok) 20 | } 21 | -------------------------------------------------------------------------------- /channels/read-only/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int) 10 | go read(ch) 11 | ch <- 1 12 | ch <- 2 13 | ch <- 3 14 | time.Sleep(time.Second) 15 | } 16 | 17 | func read(ch <-chan int) { 18 | for v := range ch { 19 | fmt.Println(v) 20 | // ch<-1 it result in a compilation error: send to receive only chan 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /channels/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | c := make(chan bool) 9 | go task1(c) 10 | <-c 11 | go task2(c) 12 | <-c 13 | } 14 | 15 | func task1(c chan bool) { 16 | fmt.Println("task 1") 17 | c <- true 18 | } 19 | 20 | func task2(c chan bool) { 21 | fmt.Println("task 2") 22 | c <- false 23 | } 24 | -------------------------------------------------------------------------------- /channels/timers-and-tickers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ticker := time.NewTicker(100 * time.Millisecond) 10 | timer := time.NewTimer(3 * time.Second) 11 | 12 | for { 13 | select { 14 | case t := <-ticker.C: 15 | // usually check for something once in a while 16 | // or try an action till it succeeds or the select times out 17 | fmt.Println("tick", t) 18 | case <-timer.C: 19 | fmt.Println("time is up") 20 | return 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /channels/unblock-goroutines/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | begin := make(chan struct{}) 12 | for i := 0; i < 5; i++ { 13 | wg.Add(1) 14 | go func(i int) { 15 | defer wg.Done() 16 | <-begin 17 | fmt.Println("go routine", i) 18 | }(i + 1) 19 | } 20 | 21 | fmt.Println("unblocking 1 go routine") 22 | begin <- struct{}{} 23 | 24 | time.Sleep(time.Second) 25 | fmt.Println("unblocking the rest of the go routines") 26 | close(begin) 27 | wg.Wait() 28 | } 29 | -------------------------------------------------------------------------------- /channels/write-only/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int) 7 | go write(ch) 8 | for v := range ch { 9 | fmt.Println(v) 10 | } 11 | } 12 | 13 | func write(ch chan<- int) { 14 | for i := 0; i < 3; i++ { 15 | ch <- i 16 | } 17 | //<-ch will result in a compilation error: receive from send-only chan 18 | close(ch) 19 | } 20 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/cover.jpg -------------------------------------------------------------------------------- /go-routines/anonymous/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | go worker() 10 | go func() { 11 | fmt.Println("print in anonymous function") 12 | }() 13 | time.Sleep(time.Second) 14 | } 15 | 16 | func worker() { 17 | fmt.Println("print in worker function") 18 | } 19 | -------------------------------------------------------------------------------- /go-routines/async-preemption/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "runtime" 4 | 5 | // Asynchronous Preemption Go >= 1.14 6 | func main() { 7 | runtime.GOMAXPROCS(1) 8 | go println("goroutine ran") 9 | for { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /go-routines/busy-vs-responsible/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | c := make(chan int) 11 | //go busy(c) 12 | go responsible(c) 13 | c <- 1 14 | close(c) 15 | time.Sleep(time.Second) 16 | fmt.Println("number of go routines", runtime.NumGoroutine()) 17 | } 18 | 19 | func busy(in chan int) { 20 | defer fmt.Println("I am never done") 21 | for { 22 | select { 23 | case v := <-in: 24 | fmt.Println("value:", v) 25 | } 26 | } 27 | } 28 | 29 | func responsible(in chan int) { 30 | defer fmt.Println("I am responsibly done") 31 | for { 32 | select { 33 | case v, ok := <-in: 34 | if !ok { 35 | return 36 | } 37 | fmt.Println("value:", v) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /go-routines/closures/anonymous-vs-named/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | namedClosures() 10 | anonymousClosures() 11 | } 12 | 13 | func namedClosures() { 14 | var wg sync.WaitGroup 15 | f := func(n int) { 16 | defer wg.Done() 17 | fmt.Println("printing from named closure", n) 18 | } 19 | for i := 0; i < 10; i++ { 20 | wg.Add(1) 21 | go f(i) 22 | } 23 | wg.Wait() 24 | } 25 | 26 | func anonymousClosures() { 27 | var wg sync.WaitGroup 28 | for i := 0; i < 10; i++ { 29 | wg.Add(1) 30 | go func(n int) { 31 | defer wg.Done() 32 | fmt.Println("printing from anonymoys closure", n) 33 | }(i) 34 | } 35 | wg.Wait() 36 | } 37 | -------------------------------------------------------------------------------- /go-routines/closures/loops/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type request func() 9 | 10 | func main() { 11 | requests := make([]request, 0) 12 | for i := 1; i <= 100; i++ { 13 | f := func() { 14 | time.Sleep(200 * time.Millisecond) 15 | // the value will be accessed from heap 16 | // if it has not been evaluated and the function returned 17 | fmt.Println("request", i) 18 | } 19 | requests = append(requests, f) 20 | } 21 | 22 | for _, r := range requests { 23 | r() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /go-routines/consecutive-order-channels/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | done := make(chan struct{}) 10 | go task1(done) 11 | <-done 12 | go task2(done) 13 | <-done 14 | go task3(done) 15 | <-done 16 | } 17 | 18 | func task1(done chan struct{}) { 19 | time.Sleep(100 * time.Millisecond) 20 | fmt.Println("task 1") 21 | done <- struct{}{} 22 | } 23 | 24 | func task2(done chan struct{}) { 25 | time.Sleep(50 * time.Millisecond) 26 | fmt.Println("task 2") 27 | done <- struct{}{} 28 | } 29 | 30 | func task3(done chan struct{}) { 31 | time.Sleep(10 * time.Millisecond) 32 | fmt.Println("task 3") 33 | done <- struct{}{} 34 | } 35 | -------------------------------------------------------------------------------- /go-routines/consecutive-order-waitgroups/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | var wg sync.WaitGroup 10 | 11 | func main() { 12 | wg.Add(1) 13 | go task1() 14 | wg.Wait() 15 | 16 | wg.Add(1) 17 | go task2() 18 | wg.Wait() 19 | 20 | wg.Add(1) 21 | go task3() 22 | wg.Wait() 23 | } 24 | 25 | func task1() { 26 | time.Sleep(100 * time.Millisecond) 27 | fmt.Println("task 1") 28 | wg.Done() 29 | } 30 | 31 | func task2() { 32 | time.Sleep(50 * time.Millisecond) 33 | fmt.Println("task 2") 34 | wg.Done() 35 | } 36 | 37 | func task3() { 38 | time.Sleep(10 * time.Millisecond) 39 | fmt.Println("task 3") 40 | wg.Done() 41 | } 42 | -------------------------------------------------------------------------------- /go-routines/ctxswitch/ctxswitch_test.go: -------------------------------------------------------------------------------- 1 | package ctxswitch 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkContextSwitch(b *testing.B) { 9 | var wg sync.WaitGroup 10 | begin := make(chan struct{}) 11 | c := make(chan struct{}) 12 | sender := func() { 13 | defer wg.Done() 14 | for i := 0; i < b.N; i++ { 15 | c <- struct{}{} 16 | } 17 | } 18 | receiver := func() { 19 | defer wg.Done() 20 | <-begin 21 | for i := 0; i < b.N; i++ { 22 | <-c 23 | } 24 | } 25 | 26 | wg.Add(2) 27 | go sender() 28 | go receiver() 29 | 30 | b.StartTimer() 31 | close(begin) 32 | 33 | wg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /go-routines/goexit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | // calling runtime.Goexit inside main will crash the program if 10 | // no go routines are running, resulting in a deadlock 11 | func main() { 12 | var wg sync.WaitGroup 13 | wg.Add(1) 14 | go func() { 15 | defer wg.Done() 16 | defer fmt.Println("go routine exited") 17 | var i int 18 | for { 19 | if i == 100 { 20 | runtime.Goexit() 21 | return 22 | } 23 | i++ 24 | } 25 | }() 26 | wg.Wait() 27 | } 28 | -------------------------------------------------------------------------------- /go-routines/gomaxprocs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | runtime.GOMAXPROCS(1) 11 | go task1() 12 | go task2() 13 | go task3() 14 | time.Sleep(time.Second) 15 | //for{} 16 | } 17 | 18 | func task1() { 19 | fmt.Println("task 1") 20 | } 21 | 22 | func task2() { 23 | fmt.Println("task 2") 24 | } 25 | 26 | func task3() { 27 | fmt.Println("task 3") 28 | } 29 | -------------------------------------------------------------------------------- /go-routines/gosched/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | // try running the example on Go version < 1.14 9 | func main() { 10 | runtime.GOMAXPROCS(1) 11 | go fmt.Println("I try to print") 12 | // enable below to statement to allow main go routine 13 | // to be preempted so that other go routines can take execution 14 | // runtime.Gosched() 15 | for { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /go-routines/hanging/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | go func() { 11 | // will hang forever 12 | }() 13 | // always at least 1 go routine for main 14 | fmt.Println("number of go routines", runtime.NumGoroutine()) 15 | time.Sleep(time.Second) 16 | fmt.Println("main is done") 17 | } 18 | -------------------------------------------------------------------------------- /go-routines/io/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // go build 12 | // GOMAXPROCS=1 GOGC=off GODEBUG=schedtrace=1000,scheddetail=1 ./io 13 | func main() { 14 | // keep in mind the values from: 15 | // P0: schedtick 16 | // P0: syscalltick 17 | // https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L608 18 | // https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L609 19 | var wg sync.WaitGroup 20 | wg.Add(3) 21 | go func() { 22 | defer wg.Done() 23 | time.Sleep(1 * time.Second) 24 | fmt.Println("go routine 1: done") 25 | }() 26 | go func() { 27 | defer wg.Done() 28 | time.Sleep(2 * time.Second) 29 | fmt.Println("go routine 2: done") 30 | }() 31 | go func() { 32 | defer wg.Done() 33 | file, _ := os.CreateTemp("", "test.txt") 34 | // 1GB write 35 | _, _ = file.Write([]byte(strings.Repeat("a", 1_000_000_000))) 36 | fmt.Println("done writing") 37 | }() 38 | wg.Wait() 39 | 40 | // wait for one more Tracing event 41 | time.Sleep(2 * time.Second) 42 | } 43 | -------------------------------------------------------------------------------- /go-routines/leaking/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | // usually 1 go routine goes for main 11 | // it automatically runs in its own go routine 12 | fmt.Println("go routines before work:", runtime.NumGoroutine()) 13 | go work(nil) 14 | timer := time.NewTimer(5 * time.Second) 15 | for { 16 | select { 17 | case <-timer.C: 18 | fmt.Println("exiting") 19 | return 20 | default: 21 | } 22 | time.Sleep(time.Second) 23 | fmt.Println("number of go routines:", runtime.NumGoroutine()) 24 | } 25 | } 26 | 27 | func work(strings <-chan string) { 28 | defer fmt.Println("work is done") 29 | for s := range strings { 30 | fmt.Println(s) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /go-routines/memory-stress/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | done := make(chan struct{}) 5 | go func() { 6 | // memory allocations like this are pretty dangerous 7 | memory := make([]string, 0) 8 | for { 9 | memory = append(memory, "aaa") 10 | } 11 | }() 12 | 13 | <-done 14 | } 15 | -------------------------------------------------------------------------------- /go-routines/nested/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var mainWG sync.WaitGroup 10 | mainWG.Add(1) 11 | go func() { 12 | defer mainWG.Done() 13 | var g1WG sync.WaitGroup 14 | g1WG.Add(1) 15 | 16 | go func() { 17 | defer g1WG.Done() 18 | var g2WG sync.WaitGroup 19 | g2WG.Add(1) 20 | 21 | go func() { 22 | defer g2WG.Done() 23 | var g3WG sync.WaitGroup 24 | g3WG.Add(1) 25 | 26 | go func() { 27 | defer g3WG.Done() 28 | var g4WG sync.WaitGroup 29 | g4WG.Add(2) 30 | 31 | go func() { 32 | fmt.Println("g5 done") 33 | g4WG.Done() 34 | }() 35 | go func() { 36 | fmt.Println("g6 done") 37 | g4WG.Done() 38 | }() 39 | 40 | g4WG.Wait() 41 | fmt.Println("g4 done") 42 | }() 43 | 44 | g3WG.Wait() 45 | fmt.Println("g3 done") 46 | }() 47 | 48 | g2WG.Wait() 49 | fmt.Println("g2 done") 50 | }() 51 | 52 | g1WG.Wait() 53 | fmt.Println("g1 done") 54 | }() 55 | mainWG.Wait() 56 | fmt.Println("main g done") 57 | } 58 | -------------------------------------------------------------------------------- /go-routines/netpoller/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "runtime" 7 | ) 8 | 9 | func main() { 10 | runtime.GOMAXPROCS(1) 11 | go fmt.Println("I am running in different thread") 12 | // runs in net poller thread 13 | _, _ = http.Get("https://www.google.com") 14 | } 15 | -------------------------------------------------------------------------------- /go-routines/no-leaks-done-channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("go routines before work:", runtime.NumGoroutine()) 11 | // conventionally it's called done to signal cancellation of processes 12 | // also conventionally it's a chan os struct{}, we just need to signal 13 | // we don't need to pass any kind of memory around go routines 14 | // also a a convention the done channel is the 1st param, but it does not have to be 15 | done := make(chan struct{}) 16 | go work(done, nil) 17 | go func() { 18 | time.Sleep(time.Second) 19 | fmt.Println("cancelling all go routines, listening on done") 20 | close(done) 21 | }() 22 | fmt.Println("number of go routines while working:", runtime.NumGoroutine()) 23 | 24 | time.Sleep(2 * time.Second) 25 | fmt.Println("number of go routines after closing done:", runtime.NumGoroutine()) 26 | } 27 | 28 | func work(done chan struct{}, strings <-chan string) { 29 | defer fmt.Println("work is done") 30 | for { 31 | select { 32 | case s := <-strings: 33 | fmt.Println(s) 34 | case <-done: 35 | return 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /go-routines/panic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // go routine panicking 5 | } 6 | -------------------------------------------------------------------------------- /go-routines/preemptive-vs-nonpreemptive/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | done := make(chan struct{}) 11 | time.AfterFunc(time.Second, func() { 12 | close(done) 13 | }) 14 | 15 | var wg sync.WaitGroup 16 | wg.Add(2) 17 | go func() { 18 | defer wg.Done() 19 | defer fmt.Println("preemptive go routine done") 20 | 21 | select { 22 | case <-done: 23 | return 24 | case <-time.After(5 * time.Second): 25 | } 26 | 27 | fmt.Println("work that is cancelled") 28 | }() 29 | go func() { 30 | defer wg.Done() 31 | defer fmt.Println("non-preemptive go routine done") 32 | 33 | select { 34 | case <-time.After(5 * time.Second): 35 | } 36 | 37 | fmt.Println("work that is not cancelled") 38 | }() 39 | 40 | wg.Wait() 41 | } 42 | -------------------------------------------------------------------------------- /go-routines/pure-concurrency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | var wg sync.WaitGroup 11 | 12 | func main() { 13 | runtime.GOMAXPROCS(1) 14 | 15 | wg.Add(3) 16 | now := time.Now() 17 | go func() { 18 | task1() 19 | wg.Done() 20 | }() 21 | go func() { 22 | task2() 23 | wg.Done() 24 | }() 25 | go func() { 26 | task3() 27 | wg.Done() 28 | }() 29 | wg.Wait() 30 | fmt.Println("elapsed", time.Now().Sub(now)) 31 | 32 | fmt.Printf("\n\n") 33 | 34 | now = time.Now() 35 | task1() 36 | task2() 37 | task3() 38 | fmt.Println("elapsed", time.Now().Sub(now)) 39 | } 40 | 41 | func task1() { 42 | fmt.Println("before task 1") 43 | time.Sleep(1 * time.Second) 44 | fmt.Println("after task 1") 45 | } 46 | 47 | func task2() { 48 | fmt.Println("before task 2") 49 | time.Sleep(3 * time.Second) 50 | fmt.Println("after task 2") 51 | } 52 | 53 | func task3() { 54 | fmt.Println("before task 3") 55 | time.Sleep(2 * time.Second) 56 | fmt.Println("after task 3") 57 | } 58 | -------------------------------------------------------------------------------- /go-routines/race-condition/README.md: -------------------------------------------------------------------------------- 1 | # Race Conditions 2 | 3 | [Home](https://github.com/golang-basics/concurrency) 4 | -------------------------------------------------------------------------------- /go-routines/race-condition/race-miss/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | // Try and run this program using the -race flag 10 | // it will not display any race condition 11 | // even if the code has a race condition. 12 | // The race detector only works on running code. 13 | // --------------------------------------------- 14 | // The race condition will get detected only when 15 | // the -inc and -dec flags are provided like so: 16 | // go run -race -inc -dec 17 | func main() { 18 | count := 0 19 | incrementFlag := flag.Bool("inc", false, "increment counter") 20 | decrementFlag := flag.Bool("dec", false, "decrement counter") 21 | flag.Parse() 22 | 23 | var wg sync.WaitGroup 24 | wg.Add(2) 25 | go func() { 26 | defer wg.Done() 27 | if *incrementFlag { 28 | count++ 29 | } 30 | }() 31 | go func() { 32 | defer wg.Done() 33 | if *decrementFlag { 34 | count-- 35 | } 36 | }() 37 | wg.Wait() 38 | fmt.Println(count) 39 | } 40 | -------------------------------------------------------------------------------- /go-routines/race-condition/read-write/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // to check for race conditions use the -race flag 9 | // go run -race main.go 10 | func main() { 11 | value := 10 12 | 13 | var wg sync.WaitGroup 14 | wg.Add(3) 15 | go func() { 16 | defer wg.Done() 17 | fmt.Println("reading value:", value) 18 | }() 19 | go func() { 20 | defer wg.Done() 21 | fmt.Println("overwriting value to 15") 22 | value = 15 23 | }() 24 | go func() { 25 | defer wg.Done() 26 | fmt.Println() 27 | fmt.Println("reading then overwriting value to 20") 28 | fmt.Println("reading value before overwriting", value) 29 | value = 20 30 | }() 31 | wg.Wait() 32 | 33 | fmt.Println("value in main:", value) 34 | } 35 | -------------------------------------------------------------------------------- /go-routines/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | go longTask() 10 | immediateTask() 11 | time.Sleep(time.Second) 12 | } 13 | 14 | func immediateTask() { 15 | fmt.Println("I executed immediately") 16 | } 17 | 18 | func longTask() { 19 | time.Sleep(500 * time.Millisecond) 20 | fmt.Println("I executed after 500ms") 21 | } 22 | -------------------------------------------------------------------------------- /go-routines/stack-overflow/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func f() { 8 | f() 9 | } 10 | 11 | func main() { 12 | go f() 13 | time.Sleep(10 * time.Second) 14 | } 15 | -------------------------------------------------------------------------------- /go-routines/stack-size/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | var c <-chan struct{} 12 | noop := func() { 13 | wg.Done() 14 | <-c 15 | } 16 | const numGoRoutines = 1e4 17 | wg.Add(numGoRoutines) 18 | before := stackSize() 19 | for i := 0; i < numGoRoutines; i++ { 20 | go noop() 21 | } 22 | wg.Wait() 23 | after := stackSize() 24 | fmt.Printf("%.3fkb", float64(after-before)/numGoRoutines/1024) 25 | } 26 | 27 | func stackSize() uint64 { 28 | runtime.GC() 29 | var m runtime.MemStats 30 | runtime.ReadMemStats(&m) 31 | return m.StackSys 32 | } 33 | -------------------------------------------------------------------------------- /go-routines/tracing/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime/trace" 7 | "sync" 8 | ) 9 | 10 | // view trace after running the program by running the command 11 | // go tool trace trace.out 12 | func main() { 13 | f, err := os.Create("trace.out") 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer f.Close() 18 | err = trace.Start(f) 19 | if err != nil { 20 | panic(err) 21 | } 22 | defer trace.Stop() 23 | 24 | var wg sync.WaitGroup 25 | for i := 0; i < 30; i++ { 26 | wg.Add(1) 27 | go func() { 28 | t := 0 29 | for i := 0; i < 100; i++ { 30 | t += 2 31 | } 32 | fmt.Println("total:", t) 33 | wg.Done() 34 | }() 35 | } 36 | wg.Wait() 37 | } 38 | -------------------------------------------------------------------------------- /go-routines/tracing/http-calls/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // To enable tracing on this program make sure to run the below commands 10 | // go build main.go 11 | // GOMAXPROCS=2 GODEBUG=schedtrace=1000,scheddetail=1 ./main 12 | func main() { 13 | var wg sync.WaitGroup 14 | for i := 0; i < 5; i++ { 15 | wg.Add(1) 16 | go func() { 17 | _, _ = http.Get("https://www.google.com") 18 | wg.Done() 19 | }() 20 | } 21 | wg.Wait() 22 | 23 | // wait for Global Run Queue 24 | time.Sleep(3 * time.Second) 25 | } 26 | -------------------------------------------------------------------------------- /go-routines/tracing/io-operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // To enable tracing on this program make sure to run the below commands 11 | // go build main.go 12 | // GOMAXPROCS=2 GODEBUG=schedtrace=1000,scheddetail=1 ./main 13 | func main() { 14 | var wg sync.WaitGroup 15 | file, _ := os.Create("tmp.txt") 16 | defer file.Close() 17 | for i := 0; i < 5; i++ { 18 | wg.Add(1) 19 | go func(n int) { 20 | _, err := file.WriteString(strconv.Itoa(n)) 21 | if err != nil { 22 | panic(err) 23 | } 24 | time.Sleep(time.Second) 25 | wg.Done() 26 | }(i) 27 | } 28 | wg.Wait() 29 | 30 | // wait for Global Run Queue 31 | time.Sleep(3 * time.Second) 32 | } 33 | -------------------------------------------------------------------------------- /go-routines/tracing/max-lrq-capacity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // To enable tracing on this program make sure to run the below commands 9 | // go build main.go 10 | // GOMAXPROCS=1 GODEBUG=schedtrace=1000 ./main 11 | // GOMAXPROCS=2 GODEBUG=schedtrace=1000 ./main 12 | // GOMAXPROCS=2 GODEBUG=schedtrace=1000,scheddetail=1 ./main 13 | func main() { 14 | var wg sync.WaitGroup 15 | wg.Add(10) 16 | for i := 0; i < 10; i++ { 17 | go work(&wg) 18 | } 19 | wg.Wait() 20 | // Wait to see the global run queue complete 21 | time.Sleep(3 * time.Second) 22 | } 23 | 24 | func work(wg *sync.WaitGroup) { 25 | time.Sleep(time.Second) 26 | var counter int 27 | for i := 0; i < 1e10; i++ { 28 | counter++ 29 | } 30 | wg.Done() 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module concurrency 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /gomaxprocs/README.md: -------------------------------------------------------------------------------- 1 | # GOMAXPROCS 2 | 3 | ### Examples 4 | 5 | - [NumCPU](https://github.com/golang-basics/concurrency/blob/master/gomaxprocs/gomaxprocs-numcpu/main.go) 6 | - [Many Threads](https://github.com/golang-basics/concurrency/blob/master/gomaxprocs/gomaxprocs-many-threads/main.go) 7 | 8 | [Home](https://github.com/golang-basics/concurrency) 9 | -------------------------------------------------------------------------------- /gomaxprocs/gomaxprocs-many-threads/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("PID", os.Getpid()) 12 | runtime.GOMAXPROCS(32) 13 | for i := 0; i < 32; i++ { 14 | go func() { 15 | for { 16 | } 17 | }() 18 | } 19 | time.Sleep(time.Minute) 20 | } 21 | -------------------------------------------------------------------------------- /gomaxprocs/gomaxprocs-numcpu/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("number of cores", runtime.NumCPU()) 12 | fmt.Println("PID", os.Getpid()) 13 | runtime.GOMAXPROCS(32) 14 | time.Sleep(time.Minute) 15 | } 16 | -------------------------------------------------------------------------------- /intro/async-tasks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | //asyncNotWorking() 10 | asyncTmpFix() 11 | } 12 | 13 | func asyncNotWorking() { 14 | now := time.Now() 15 | go task1() 16 | go task2() 17 | go task3() 18 | go task4() 19 | // WHY THE CODE ABOVE DOES NOT WORK? 20 | // WHAT COULD POSSIBLY GO WRONG? 21 | fmt.Println("elapsed", time.Now().Sub(now)) 22 | } 23 | 24 | func asyncTmpFix() { 25 | now := time.Now() 26 | go task1() 27 | go task2() 28 | go task3() 29 | go task4() 30 | // ALSO WHAT THE HECK IS WRONG with the order 31 | fmt.Println("elapsed", time.Now().Sub(now)) 32 | // WHY? FOR WHAT? 33 | time.Sleep(time.Second) 34 | } 35 | 36 | func task1() { 37 | time.Sleep(100 * time.Millisecond) 38 | fmt.Println("task 1") 39 | } 40 | 41 | func task2() { 42 | time.Sleep(200 * time.Millisecond) 43 | fmt.Println("task 2") 44 | } 45 | 46 | func task3() { 47 | fmt.Println("task 3") 48 | } 49 | 50 | func task4() { 51 | time.Sleep(100 * time.Millisecond) 52 | fmt.Println("task 4") 53 | } 54 | -------------------------------------------------------------------------------- /intro/fixed-async-tasks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | done := make(chan struct{}) 10 | now := time.Now() 11 | go task1(done) 12 | go task2(done) 13 | go task3(done) 14 | go task4(done) 15 | 16 | <-done 17 | <-done 18 | <-done 19 | <-done 20 | fmt.Println("elapsed:", time.Since(now)) 21 | } 22 | 23 | func task1(done chan struct{}) { 24 | time.Sleep(100 * time.Millisecond) 25 | fmt.Println("task1") 26 | done <- struct{}{} 27 | } 28 | 29 | func task2(done chan struct{}) { 30 | time.Sleep(200 * time.Millisecond) 31 | fmt.Println("task2") 32 | done <- struct{}{} 33 | } 34 | 35 | func task3(done chan struct{}) { 36 | fmt.Println("task3") 37 | done <- struct{}{} 38 | } 39 | 40 | func task4(done chan struct{}) { 41 | time.Sleep(100 * time.Millisecond) 42 | fmt.Println("task4") 43 | done <- struct{}{} 44 | } 45 | -------------------------------------------------------------------------------- /intro/fork-join/channel-join-point/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | now := time.Now() 10 | done := make(chan struct{}) 11 | go func() { 12 | work() 13 | done <- struct{}{} 14 | }() 15 | 16 | <-done 17 | fmt.Println("elapsed:", time.Since(now)) 18 | fmt.Println("done waiting, main exits") 19 | } 20 | 21 | func work() { 22 | time.Sleep(500 * time.Millisecond) 23 | fmt.Println("printing some stuff") 24 | } 25 | -------------------------------------------------------------------------------- /intro/fork-join/no-join-point/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | go work() // fork point 10 | time.Sleep(100 * time.Millisecond) 11 | fmt.Println("done waiting, main exits") 12 | // join point 13 | } 14 | 15 | func work() { 16 | time.Sleep(500 * time.Millisecond) 17 | fmt.Println("printing some stuff") 18 | } 19 | -------------------------------------------------------------------------------- /intro/fork-join/wg-join-point/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | now := time.Now() 11 | var wg sync.WaitGroup 12 | wg.Add(1) 13 | go func() { 14 | defer wg.Done() 15 | work() 16 | }() 17 | 18 | wg.Wait() 19 | fmt.Println("elapsed:", time.Since(now)) 20 | fmt.Println("done waiting, main exits") 21 | } 22 | 23 | func work() { 24 | time.Sleep(500 * time.Millisecond) 25 | fmt.Println("printing some stuff") 26 | } 27 | -------------------------------------------------------------------------------- /intro/sync-tasks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | now := time.Now() 10 | task1() 11 | task2() 12 | task3() 13 | task4() 14 | fmt.Println("elapsed:", time.Since(now)) 15 | } 16 | 17 | func task1() { 18 | time.Sleep(100 * time.Millisecond) 19 | fmt.Println("task1") 20 | } 21 | 22 | func task2() { 23 | time.Sleep(200 * time.Millisecond) 24 | fmt.Println("task2") 25 | } 26 | 27 | func task3() { 28 | fmt.Println("task3") 29 | } 30 | 31 | func task4() { 32 | time.Sleep(100 * time.Millisecond) 33 | fmt.Println("task4") 34 | } 35 | -------------------------------------------------------------------------------- /mutexes/atomic-mutex-mix/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // try running this with the -race flag 10 | // go run -race main.go 11 | func main() { 12 | var count int32 13 | var mu sync.Mutex 14 | var wg sync.WaitGroup 15 | 16 | for i := 0; i < 10000; i++ { 17 | wg.Add(2) 18 | go func() { 19 | defer wg.Done() 20 | atomic.AddInt32(&count, 1) 21 | }() 22 | go func() { 23 | defer wg.Done() 24 | mu.Lock() 25 | count++ 26 | mu.Unlock() 27 | }() 28 | } 29 | 30 | wg.Wait() 31 | // the result is correct, but Go is not able to properly detect a race condition 32 | // due to mixed usage of Mutex and Atomics 33 | fmt.Println("count:", count) 34 | } 35 | -------------------------------------------------------------------------------- /mutexes/basic-mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // to check for race conditions use the -race flag 10 | // go run -race main.go 11 | func main() { 12 | var count int 13 | var mu sync.Mutex 14 | go func() { 15 | mu.Lock() 16 | count = 10 17 | mu.Unlock() 18 | }() 19 | 20 | // remember: the main function is also running as a go routine 21 | // a good rule of thumb is to avoid RW conflicts in the main 22 | // and keep all the concurrent operations in their own go routines 23 | mu.Lock() 24 | count = 15 25 | mu.Unlock() 26 | 27 | // to avoid using WaitGroup 28 | time.Sleep(time.Second) 29 | mu.Lock() 30 | fmt.Println("count", count) 31 | mu.Unlock() 32 | } 33 | -------------------------------------------------------------------------------- /mutexes/cond/button/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type button struct { 9 | clicked *sync.Cond 10 | } 11 | 12 | func main() { 13 | btn := button{clicked: sync.NewCond(&sync.Mutex{})} 14 | var wg sync.WaitGroup 15 | 16 | wg.Add(3) 17 | subscribe(btn.clicked, handler1(&wg)) 18 | subscribe(btn.clicked, handler2(&wg)) 19 | subscribe(btn.clicked, handler3(&wg)) 20 | 21 | btn.clicked.Broadcast() 22 | wg.Wait() 23 | } 24 | 25 | func subscribe(cond *sync.Cond, fn func()) { 26 | var wg sync.WaitGroup 27 | wg.Add(1) 28 | go func() { 29 | wg.Done() 30 | cond.L.Lock() 31 | defer cond.L.Unlock() 32 | cond.Wait() 33 | fn() 34 | }() 35 | wg.Wait() 36 | } 37 | 38 | func handler1(wg *sync.WaitGroup) func() { 39 | return func() { 40 | fmt.Println("closing popup window") 41 | wg.Done() 42 | } 43 | } 44 | 45 | func handler2(wg *sync.WaitGroup) func() { 46 | return func() { 47 | fmt.Println("redirecting to a different page") 48 | wg.Done() 49 | } 50 | } 51 | 52 | func handler3(wg *sync.WaitGroup) func() { 53 | return func() { 54 | fmt.Println("display available options") 55 | wg.Done() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mutexes/cond/cond-implementation/channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func main() { 6 | } 7 | 8 | type cond struct { 9 | L sync.Mutex // used by caller 10 | mu sync.Mutex // guards ch 11 | ch chan struct{} 12 | } 13 | 14 | func (c *cond) Wait() { 15 | c.mu.Lock() 16 | ch := c.ch 17 | c.mu.Unlock() 18 | c.L.Unlock() 19 | <-ch 20 | c.L.Lock() 21 | } 22 | 23 | func (c *cond) Signal() { 24 | c.mu.Lock() 25 | defer c.mu.Unlock() 26 | select { 27 | case c.ch <- struct{}{}: 28 | default: 29 | } 30 | } 31 | 32 | func (c *cond) Broadcast() { 33 | c.mu.Lock() 34 | defer c.mu.Unlock() 35 | close(c.ch) 36 | c.ch = make(chan struct{}) 37 | } 38 | -------------------------------------------------------------------------------- /mutexes/cond/cond-vs-channel/channel-broadcast/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | broadcast := make(chan struct{}) 11 | var wg sync.WaitGroup 12 | wg.Add(2) 13 | go func() { 14 | defer wg.Done() 15 | <-broadcast 16 | fmt.Println("go routine 1: done") 17 | }() 18 | go func() { 19 | defer wg.Done() 20 | <-broadcast 21 | fmt.Println("go routine 2: done") 22 | }() 23 | 24 | time.Sleep(time.Second) 25 | close(broadcast) 26 | 27 | wg.Wait() 28 | } 29 | -------------------------------------------------------------------------------- /mutexes/cond/cond-vs-channel/channel-signal/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | signal := make(chan struct{}) 11 | var wg sync.WaitGroup 12 | wg.Add(2) 13 | go func() { 14 | defer wg.Done() 15 | <-signal 16 | fmt.Println("go routine 1: done") 17 | }() 18 | go func() { 19 | defer wg.Done() 20 | <-signal 21 | fmt.Println("go routine 2: done") 22 | }() 23 | 24 | // wake (signal) 1 waiting go routine 25 | time.Sleep(500 * time.Millisecond) 26 | signal <- struct{}{} 27 | 28 | // wake (signal) 1 waiting go routine 29 | time.Sleep(500 * time.Millisecond) 30 | signal <- struct{}{} 31 | 32 | wg.Wait() 33 | } 34 | -------------------------------------------------------------------------------- /mutexes/cond/deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // sync.Cond uses a mutex, so it pretty much has the exact same mutex issues 6 | func main() { 7 | cond := sync.NewCond(new(sync.Mutex)) 8 | cond.L.Lock() 9 | // the call to Wait() does 3 things 10 | // 1. Call Unlock() on Cond locker 11 | // 2. notify the wait list 12 | // 3. Call Lock() on Cond locker 13 | cond.Wait() 14 | } 15 | -------------------------------------------------------------------------------- /mutexes/cond/enqueue-dequeue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | cond := sync.NewCond(&sync.Mutex{}) 11 | queue := make([]int, 0, 10) 12 | dequeue := func(delay time.Duration) { 13 | time.Sleep(delay) 14 | cond.L.Lock() 15 | fmt.Println("dequeued", queue[0]) 16 | queue = queue[1:] 17 | cond.L.Unlock() 18 | cond.Signal() 19 | } 20 | 21 | for i := 0; i < 10; i++ { 22 | cond.L.Lock() 23 | // wait for dequeue if queue is full => 2 24 | for len(queue) == 2 { 25 | cond.Wait() 26 | } 27 | fmt.Println("enqueue", i) 28 | queue = append(queue, i) 29 | go dequeue(time.Second) 30 | cond.L.Unlock() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mutexes/cond/multiple-unlocks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // Calling Wait() requires the Mutex to be locked, 6 | // Not doing so results in a panic. 7 | // In fact Wait() calls Unlock() in the first place 8 | func main() { 9 | cond := sync.NewCond(new(sync.Mutex)) 10 | cond.Wait() 11 | //cond.L.Unlock() 12 | } 13 | -------------------------------------------------------------------------------- /mutexes/cond/reward-users/inefficient-lock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | var mu sync.Mutex 12 | var wg sync.WaitGroup 13 | var checks int32 14 | users := make([]string, 0) 15 | 16 | wg.Add(2) 17 | go func() { 18 | defer wg.Done() 19 | for i := 0; i < 100; i++ { 20 | mu.Lock() 21 | time.Sleep(5 * time.Millisecond) 22 | users = append(users, fmt.Sprintf("user %d", i+1)) 23 | mu.Unlock() 24 | } 25 | }() 26 | go func() { 27 | defer wg.Done() 28 | // burning unnecessary CPU cycles 29 | for { 30 | mu.Lock() 31 | atomic.AddInt32(&checks, 1) 32 | if len(users) >= 100 { 33 | reward(users[:10]) 34 | mu.Unlock() 35 | return 36 | } 37 | mu.Unlock() 38 | } 39 | }() 40 | 41 | wg.Wait() 42 | fmt.Println("number of checks", checks) 43 | } 44 | 45 | func reward(users []string) { 46 | for _, user := range users { 47 | fmt.Println("rewarding:", user) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mutexes/cond/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // The call to Wait does the following under the hood 10 | // 1. Calls Unlock() on the condition Locker 11 | // 2. Notifies the list wait 12 | // 3. Calls Lock() on the condition Locker 13 | 14 | // The Cond type besides the Locker also has access to 2 important methods: 15 | // 1. Signal - wakes up 1 go routine waiting on a condition (rendezvous point) 16 | // 2. Broadcast - wakes up all go routines waiting on a condition (rendezvous point) 17 | func main() { 18 | var wg sync.WaitGroup 19 | cond := sync.NewCond(&sync.Mutex{}) 20 | 21 | wg.Add(2) 22 | go func() { 23 | defer wg.Done() 24 | cond.L.Lock() 25 | defer cond.L.Unlock() 26 | cond.Wait() 27 | fmt.Println("go routine 1") 28 | }() 29 | go func() { 30 | defer wg.Done() 31 | cond.L.Lock() 32 | defer cond.L.Unlock() 33 | cond.Wait() 34 | fmt.Println("go routine 2") 35 | }() 36 | 37 | // Without this sleep, we may run into deadlocks, 38 | // because not all Done() calls will happen 39 | // Not broadcasting to all go routines => not having all Done() calls 40 | // This is why, WE MUST HAVE THE GO ROUTINES READY, before we broadcast 41 | time.Sleep(time.Second) 42 | cond.Broadcast() 43 | 44 | wg.Wait() 45 | } 46 | -------------------------------------------------------------------------------- /mutexes/cond/too-much-cpu/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | var condition int32 12 | var wg sync.WaitGroup 13 | wg.Add(3) 14 | go func() { 15 | defer wg.Done() 16 | // the below for loop makes the CPU consume all cycles on 1 CORE 17 | for { 18 | if atomic.LoadInt32(&condition) == 1 { 19 | fmt.Println("go routine 1: done") 20 | return 21 | } 22 | } 23 | }() 24 | go func() { 25 | defer wg.Done() 26 | // the below for loop makes the CPU consume all cycles on 1 CORE 27 | for { 28 | if atomic.LoadInt32(&condition) == 1 { 29 | fmt.Println("go routine 2: done") 30 | return 31 | } 32 | } 33 | }() 34 | go func() { 35 | defer wg.Done() 36 | time.Sleep(time.Second) 37 | atomic.StoreInt32(&condition, 1) 38 | }() 39 | wg.Wait() 40 | } 41 | -------------------------------------------------------------------------------- /mutexes/cond/too-much-sleep/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | var condition int32 12 | var wg sync.WaitGroup 13 | wg.Add(3) 14 | go func() { 15 | // the below for loop is inefficient, wastes too much time on sleeping 16 | // wasted extra 90 milliseconds 17 | defer wg.Done() 18 | for { 19 | time.Sleep(100 * time.Millisecond) 20 | if atomic.LoadInt32(&condition) == 1 { 21 | fmt.Println("go routine 1: done") 22 | return 23 | } 24 | } 25 | }() 26 | go func() { 27 | // the below for loop is inefficient, wastes too much time on sleeping 28 | // wasted extra 90 milliseconds 29 | defer wg.Done() 30 | for { 31 | time.Sleep(100 * time.Millisecond) 32 | if atomic.LoadInt32(&condition) == 1 { 33 | fmt.Println("go routine 2: done") 34 | return 35 | } 36 | } 37 | }() 38 | go func() { 39 | defer wg.Done() 40 | time.Sleep(10 * time.Millisecond) 41 | atomic.StoreInt32(&condition, 1) 42 | }() 43 | wg.Wait() 44 | } 45 | -------------------------------------------------------------------------------- /mutexes/cond/wg-deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | cond := sync.NewCond(&sync.Mutex{}) 10 | var wg sync.WaitGroup 11 | 12 | wg.Add(2) 13 | go func() { 14 | defer wg.Done() 15 | cond.L.Lock() 16 | cond.Wait() 17 | fmt.Println("handler 1") 18 | cond.L.Unlock() 19 | }() 20 | go func() { 21 | defer wg.Done() 22 | cond.L.Lock() 23 | cond.Wait() 24 | fmt.Println("handler 2") 25 | cond.L.Unlock() 26 | }() 27 | wg.Wait() 28 | // Looks like the call to Broadcast is way too fast 29 | // Whoa, slow down. Looks like not all go routines are ready 30 | // You missed some Done() calls ;) 31 | cond.Broadcast() 32 | } 33 | -------------------------------------------------------------------------------- /mutexes/contexts/coarse-grained-context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | var mu sync.Mutex 11 | var count int 12 | 13 | wg.Add(1000) 14 | for i := 0; i < 1000; i++ { 15 | go func() { 16 | defer wg.Done() 17 | var localCount int 18 | mu.Lock() 19 | localCount = count + 1 20 | count = localCount 21 | mu.Unlock() 22 | }() 23 | } 24 | 25 | wg.Wait() 26 | fmt.Println("count", count) 27 | } 28 | -------------------------------------------------------------------------------- /mutexes/contexts/fine-grained-context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | var mu sync.Mutex 11 | var count int 12 | 13 | wg.Add(1000) 14 | for i := 0; i < 1000; i++ { 15 | go func() { 16 | defer wg.Done() 17 | var localCount int 18 | mu.Lock() 19 | localCount = count + 1 20 | mu.Unlock() 21 | mu.Lock() 22 | count = localCount 23 | mu.Unlock() 24 | }() 25 | } 26 | 27 | wg.Wait() 28 | fmt.Println("count", count) 29 | } 30 | -------------------------------------------------------------------------------- /mutexes/contexts/multiple-gouroutines-readonly/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // try running this with the -race flag 9 | // go run -race main.go 10 | func main() { 11 | var wg sync.WaitGroup 12 | var count int 13 | wg.Add(10) 14 | for i := 0; i < 10; i++ { 15 | go func(i int) { 16 | defer wg.Done() 17 | // Each go routine only reads the count data and does not expose its data outside 18 | var localCount int 19 | localCount += count + i + 1 20 | fmt.Println("local count", localCount) 21 | }(i) 22 | } 23 | wg.Wait() 24 | } 25 | -------------------------------------------------------------------------------- /mutexes/contexts/single-and-main-gouroutine/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // try running this with the -race flag 9 | // go run -race main.go 10 | func main() { 11 | var wg sync.WaitGroup 12 | var count int 13 | 14 | wg.Add(1) 15 | go func() { 16 | defer wg.Done() 17 | // G1 will attempt a read operation 18 | //fmt.Println(count) 19 | // G1 will attempt a write operation 20 | count++ 21 | }() 22 | // Main G will also attempt a write operation 23 | count++ 24 | 25 | wg.Wait() 26 | fmt.Println("count", count) 27 | } 28 | -------------------------------------------------------------------------------- /mutexes/contexts/single-gouroutine/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // try running this with the -race flag 9 | // go run -race main.go 10 | func main() { 11 | var wg sync.WaitGroup 12 | var count int 13 | 14 | wg.Add(1) 15 | go func() { 16 | defer wg.Done() 17 | // Only the G1 go routine will do the write operation, thus it's safe 18 | count++ 19 | }() 20 | // Main G does not read or write while G1 is running, 21 | // so this will not run in a race condition. 22 | // Or we could just move count++ inside main, which will execute in its own go routine 23 | 24 | wg.Wait() 25 | fmt.Println("count", count) 26 | } 27 | -------------------------------------------------------------------------------- /mutexes/crypto-reader/.gitignore: -------------------------------------------------------------------------------- 1 | testdata/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /mutexes/crypto-reader/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | @echo "building the crypto-generator binary" 3 | go build -o bin/crypto-generator 4 | @echo "building the crypto-reader binary" 5 | go build -o bin/crypto-reader 6 | 7 | test: 8 | @echo "running all tests" 9 | go test -v ./... 10 | 11 | bench: 12 | @echo "running all benchmarks" 13 | go test -bench . ./... 14 | -------------------------------------------------------------------------------- /mutexes/crypto-reader/crypto/file_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | -------------------------------------------------------------------------------- /mutexes/crypto-reader/crypto/reader_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | -------------------------------------------------------------------------------- /mutexes/crypto-reader/go.mod: -------------------------------------------------------------------------------- 1 | module githubc.com/steevehook/crypto-reader 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /mutexes/crypto-reader/go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/mutexes/crypto-reader/go.sum -------------------------------------------------------------------------------- /mutexes/dbcache/mutex-beautiful/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // to check for race conditions use the -race flag 9 | // go run -race main.go 10 | func main() { 11 | d := newDB() 12 | var wg sync.WaitGroup 13 | wg.Add(2) 14 | go func() { 15 | // setting k1 here 16 | d.set("k1", "v1") 17 | wg.Done() 18 | }() 19 | go func() { 20 | // setting k1 as well here 21 | d.set("k1", "v2") 22 | wg.Done() 23 | }() 24 | // trying to get a value while it's being set by the go routines 25 | fmt.Println(d.get("k1")) 26 | wg.Wait() 27 | } 28 | 29 | func newDB() db { 30 | return db{values: map[string]string{}} 31 | } 32 | 33 | type db struct { 34 | values map[string]string 35 | mu sync.Mutex 36 | } 37 | 38 | func (d *db) set(key, value string) { 39 | d.mu.Lock() 40 | defer d.mu.Unlock() 41 | d.values[key] = value 42 | } 43 | 44 | func (d *db) get(key string) string { 45 | d.mu.Lock() 46 | defer d.mu.Unlock() 47 | return d.values[key] 48 | } 49 | -------------------------------------------------------------------------------- /mutexes/dbcache/mutex-ugly/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // to check for race conditions use the -race flag 9 | // go run -race main.go 10 | func main() { 11 | d := newDB() 12 | var mu sync.Mutex 13 | var wg sync.WaitGroup 14 | wg.Add(2) 15 | go func() { 16 | // setting k1 here 17 | mu.Lock() 18 | d.set("k1", "v1") 19 | mu.Unlock() 20 | wg.Done() 21 | }() 22 | go func() { 23 | // setting k1 as well here 24 | mu.Lock() 25 | d.set("k1", "v2") 26 | mu.Unlock() 27 | wg.Done() 28 | }() 29 | // trying to get a value while it's being set by the go routines 30 | mu.Lock() 31 | fmt.Println(d.get("k1")) 32 | mu.Unlock() 33 | wg.Wait() 34 | } 35 | 36 | func newDB() db { 37 | return db{values: map[string]string{}} 38 | } 39 | 40 | type db struct { 41 | values map[string]string 42 | } 43 | 44 | func (d *db) set(key, value string) { 45 | d.values[key] = value 46 | } 47 | 48 | func (d db) get(key string) string { 49 | return d.values[key] 50 | } 51 | -------------------------------------------------------------------------------- /mutexes/dbcache/race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // to check for race conditions use the -race flag 9 | // go run -race main.go 10 | func main() { 11 | d := newDB() 12 | var wg sync.WaitGroup 13 | wg.Add(2) 14 | go func() { 15 | // setting k1 here 16 | d.set("k1", "v1") 17 | wg.Done() 18 | }() 19 | go func() { 20 | // setting k1 as well here 21 | d.set("k1", "v2") 22 | wg.Done() 23 | }() 24 | // trying to get a value while it's being set by the go routines 25 | fmt.Println(d.get("k1")) 26 | wg.Wait() 27 | } 28 | 29 | func newDB() db { 30 | return db{values: map[string]string{}} 31 | } 32 | 33 | type db struct { 34 | values map[string]string 35 | } 36 | 37 | func (d *db) set(key, value string) { 38 | d.values[key] = value 39 | } 40 | 41 | func (d db) get(key string) string { 42 | return d.values[key] 43 | } 44 | -------------------------------------------------------------------------------- /mutexes/deadlock-and-race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Having both deadlock and race 9 | // will result in the program hanging 10 | // when ran with -race flag 11 | // go run -race main.go => will show races, but hang forever 12 | 13 | // Try running the program first without -race => fix the deadlock 14 | // go run main.go 15 | // then using the -race flag => fix the race condition 16 | // go run -race main.go 17 | func main() { 18 | var count int 19 | var mu sync.Mutex 20 | var wg sync.WaitGroup 21 | 22 | wg.Add(2) 23 | go func() { 24 | defer wg.Done() 25 | count++ 26 | 27 | }() 28 | go func() { 29 | defer wg.Done() 30 | count++ 31 | }() 32 | 33 | wg.Wait() 34 | mu.Lock() 35 | mu.Lock() 36 | fmt.Println("count:", count) 37 | } 38 | -------------------------------------------------------------------------------- /mutexes/deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Every Lock() call must have a corresponding Unlock() call 8 | // If the method Lock() more times in a row without the lock being released 9 | // that will result in a deadlock 10 | // The same is true for RWMutex, any RLock or Lock MUST be followed by an Unlock() call. 11 | // If either the read or write lock is called more times in a row 12 | // it will also result in a deadlock 13 | func main() { 14 | var mu sync.Mutex 15 | // mu.Lock() 16 | // mu.Lock() 17 | // var mu sync.Mutex 18 | // mu.RLock() 19 | // mu.Lock() 20 | 21 | adultsOnly(10, &mu) 22 | adultsOnly(18, &mu) 23 | } 24 | 25 | func adultsOnly(age int, mu *sync.Mutex) { 26 | mu.Lock() 27 | // defer mu.Unlock() 28 | if age < 18 { 29 | // this is like a small jump out the window :D 30 | return 31 | } 32 | mu.Unlock() 33 | } 34 | -------------------------------------------------------------------------------- /mutexes/deadlocks/circular-wait/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | m1, m2 := &sync.Mutex{}, &sync.Mutex{} 10 | var wg sync.WaitGroup 11 | wg.Add(1) 12 | go func() { 13 | defer wg.Done() 14 | m1.Lock() 15 | fmt.Println("m1 locked") 16 | 17 | // m2.Unlock() never gets called => deadlock, while waiting to acquire lock 18 | m2.Lock() 19 | fmt.Println("m2 locked") 20 | m2.Unlock() 21 | fmt.Println("m2 unlocked") 22 | fmt.Println("done in go routine") 23 | }() 24 | 25 | m2.Lock() 26 | fmt.Println("m2 locked") 27 | 28 | // to simulate a circular wait we need to create the condition 29 | // that the call to Done() is not being made, thus the call to Wait will be stuck 30 | // causing the code below to deadlock, while waiting to acquire lock 31 | wg.Wait() 32 | // m1.Unlock() never gets called => deadlock, while waiting to acquire lock 33 | m1.Lock() 34 | fmt.Println("m1 locked") 35 | m1.Unlock() 36 | fmt.Println("m1 unlocked") 37 | fmt.Println("done in main") 38 | } 39 | -------------------------------------------------------------------------------- /mutexes/deadlocks/goexit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "runtime" 4 | 5 | func main() { 6 | // this will cause the main goroutine to be terminated, resulting in a deadlock 7 | // basically the scheduler will check for running go routines, if none are running it will result in 8 | // waiting forever on nothing to run => deadlock 9 | runtime.Goexit() 10 | } 11 | -------------------------------------------------------------------------------- /mutexes/deadlocks/hold-and-wait/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // let's assume the condition is: in order to compute total we need the values from v1 and v2 11 | func main() { 12 | var mu sync.Mutex 13 | var v1, v2, total int 14 | go func() { 15 | mu.Lock() 16 | v1 = 2 17 | v2 = 3 18 | // we could also simply not call the Unlock method 19 | runtime.Goexit() 20 | mu.Unlock() 21 | }() 22 | 23 | // wait for go routine to call Lock first 24 | // this is to avoid using wait groups 25 | time.Sleep(time.Second) 26 | mu.Lock() 27 | total = v1 + v2 28 | mu.Unlock() 29 | fmt.Println(total) 30 | } 31 | -------------------------------------------------------------------------------- /mutexes/deadlocks/no-preemption/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // GOROOT/src/runtime/proc.go#5206 11 | // https://github.com/golang/go/blob/35ea62468bf7e3a79011c3ad713e847daa9a45a2/src/runtime/proc.go#L4159-L4233 12 | // for{} loops in conjunction with runtime.GOMAXPROCS(1) will make a go routine non-preemptive, or hanging forever 13 | func main() { 14 | runtime.GOMAXPROCS(1) 15 | var mu sync.Mutex 16 | var count int 17 | go func() { 18 | mu.Lock() 19 | defer mu.Unlock() 20 | count = 10 21 | // No longer an issue for Go 1.14+ 22 | for { 23 | } 24 | }() 25 | 26 | time.Sleep(time.Second) 27 | mu.Lock() 28 | count++ 29 | mu.Unlock() 30 | fmt.Println("count", count) 31 | } 32 | -------------------------------------------------------------------------------- /mutexes/distributed-db/.gitignore: -------------------------------------------------------------------------------- 1 | .data/ 2 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/delete.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "distributed-db/models" 9 | ) 10 | 11 | type cacheRemover interface { 12 | Delete(keys []string) 13 | } 14 | 15 | func remove(svc cacheRemover) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | var req models.DeleteRequest 18 | err := json.NewDecoder(r.Body).Decode(&req) 19 | if err != nil { 20 | log.Printf("could not decode delete request: %v", err) 21 | w.WriteHeader(http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | svc.Delete(req.Keys) 26 | w.WriteHeader(http.StatusOK) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/get.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "distributed-db/models" 9 | ) 10 | 11 | type cacheGetter interface { 12 | Get(keys []string) []models.CacheItem 13 | } 14 | 15 | func get(svc cacheGetter) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | var req models.GetRequest 18 | err := json.NewDecoder(r.Body).Decode(&req) 19 | if err != nil { 20 | log.Printf("could not decode get request: %v", err) 21 | w.WriteHeader(http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | items := svc.Get(req.Keys) 26 | 27 | w.Header().Set("Content-Type", "application/json") 28 | err = json.NewEncoder(w).Encode(items) 29 | if err != nil { 30 | log.Printf("could not encode json: %v", err) 31 | w.WriteHeader(http.StatusInternalServerError) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/gossip.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "distributed-db/models" 9 | ) 10 | 11 | type tokensUpdater interface { 12 | UpdateTokens(node string, newNodes models.NodesMap, tokensChecksum string) (oldNodes models.NodesMap, err error) 13 | } 14 | 15 | func gossip(svc tokensUpdater) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | var req models.GossipRequest 18 | err := json.NewDecoder(r.Body).Decode(&req) 19 | if err != nil { 20 | log.Printf("could not decode gossip request: %v", err) 21 | w.WriteHeader(http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | oldNodes, err := svc.UpdateTokens(r.Host, req.Nodes, req.TokensChecksum) 26 | if err != nil { 27 | log.Printf("could not update tokens: %v", err) 28 | w.WriteHeader(http.StatusInternalServerError) 29 | return 30 | } 31 | 32 | w.Header().Set("Content-Type", "application/json") 33 | res := models.GossipResponse{Nodes: oldNodes} 34 | err = json.NewEncoder(w).Encode(res) 35 | if err != nil { 36 | log.Printf("could not encode gossip response: %v", err) 37 | w.WriteHeader(http.StatusInternalServerError) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/router.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type CacheService interface { 8 | cacheGetter 9 | cacheSetter 10 | cacheBatchSetter 11 | cacheRemover 12 | tokensGetter 13 | tokensUpdater 14 | } 15 | 16 | func NewRouter(svc CacheService) http.Handler { 17 | mux := http.NewServeMux() 18 | mux.HandleFunc("/get", get(svc)) 19 | mux.HandleFunc("/set", set(svc)) 20 | mux.HandleFunc("/delete", remove(svc)) 21 | // add a middleware that does not allow 22 | // requests that do not come from other nodes 23 | mux.HandleFunc("/set/batch", setBatch(svc)) 24 | mux.HandleFunc("/gossip", gossip(svc)) 25 | mux.HandleFunc("/tokens", tokens(svc)) 26 | 27 | return mux 28 | } 29 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/set.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "distributed-db/models" 9 | ) 10 | 11 | type cacheSetter interface { 12 | Set(key, value string) (models.CacheItem, error) 13 | } 14 | 15 | func set(svc cacheSetter) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | var req models.SetRequest 18 | err := json.NewDecoder(r.Body).Decode(&req) 19 | if err != nil { 20 | log.Printf("could not decode set request: %v", err) 21 | w.WriteHeader(http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | item, err := svc.Set(req.Key, req.Value) 26 | if err != nil { 27 | log.Printf("could not store cache item: %v", err) 28 | w.WriteHeader(http.StatusInternalServerError) 29 | return 30 | } 31 | 32 | log.Printf("successfully stored record with key: %s on: %s", item.Key, item.Node) 33 | w.Header().Set("Content-Type", "application/json") 34 | err = json.NewEncoder(w).Encode(item) 35 | if err != nil { 36 | log.Printf("could not encode set response: %v", err) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/set_batch.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "distributed-db/models" 9 | ) 10 | 11 | type cacheBatchSetter interface { 12 | SetBatch(items map[int]models.CacheItem) []models.CacheItem 13 | } 14 | 15 | func setBatch(svc cacheBatchSetter) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | var req models.SetBatchRequest 18 | err := json.NewDecoder(r.Body).Decode(&req) 19 | if err != nil { 20 | log.Printf("could not decode set batch request: %v", err) 21 | w.WriteHeader(http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | items := svc.SetBatch(req.Items) 26 | log.Printf("successfully stored batch") 27 | 28 | w.Header().Set("Content-Type", "application/json") 29 | err = json.NewEncoder(w).Encode(items) 30 | if err != nil { 31 | log.Printf("could not encode set batch response: %v", err) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mutexes/distributed-db/controllers/tokens.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "distributed-db/models" 9 | ) 10 | 11 | type tokensGetter interface { 12 | GetTokens() map[int]string 13 | } 14 | 15 | func tokens(svc tokensGetter) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | res := models.TokensResponse{ 18 | Tokens: svc.GetTokens(), 19 | } 20 | 21 | w.Header().Set("Content-Type", "application/json") 22 | err := json.NewEncoder(w).Encode(res) 23 | if err != nil { 24 | log.Printf("could not encode gossip response: %v", err) 25 | w.WriteHeader(http.StatusInternalServerError) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mutexes/distributed-db/go.mod: -------------------------------------------------------------------------------- 1 | module distributed-db 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /mutexes/distributed-db/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "distributed-db/app" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | func main() { 13 | ctx, cancel := context.WithCancel(context.Background()) 14 | signals := make(chan os.Signal, 1) 15 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 16 | 17 | a, err := app.New() 18 | if err != nil { 19 | log.Fatalf("could not create the app: %v", err) 20 | } 21 | 22 | go func() { 23 | err = a.Start(ctx) 24 | if err != nil { 25 | log.Fatalf("could not start the app: %v", err) 26 | } 27 | }() 28 | 29 | select { 30 | case <-signals: 31 | cancel() 32 | err = a.Stop(ctx) 33 | if err != nil { 34 | log.Fatalf("could not stop the app: %v", err) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mutexes/distributed-db/models/cache.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type CacheItem struct { 8 | Key string `json:"key"` 9 | Value string `json:"value"` 10 | UpdatedAt time.Time `json:"updated_at,omitempty"` 11 | Node string `json:"node,omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /mutexes/distributed-db/models/flags.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type NodesMap map[string]int 9 | 10 | func (m NodesMap) Set(element string) error { 11 | m[element] = NodeStatusUp 12 | return nil 13 | } 14 | 15 | func (m NodesMap) String() string { 16 | list := make([]string, 0, len(m)) 17 | for node, status := range m { 18 | list = append(list, fmt.Sprintf("%s:%d", node, status)) 19 | } 20 | return strings.Join(list, ",") 21 | } 22 | -------------------------------------------------------------------------------- /mutexes/distributed-db/models/responses.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type GossipResponse struct { 4 | Nodes map[string]int `json:"nodes"` 5 | } 6 | 7 | type TokensResponse struct { 8 | Tokens TokenMappings `json:"tokens"` 9 | } 10 | -------------------------------------------------------------------------------- /mutexes/distributed-db/workers/gossip.go: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | ) 8 | 9 | const gossipPeriod = 3 * time.Second 10 | 11 | type gossiper interface { 12 | Gossip() 13 | } 14 | 15 | func NewGossip(svc gossiper) Gossip { 16 | return Gossip{ 17 | svc: svc, 18 | } 19 | } 20 | 21 | type Gossip struct { 22 | svc gossiper 23 | } 24 | 25 | func (g *Gossip) Start(ctx context.Context) { 26 | log.Println("gossip worker started successfully") 27 | 28 | for { 29 | select { 30 | case <-ctx.Done(): 31 | log.Println("stopping the gossip worker") 32 | return 33 | case <-time.NewTicker(gossipPeriod).C: 34 | g.svc.Gossip() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mutexes/exercises/1/exercise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // GOFLAGS="-count=1" go test -race . 8 | func TestExercise(t *testing.T) { 9 | clicks := exercise() 10 | 11 | n1 := clicks.elements["btn1"] 12 | n2 := clicks.elements["btn2"] 13 | total := clicks.total 14 | 15 | if n1 != 10 { 16 | t.Fatalf("expected number of clicks for btn1: %d, got: %d", 10, n1) 17 | } 18 | if n2 != 10 { 19 | t.Fatalf("expected number of clicks for btn2: %d, got: %d", 10, n2) 20 | } 21 | if total != 20 { 22 | t.Fatalf("expected total to be: %d, got: %d", 20, total) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mutexes/exercises/2/exercise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // GOFLAGS="-count=1" go test . 9 | func TestExercise(t *testing.T) { 10 | c := &catalog{data: map[string]string{ 11 | "p1": "apples", 12 | "p2": "oranges", 13 | "p3": "grapes", 14 | "p4": "pineapple", 15 | "p5": "bananas", 16 | }} 17 | 18 | now := time.Now() 19 | exercise(c, "p1", "p2", "p3", "p4", "p5") 20 | elapsed := time.Since(now) 21 | numberOfProducts := len(c.data) 22 | 23 | if numberOfProducts != 1005 { 24 | t.Fatalf("wrong number of products in the catalog, got: %d", numberOfProducts) 25 | } 26 | if elapsed > 10*time.Millisecond { 27 | t.Fatalf("exercise took too long, elapsed: %v", elapsed) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mutexes/exercises/3/exercise.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // EXERCISE: 10 | // Find what's wrong in the exercise() function 11 | // Make sure all the tests are passing 12 | // DO NOT remove any Sleep() calls 13 | 14 | // try running this with the -race flag 15 | // go run -race exercise.go 16 | 17 | // run the tests using: 18 | // GOFLAGS="-count=1" go test . 19 | 20 | func main() { 21 | now := time.Now() 22 | count := exercise() 23 | fmt.Println("count:", count) 24 | fmt.Println("elapsed:", time.Since(now)) 25 | } 26 | 27 | func exercise() int { 28 | var A, B, count int 29 | var wg sync.WaitGroup 30 | var mu sync.Mutex 31 | 32 | wg.Add(1000) 33 | for i := 0; i < 1000; i++ { 34 | go func() { 35 | defer wg.Done() 36 | var a, b int 37 | mu.Lock() 38 | a = A 39 | mu.Unlock() 40 | 41 | mu.Lock() 42 | b = B 43 | mu.Unlock() 44 | 45 | mu.Lock() 46 | count += a + b 47 | mu.Unlock() 48 | 49 | mu.Lock() 50 | A++ 51 | B++ 52 | mu.Unlock() 53 | }() 54 | } 55 | wg.Wait() 56 | return count 57 | } 58 | -------------------------------------------------------------------------------- /mutexes/exercises/3/exercise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // GOFLAGS="-count=1" go test . 8 | func TestExercise(t *testing.T) { 9 | count := exercise() 10 | if count != 999000 { 11 | t.Fatalf("got a different count: %d", count) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mutexes/exercises/3/solution/solution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // EXERCISE: 10 | // Find what's wrong in the exercise() function 11 | // Make sure all the tests are passing 12 | // DO NOT remove any Sleep() calls 13 | 14 | // try running this with the -race flag 15 | // go run -race exercise.go 16 | 17 | // run the tests using: 18 | // GOFLAGS="-count=1" go test . 19 | 20 | // SOLUTION 21 | // The problem with this implementation is the fine-grained 22 | // defined context by the mutex, resulting in an undesired result. 23 | // All we really need to fix the problem is limit the context 24 | // of the mutex, which will also give us the expected result 25 | func main() { 26 | now := time.Now() 27 | count := exercise() 28 | fmt.Println("count:", count) 29 | fmt.Println("elapsed:", time.Since(now)) 30 | } 31 | 32 | func exercise() int { 33 | var A, B, count int 34 | var wg sync.WaitGroup 35 | var mu sync.Mutex 36 | 37 | wg.Add(1000) 38 | for i := 0; i < 1000; i++ { 39 | go func() { 40 | defer wg.Done() 41 | var a, b int 42 | mu.Lock() 43 | a = A 44 | b = B 45 | count += a + b 46 | A++ 47 | B++ 48 | mu.Unlock() 49 | }() 50 | } 51 | wg.Wait() 52 | return count 53 | } 54 | -------------------------------------------------------------------------------- /mutexes/exercises/4/exercise.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // EXERCISE: 10 | // Find what's wrong in the exercise() function 11 | // Make sure all the tests are passing 12 | // DO NOT remove any Sleep() calls 13 | 14 | // try running this with the -race flag 15 | // go run -race exercise.go 16 | 17 | // run the tests using: 18 | // GOFLAGS="-count=1" go test . 19 | 20 | func main() { 21 | now := time.Now() 22 | A := exercise() 23 | fmt.Println("A:", A) 24 | fmt.Println("elapsed:", time.Since(now)) 25 | } 26 | 27 | func exercise() int { 28 | var A, B int 29 | var muA, muB sync.Mutex 30 | 31 | for i := 0; i < 1000; i++ { 32 | go func() { 33 | muA.Lock() 34 | defer muA.Unlock() 35 | muB.Lock() 36 | defer muB.Unlock() 37 | 38 | B++ 39 | // simulate load 40 | time.Sleep(time.Millisecond) 41 | A++ 42 | }() 43 | 44 | } 45 | 46 | var wg sync.WaitGroup 47 | wg.Add(1000) 48 | for i := 0; i < 1000; i++ { 49 | go func() { 50 | defer wg.Done() 51 | muA.Lock() 52 | defer muA.Unlock() 53 | A += 5 54 | }() 55 | } 56 | 57 | wg.Wait() 58 | return A 59 | } 60 | -------------------------------------------------------------------------------- /mutexes/exercises/4/exercise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // GOFLAGS="-count=1" go test . 9 | func TestExercise(t *testing.T) { 10 | now := time.Now() 11 | A := exercise() 12 | elapsed := time.Since(now) 13 | 14 | if A != 6000 { 15 | t.Fatalf("Expected A to be 6000, got: %d", A) 16 | } 17 | if elapsed > 10*time.Millisecond { 18 | t.Fatalf("exercise took too long: %v", elapsed) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mutexes/exercises/5/exercise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const delta = 5000 10 | 11 | // GOFLAGS="-count=1" go test . 12 | func TestExercise(t *testing.T) { 13 | p1, p2 := exercise(time.Second) 14 | 15 | min, max := math.Min(float64(p1), float64(p2)), math.Max(float64(p1), float64(p2)) 16 | if max-min > delta { 17 | t.Fatalf("difference in values is bigger than delta: %d, got: %d", delta, int(max-min)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mutexes/exercises/6/exercise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | // GOFLAGS="-count=1" go test . 9 | func TestExercise(t *testing.T) { 10 | now := time.Now() 11 | accounts := exercise() 12 | elapsed := time.Since(now) 13 | expectedAccounts := []account{ 14 | {id: "a1", amount: 305, transactions: []string{"bank2: $100", "bank1: $200"}}, 15 | {id: "a2", amount: 310, transactions: []string{"bank2: $100", "bank1: $200"}}, 16 | } 17 | 18 | if len(accounts) != 2 { 19 | t.Fatalf("expected number of account to be 2, got %d", len(accounts)) 20 | } 21 | for i, a := range accounts { 22 | if a.id != expectedAccounts[i].id || a.amount != expectedAccounts[i].amount || len(accounts[i].transactions) != 2 { 23 | t.Fatalf("expected account to be %v, got %v", expectedAccounts[i], a) 24 | } 25 | } 26 | if elapsed > 3*time.Second { 27 | t.Fatalf("exercise took too long: %v", elapsed) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mutexes/lock-contention/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Lock Contention is the process when a process or thread tries to acquire a lock that is held by another 10 | // process or thread, thus causing it to wait longer than it needs to 11 | func main() { 12 | var wg sync.WaitGroup 13 | var mu sync.Mutex 14 | wg.Add(2) 15 | 16 | go func() { 17 | mu.Lock() 18 | time.Sleep(3 * time.Second) 19 | fmt.Println("go routine 1 releasing lock after 3s:", time.Now()) 20 | mu.Unlock() 21 | wg.Done() 22 | }() 23 | 24 | // simulate order 25 | time.Sleep(time.Nanosecond) 26 | 27 | go func() { 28 | fmt.Println("go routine 2 trying to acquire lock:", time.Now()) 29 | mu.Lock() 30 | fmt.Println("go routine 2 acquired lock after 3s:", time.Now()) 31 | mu.Unlock() 32 | wg.Done() 33 | }() 34 | 35 | wg.Wait() 36 | } 37 | -------------------------------------------------------------------------------- /mutexes/multiple-locks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // Only multiple consecutive calls to Lock() on Write (exclusive) mutex 6 | // will cause the program do deadlock, every other kind of mutex is considered 7 | // to be a Read (shared) mutex, which will work perfectly fine 8 | func main() { 9 | // deadlock -> fatal error 10 | var mu sync.Mutex 11 | mu.Lock() 12 | mu.Lock() 13 | 14 | // var rwMu sync.RWMutex 15 | // deadlock -> fatal error 16 | // rwMu.Lock() 17 | // rwMu.Lock() 18 | // no deadlock -> works fine (shared lock) 19 | // rwMu.RLock() 20 | // rwMu.RLock() 21 | 22 | // works fine -> equivalent to RLock (shared lock) 23 | // var mu sync.RWMutex 24 | // l := mu.RLocker() 25 | // l.Lock() 26 | // l.Lock() 27 | } 28 | -------------------------------------------------------------------------------- /mutexes/multiple-unlocks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // A call to Unlock() must only happen after a call to Lock() has been made 6 | // If done otherwise, it will result in a fatal error 7 | // Any of the below lock types (mutexes) will result in a fatal error 8 | func main() { 9 | var mu sync.Mutex 10 | mu.Unlock() 11 | 12 | // var rwMu sync.RWMutex 13 | // rwMu.Unlock() 14 | // rwMu.RUnlock() 15 | 16 | // var mu sync.RWMutex 17 | // l := mu.RLocker() 18 | // l.Unlock() 19 | } 20 | -------------------------------------------------------------------------------- /mutexes/mutex-implementation/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define PING "ping" 9 | #define PONG "pong" 10 | 11 | void critical(const char * str) { 12 | size_t len = strlen(str); 13 | for (size_t i = 0; i < len; ++i) { 14 | printf("%c", str[i]); 15 | } // for 16 | printf("\n"); 17 | } // str 18 | 19 | typedef atomic_flag mut_t; 20 | volatile mut_t mut = ATOMIC_FLAG_INIT; // false; true = locked; false = unlocked 21 | 22 | // void acquire(mut_t * m) 23 | #define acquire(m) while (atomic_flag_test_and_set(m)) 24 | // void release(mut_t * m) 25 | #define release(m) atomic_flag_clear(m) 26 | 27 | void * pingpong(void * p) { 28 | char * msg = (char *) p; 29 | for (;;) { 30 | acquire(&mut); 31 | critical(msg); 32 | release(&mut); 33 | } // for 34 | } // pingpong 35 | 36 | // gcc main.c -o exec 37 | // ./exec 38 | int main() { 39 | setvbuf(stdout, NULL, _IONBF, 0); 40 | pthread_t ping_thread; 41 | pthread_t pong_thread; 42 | pthread_create(&ping_thread, NULL, pingpong, PING); 43 | pthread_create(&pong_thread, NULL, pingpong, PONG); 44 | for(;;); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /mutexes/mutex-vs-atomic/atomic_test.go: -------------------------------------------------------------------------------- 1 | package mutex_vs_atomic 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | ) 8 | 9 | // to run the benchmarks cd into "atomics/benchmarks" and run: 10 | // go test -bench=. 11 | func BenchmarkAtomicNumber(b *testing.B) { 12 | b.ReportAllocs() 13 | var wg sync.WaitGroup 14 | var v int64 15 | for i := 0; i < 5; i++ { 16 | wg.Add(1) 17 | go func() { 18 | for j := 0; j < b.N; j++ { 19 | atomic.SwapInt64(&v, int64(j)) 20 | } 21 | wg.Done() 22 | }() 23 | } 24 | wg.Wait() 25 | // to avoid compile errors 26 | v = v 27 | } 28 | 29 | // to run the benchmarks cd into "atomics/benchmarks" and run: 30 | // go test -bench=. 31 | func BenchmarkAtomicStruct(b *testing.B) { 32 | b.ReportAllocs() 33 | var wg sync.WaitGroup 34 | var v atomic.Value 35 | for i := 0; i < 5; i++ { 36 | wg.Add(1) 37 | go func() { 38 | for j := 0; j < b.N; j++ { 39 | v.Store(Config{ 40 | a: []int{j + 1, j + 2, j + 3, j + 4, j + 5}, 41 | }) 42 | } 43 | wg.Done() 44 | }() 45 | } 46 | wg.Wait() 47 | // to avoid compile errors 48 | v = v 49 | } 50 | -------------------------------------------------------------------------------- /mutexes/mutex-vs-atomic/config_test.go: -------------------------------------------------------------------------------- 1 | package mutex_vs_atomic 2 | 3 | type Config struct { 4 | a []int 5 | } 6 | -------------------------------------------------------------------------------- /mutexes/mutex-vs-atomic/mutex_test.go: -------------------------------------------------------------------------------- 1 | package mutex_vs_atomic 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | // to run the benchmarks cd into "atomics/benchmarks" and run: 9 | // go test -bench=. 10 | func BenchmarkMutexNumber(b *testing.B) { 11 | b.ReportAllocs() 12 | var wg sync.WaitGroup 13 | var mu sync.Mutex 14 | var v int64 15 | for i := 0; i < 5; i++ { 16 | wg.Add(1) 17 | go func() { 18 | for j := 0; j < b.N; j++ { 19 | mu.Lock() 20 | v = int64(j) 21 | mu.Unlock() 22 | } 23 | wg.Done() 24 | }() 25 | } 26 | wg.Wait() 27 | // to avoid compile errors 28 | v = v 29 | } 30 | 31 | // to run the benchmarks cd into "atomics/benchmarks" and run: 32 | // go test -bench=. 33 | func BenchmarkMutexStruct(b *testing.B) { 34 | b.ReportAllocs() 35 | var wg sync.WaitGroup 36 | var mu sync.Mutex 37 | cfg := Config{} 38 | for i := 0; i < 5; i++ { 39 | wg.Add(1) 40 | go func() { 41 | for j := 0; j < b.N; j++ { 42 | mu.Lock() 43 | cfg = Config{ 44 | a: []int{j + 1, j + 2, j + 3, j + 4, j + 5}, 45 | } 46 | mu.Unlock() 47 | } 48 | wg.Done() 49 | }() 50 | } 51 | wg.Wait() 52 | // to avoid compile errors 53 | cfg = cfg 54 | } 55 | -------------------------------------------------------------------------------- /mutexes/once/caching/bad/bad_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // cd "caching" 10 | // go test -bench . ./... 11 | func BenchmarkBad(b *testing.B) { 12 | b.ReportAllocs() 13 | c := client{ 14 | cache: map[string]*cacheEntry{}, 15 | mu: new(sync.Mutex), 16 | } 17 | var wg sync.WaitGroup 18 | wg.Add(b.N) 19 | for i := 0; i < b.N; i++ { 20 | go func() { 21 | defer wg.Done() 22 | c.httpCall("req", 300*time.Millisecond) 23 | }() 24 | } 25 | wg.Wait() 26 | } 27 | -------------------------------------------------------------------------------- /mutexes/once/caching/better/better_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // cd "caching" 10 | // go test -bench . ./... 11 | func BenchmarkBetter(b *testing.B) { 12 | b.ReportAllocs() 13 | c := client{ 14 | cache: map[string]*cacheEntry{}, 15 | mu: new(sync.RWMutex), 16 | } 17 | var wg sync.WaitGroup 18 | wg.Add(b.N) 19 | for i := 0; i < b.N; i++ { 20 | go func() { 21 | defer wg.Done() 22 | c.httpCall("req", 300*time.Millisecond) 23 | }() 24 | } 25 | wg.Wait() 26 | } 27 | -------------------------------------------------------------------------------- /mutexes/once/caching/good/good_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // cd "caching" 10 | // go test -bench . ./... 11 | func BenchmarkGood(b *testing.B) { 12 | b.ReportAllocs() 13 | c := client{ 14 | cache: map[string]*cacheEntry{}, 15 | mu: new(sync.Mutex), 16 | } 17 | var wg sync.WaitGroup 18 | wg.Add(b.N) 19 | for i := 0; i < b.N; i++ { 20 | go func() { 21 | defer wg.Done() 22 | c.httpCall("req", 300*time.Millisecond) 23 | }() 24 | } 25 | wg.Wait() 26 | } 27 | -------------------------------------------------------------------------------- /mutexes/once/deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Taken from sync/Once.Do source code 9 | // Because no call to Do returns until the one call to f returns, if f causes 10 | // Do to be called, it will deadlock. 11 | func main() { 12 | //deadlock1() 13 | deadlock2() 14 | } 15 | 16 | func deadlock1() { 17 | var once sync.Once 18 | once.Do(func() { 19 | // This Do won't return because the next Do is called, 20 | // thus causing the main func to deadlock 21 | fmt.Println("executing func once") 22 | once.Do(func() { 23 | fmt.Println("will it work?") 24 | }) 25 | }) 26 | } 27 | 28 | func deadlock2() { 29 | var onceA, onceB sync.Once 30 | var b func() 31 | a := func() { 32 | fmt.Println("before a") 33 | onceB.Do(b) 34 | fmt.Println("after a") 35 | } 36 | b = func() { 37 | fmt.Println("before b") 38 | // onceA.Do was already executed once 39 | // thus this call will never return 40 | // causing a deadlock in main 41 | onceA.Do(a) 42 | fmt.Println("after b") 43 | } 44 | 45 | onceA.Do(a) 46 | } 47 | -------------------------------------------------------------------------------- /mutexes/once/inc-dec/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var i int 10 | var once sync.Once 11 | inc := func() { 12 | i++ 13 | } 14 | dec := func() { 15 | i-- 16 | } 17 | 18 | once.Do(inc) 19 | once.Do(dec) 20 | 21 | fmt.Println("i:", i) 22 | } 23 | -------------------------------------------------------------------------------- /mutexes/once/once-implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // sync.Once implementation 10 | // https://github.com/golang/go/blob/master/src/sync/once.go#L14 11 | type once struct { 12 | done uint32 13 | mu sync.Mutex 14 | } 15 | 16 | // sync.Once.Do implementation 17 | // https://github.com/golang/go/blob/master/src/sync/once.go#L42 18 | func (o *once) Do(fn func()) { 19 | if atomic.LoadUint32(&o.done) == 1 { 20 | return 21 | } 22 | 23 | // Slow-path. 24 | o.mu.Lock() 25 | defer o.mu.Unlock() 26 | if o.done == 0 { 27 | defer atomic.StoreUint32(&o.done, 1) 28 | fn() 29 | } 30 | } 31 | 32 | // try running this with the -race flag 33 | // go run -race main.go 34 | func main() { 35 | var o once 36 | f := func(i int) func() { 37 | return func() { 38 | fmt.Println("printing once:", i) 39 | } 40 | } 41 | 42 | var wg sync.WaitGroup 43 | wg.Add(3) 44 | go func() { 45 | defer wg.Done() 46 | o.Do(f(1)) 47 | }() 48 | go func() { 49 | defer wg.Done() 50 | o.Do(f(2)) 51 | }() 52 | go func() { 53 | defer wg.Done() 54 | o.Do(f(3)) 55 | }() 56 | 57 | wg.Wait() 58 | } 59 | -------------------------------------------------------------------------------- /mutexes/once/race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var once sync.Once 10 | var wg sync.WaitGroup 11 | 12 | wg.Add(100) 13 | for i := 0; i < 100; i++ { 14 | go func(i int) { 15 | defer wg.Done() 16 | once.Do(func() { 17 | fmt.Println("i:", i) 18 | }) 19 | }(i) 20 | } 21 | 22 | wg.Wait() 23 | } 24 | -------------------------------------------------------------------------------- /mutexes/once/resync/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // sync.Once implementation 10 | // https://github.com/golang/go/blob/master/src/sync/once.go#L14 11 | type once struct { 12 | done uint32 13 | mu sync.Mutex 14 | } 15 | 16 | // sync.Once.Do implementation 17 | // https://github.com/golang/go/blob/master/src/sync/once.go#L42 18 | func (o *once) Do(fn func()) { 19 | if atomic.LoadUint32(&o.done) == 1 { 20 | return 21 | } 22 | 23 | // Slow-path. 24 | o.mu.Lock() 25 | defer o.mu.Unlock() 26 | if o.done == 0 { 27 | defer atomic.StoreUint32(&o.done, 1) 28 | fn() 29 | } 30 | } 31 | 32 | // https://github.com/matryer/resync 33 | func (o *once) Reset() { 34 | o.mu.Lock() 35 | defer o.mu.Unlock() 36 | atomic.StoreUint32(&o.done, 0) 37 | } 38 | 39 | // try running this with the -race flag 40 | // go run -race main.go 41 | func main() { 42 | var i int 43 | var o once 44 | add := func(n int) func() { 45 | return func() { 46 | i += n 47 | } 48 | } 49 | 50 | o.Do(add(10)) 51 | o.Reset() 52 | o.Do(add(-5)) 53 | o.Do(add(100)) 54 | 55 | fmt.Println("i:", i) 56 | } 57 | -------------------------------------------------------------------------------- /mutexes/once/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Worker struct { 9 | once sync.Once 10 | } 11 | 12 | func (w *Worker) Run() { 13 | w.once.Do(func() { 14 | fmt.Println("this will run only once") 15 | }) 16 | } 17 | 18 | func main() { 19 | w := Worker{} 20 | w.Run() 21 | w.Run() 22 | w.Run() 23 | } 24 | -------------------------------------------------------------------------------- /mutexes/pass-by-copy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // The below example will result in a deadlock, 9 | // because both calls to worker() pass a copy of Mutex. 10 | // Thus, the Unlock() call will happen on the copy, not original mu variable 11 | // Because of that, we have 2 consecutive calls to Lock(), 12 | // which results in a deadlock 13 | func main() { 14 | var mu sync.Mutex 15 | var wg sync.WaitGroup 16 | wg.Add(2) 17 | go func() { 18 | defer wg.Done() 19 | mu.Lock() 20 | worker(mu) 21 | }() 22 | go func() { 23 | defer wg.Done() 24 | mu.Lock() 25 | worker(mu) 26 | }() 27 | wg.Wait() 28 | } 29 | 30 | func worker(mu sync.Mutex) { 31 | fmt.Println("manipulating shared data") 32 | mu.Unlock() 33 | } 34 | -------------------------------------------------------------------------------- /mutexes/reentrant-lock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Reentrant/Recursive Lock 9 | func main() { 10 | var mutex sync.Mutex 11 | var rwMutex sync.RWMutex 12 | recursive(rwMutex.RLocker(), 10) 13 | // recursiveRWMutex(&rwMutex, 10) 14 | recursive(&mutex, 10) 15 | // recursiveMutex(&mutex, 10) 16 | } 17 | 18 | // NO DEADLOCK => READ LOCK is SHARED aka can be acquired by as many go routines at a time 19 | func recursiveRWMutex(mu *sync.RWMutex, n int) { 20 | if n < 1 { 21 | return 22 | } 23 | mu.RLock() 24 | defer mu.RUnlock() 25 | fmt.Println(n) 26 | recursiveRWMutex(mu, n-1) 27 | } 28 | 29 | // DEADLOCK => WRITE LOCK is EXCLUSIVE aka can only be acquired by a single go routine at a time 30 | func recursiveMutex(mu *sync.Mutex, n int) { 31 | if n < 1 { 32 | return 33 | } 34 | mu.Lock() 35 | defer mu.Unlock() 36 | fmt.Println(n) 37 | recursiveMutex(mu, n-1) 38 | } 39 | 40 | func recursive(mu sync.Locker, n int) { 41 | if n < 1 { 42 | return 43 | } 44 | mu.Lock() 45 | defer mu.Unlock() 46 | fmt.Println(n) 47 | recursive(mu, n-1) 48 | } 49 | -------------------------------------------------------------------------------- /mutexes/rlocker-write/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Using an RLocker for Write scenarios, 9 | // has the same effect as not using any locks at all => race condition. 10 | // RLocker is only useful for Read only scenarios, 11 | // specifically if a function depends on a sync.Locker, 12 | // allowing any kind of mutex to be passed as an argument 13 | func main() { 14 | var count int 15 | var wg sync.WaitGroup 16 | var mu sync.RWMutex 17 | l := mu.RLocker() 18 | 19 | wg.Add(100) 20 | for i := 0; i < 100; i++ { 21 | go func() { 22 | defer wg.Done() 23 | l.Lock() 24 | // ++ operator does both read and write operations, 25 | // thus, this type of lock is not enough to prevent 26 | // any race conditions 27 | count++ 28 | l.Unlock() 29 | }() 30 | } 31 | 32 | wg.Wait() 33 | fmt.Println("count:", count) 34 | } 35 | -------------------------------------------------------------------------------- /mutexes/rwmutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // make sure the run the below program using the -race flag 9 | // go run -race main.go 10 | func main() { 11 | d := newDB() 12 | var wg sync.WaitGroup 13 | wg.Add(2) 14 | go func() { 15 | d.set("k1", "v1") 16 | wg.Done() 17 | }() 18 | go func() { 19 | d.set("k1", "v2") 20 | wg.Done() 21 | }() 22 | wg.Wait() 23 | fmt.Println(d.get("k1")) 24 | } 25 | 26 | func newDB() db { 27 | return db{values: map[string]string{}} 28 | } 29 | 30 | type db struct { 31 | values map[string]string 32 | mu sync.RWMutex 33 | } 34 | 35 | func (d *db) set(key, value string) { 36 | d.mu.Lock() 37 | defer d.mu.Unlock() 38 | d.values[key] = value 39 | } 40 | 41 | func (d *db) get(key string) string { 42 | d.mu.RLock() 43 | defer d.mu.RUnlock() 44 | return d.values[key] 45 | } 46 | -------------------------------------------------------------------------------- /mutexes/semaphore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | var lock int32 10 | 11 | // try running this with the -race flag 12 | // go run -race main.go 13 | func main() { 14 | var count int 15 | var wg sync.WaitGroup 16 | 17 | // if we increase the number of go routines 18 | // this could quickly be detected as a race condition 19 | // due to too many go routines' status being awake 20 | // Also there's no way we can control the go routines queue 21 | // or have access to the runtime internals 22 | wg.Add(1000) 23 | for i := 0; i < 1000; i++ { 24 | go func() { 25 | defer wg.Done() 26 | acquire() 27 | count++ 28 | release() 29 | }() 30 | } 31 | 32 | wg.Wait() 33 | fmt.Println("count", count) 34 | } 35 | 36 | func acquire() { 37 | for atomic.CompareAndSwapInt32(&lock, 0, 1) { 38 | } 39 | } 40 | 41 | func release() { 42 | for atomic.CompareAndSwapInt32(&lock, 1, 0) { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mutexes/simple-counter/norace-mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // try running the program using the -race flag 9 | // go run -race main.go 10 | func main() { 11 | var count int 12 | var mu sync.Mutex 13 | var wg sync.WaitGroup 14 | wg.Add(1000) 15 | for i := 0; i < 1000; i++ { 16 | go func() { 17 | defer wg.Done() 18 | mu.Lock() 19 | count++ 20 | mu.Unlock() 21 | }() 22 | } 23 | wg.Wait() 24 | fmt.Println("count", count) 25 | } 26 | -------------------------------------------------------------------------------- /mutexes/simple-counter/norace-waitgroup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // try running the program using the -race flag 9 | // go run -race main.go 10 | func main() { 11 | var count int 12 | var wg sync.WaitGroup 13 | for i := 0; i < 1000; i++ { 14 | // even if this code executes concurrently, 15 | // it will perform worse than normal sequential code 16 | // because of the concurrency overhead and unnecessary wait time 17 | wg.Add(1) 18 | go func() { 19 | count++ 20 | wg.Done() 21 | }() 22 | wg.Wait() 23 | } 24 | fmt.Println("count", count) 25 | } 26 | -------------------------------------------------------------------------------- /mutexes/simple-counter/race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Try running the following program with the -race flag 9 | // go run -race main.go 10 | // the order does not matter at all, the correctness of our concurrent code does 11 | // In the below example we attempt to fetch, increment and update the count variable. 12 | // While everything seems to be correct, our program does not have correctness 13 | // when it comes to running our concurrent code. 14 | // We assume each go routine will have had incremented the count variable when fetching its value, 15 | // which is not the case. This is why we end up with different results. Sometimes we increment 16 | // based on the incremented value, other times we only increment based on the initial value. 17 | func main() { 18 | var count int 19 | var wg sync.WaitGroup 20 | wg.Add(1000) 21 | for i := 0; i < 1000; i++ { 22 | go func() { 23 | defer wg.Done() 24 | count++ 25 | }() 26 | } 27 | wg.Wait() 28 | fmt.Println("count", count) 29 | } 30 | -------------------------------------------------------------------------------- /mutexes/starvation/polite-greedy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | var mu sync.Mutex 12 | 13 | wg.Add(2) 14 | go greedy(&wg, &mu) 15 | go polite(&wg, &mu) 16 | wg.Wait() 17 | } 18 | 19 | func greedy(wg *sync.WaitGroup, lock *sync.Mutex) { 20 | defer wg.Done() 21 | var count int 22 | for begin := time.Now(); time.Since(begin) < time.Second; { 23 | lock.Lock() 24 | time.Sleep(3 * time.Nanosecond) 25 | lock.Unlock() 26 | count++ 27 | // will allow the processor to move on processing other go routines 28 | // will also avoid starvation 29 | //runtime.Gosched() 30 | } 31 | fmt.Println("greedy worker executed", count, "times") 32 | } 33 | 34 | func polite(wg *sync.WaitGroup, lock *sync.Mutex) { 35 | defer wg.Done() 36 | var count int 37 | for begin := time.Now(); time.Since(begin) < time.Second; { 38 | lock.Lock() 39 | time.Sleep(time.Nanosecond) 40 | lock.Unlock() 41 | lock.Lock() 42 | time.Sleep(time.Nanosecond) 43 | lock.Unlock() 44 | lock.Lock() 45 | time.Sleep(time.Nanosecond) 46 | lock.Unlock() 47 | count++ 48 | } 49 | fmt.Println("polite worker executed", count, "times") 50 | } 51 | -------------------------------------------------------------------------------- /mutexes/syncmap/README.md: -------------------------------------------------------------------------------- 1 | # sync.Map 2 | 3 | mention atomic.Value being used 4 | 5 | ### Examples 6 | 7 | - [sync.Map](https://github.com/golang-basics/concurrency/blob/master/mutexes/syncmap/main.go) 8 | 9 | [Home](https://github.com/golang-basics/concurrency) 10 | -------------------------------------------------------------------------------- /mutexes/syncmap/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | m := sync.Map{} 10 | // keys and values are interface{} 11 | m.Store("k1", "v1") 12 | m.Store("k2", "v2") 13 | m.Store("k3", "v3") 14 | 15 | value, ok := m.Load("k1") 16 | fmt.Println(value, ok) 17 | value, ok = m.Load("kn") 18 | fmt.Println(value, ok) 19 | 20 | m.Range(func(key, value interface{}) bool { 21 | fmt.Println("key:", key, "value:", value) 22 | // if we return false the range ends 23 | // return bool acts as a break 24 | return true 25 | }) 26 | m.Delete("k1") 27 | fmt.Println(m.Load("k1")) 28 | // QUESTION: How does one get the length of sync.Map? 29 | // ANSWER: implement it yourself by using an atomic counter 30 | 31 | k2, loaded := m.LoadAndDelete("k2") 32 | fmt.Println("load and delete k2:", k2, loaded) 33 | k2, ok = m.Load("k2") 34 | fmt.Println("load k2", k2, ok) 35 | 36 | k2, loaded = m.LoadOrStore("k2", "v2") 37 | fmt.Println("load or store k2 1st:", k2, loaded) 38 | k2, loaded = m.LoadOrStore("k2", "v2") 39 | fmt.Println("load or store k2 2nd:", k2, loaded) 40 | } 41 | -------------------------------------------------------------------------------- /mutexes/syncmap/benchmarks/rwmutex_vs_syncmap_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | -------------------------------------------------------------------------------- /patterns/cancellation/method-chaining/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/cancellation" 7 | ) 8 | 9 | func main() { 10 | p := cancellation.NewIntPipeline(1, 2, 3) 11 | defer p.Done() 12 | //for i := range p.Inc().Sq().Done().Dec().Res() { 13 | for i := range p.Inc().Sq().Dec().Res() { 14 | fmt.Println(i) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /patterns/cancellation/simple-functions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/cancellation" 7 | ) 8 | 9 | var ( 10 | inc = cancellation.Inc 11 | dec = cancellation.Dec 12 | sq = cancellation.Sq 13 | ) 14 | 15 | func main() { 16 | done := make(chan struct{}) 17 | defer close(done) 18 | nums := cancellation.Gen(done, 1, 2, 3) 19 | for v := range dec(done, sq(done, inc(done, nums))) { 20 | fmt.Println("value:", v) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /patterns/circuit-breaker/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/steevehook/circuit-breaker 2 | 3 | go 1.16 4 | 5 | require github.com/sony/gobreaker v0.4.1 // indirect 6 | -------------------------------------------------------------------------------- /patterns/circuit-breaker/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 3 | github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= 4 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 7 | -------------------------------------------------------------------------------- /patterns/codegen/templates/fanin.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | const FanInTpl = `package {{.Pkg}} 4 | 5 | import "sync" 6 | 7 | func FanIn(done chan struct{}, inputs ...<-chan {{.Type}}) <-chan {{.Type}} { 8 | out := make(chan {{.Type}}) 9 | var wg sync.WaitGroup 10 | wg.Add(len(inputs)) 11 | 12 | for _, in := range inputs { 13 | go func(numbers <-chan {{.Type}}) { 14 | defer wg.Done() 15 | for n := range numbers { 16 | select { 17 | case <-done: 18 | return 19 | case out <- n: 20 | } 21 | } 22 | }(in) 23 | } 24 | go func() { 25 | wg.Wait() 26 | close(out) 27 | }() 28 | 29 | return out 30 | }` 31 | -------------------------------------------------------------------------------- /patterns/codegen/templates/repeat.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | const RepeatTpl = `package {{.Pkg}} 4 | 5 | func Repeat(done <-chan struct{}, values ...{{.Type}}) <-chan {{.Type}} { 6 | out := make(chan {{.Type}}) 7 | go func() { 8 | defer close(out) 9 | for { 10 | for _, v := range values { 11 | select { 12 | case <-done: 13 | return 14 | case out <- v: 15 | } 16 | } 17 | } 18 | }() 19 | return out 20 | }` 21 | -------------------------------------------------------------------------------- /patterns/codegen/templates/repeatfn.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | const RepeatFnTpl = `package {{.Pkg}} 4 | 5 | func RepeatFn(done <-chan struct{}, fn func() {{.Type}}) <-chan {{.Type}} { 6 | out := make(chan {{.Type}}) 7 | go func() { 8 | defer close(out) 9 | for { 10 | select { 11 | case <-done: 12 | return 13 | case out <- fn(): 14 | } 15 | } 16 | }() 17 | return out 18 | }` 19 | -------------------------------------------------------------------------------- /patterns/codegen/templates/take.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | const TakeTpl = `package {{.Pkg}} 4 | 5 | func Take(done <-chan struct{}, in <-chan {{.Type}}, num int) <-chan {{.Type}} { 6 | out := make(chan {{.Type}}) 7 | go func() { 8 | defer close(out) 9 | for i := 0; i < num; i++ { 10 | select { 11 | case <-done: 12 | return 13 | case out <- <-in: 14 | } 15 | } 16 | }() 17 | return out 18 | }` 19 | -------------------------------------------------------------------------------- /patterns/context/context-keys/collision/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "concurrency/patterns/context/context-keys/colision/mycontext" 8 | ) 9 | 10 | const ctxKey = "ctxKey" 11 | 12 | func main() { 13 | ctx := mycontext.WithSomeValue(context.Background(), "main") 14 | 15 | ctx = req(ctx) 16 | fmt.Println("ctx main value:", mycontext.SomeValue(ctx)) 17 | 18 | val := ctx.Value(ctxKey).(string) 19 | fmt.Println("ctx req value:", val) 20 | } 21 | 22 | func req(ctx context.Context) context.Context { 23 | return context.WithValue(ctx, ctxKey, "req") 24 | } 25 | -------------------------------------------------------------------------------- /patterns/context/context-keys/collision/mycontext/mycontext.go: -------------------------------------------------------------------------------- 1 | package mycontext 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | const ctxKey = "ctxKey" 8 | 9 | func WithSomeValue(ctx context.Context, value string) context.Context { 10 | return context.WithValue(ctx, ctxKey, value) 11 | } 12 | 13 | func SomeValue(ctx context.Context) string { 14 | someValue, ok := ctx.Value(ctxKey).(string) 15 | if !ok { 16 | return "" 17 | } 18 | return someValue 19 | } 20 | -------------------------------------------------------------------------------- /patterns/context/context-keys/private-keys/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "concurrency/patterns/context/context-keys/private-keys/mycontext" 8 | ) 9 | 10 | const ctxKey = "ctxKey" 11 | 12 | func main() { 13 | ctx := mycontext.WithSomeValue(context.Background(), "main") 14 | 15 | ctx = req(ctx) 16 | fmt.Println("ctx main value:", mycontext.SomeValue(ctx)) 17 | 18 | val := ctx.Value(ctxKey).(string) 19 | fmt.Println("ctx req value:", val) 20 | } 21 | 22 | func req(ctx context.Context) context.Context { 23 | return context.WithValue(ctx, ctxKey, "req") 24 | } 25 | -------------------------------------------------------------------------------- /patterns/context/context-keys/private-keys/mycontext/mycontext.go: -------------------------------------------------------------------------------- 1 | package mycontext 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type key string 8 | 9 | const ctxKey = key("ctxKey") 10 | 11 | func WithSomeValue(ctx context.Context, value string) context.Context { 12 | return context.WithValue(ctx, ctxKey, value) 13 | } 14 | 15 | func SomeValue(ctx context.Context) string { 16 | someValue, ok := ctx.Value(ctxKey).(string) 17 | if !ok { 18 | return "" 19 | } 20 | return someValue 21 | } 22 | -------------------------------------------------------------------------------- /patterns/context/mycontext/mycontext.go: -------------------------------------------------------------------------------- 1 | package mycontext 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | type key string 9 | 10 | const ( 11 | someValueKey = key("some_value") 12 | someValueRequestKey = "X-Some-Value" 13 | ) 14 | 15 | func WithSomeValue(ctx context.Context, value string) context.Context { 16 | return context.WithValue(ctx, someValueKey, value) 17 | } 18 | 19 | func SomeValue(ctx context.Context) string { 20 | someValue, ok := ctx.Value(someValueKey).(string) 21 | if !ok { 22 | return "" 23 | } 24 | return someValue 25 | } 26 | 27 | func WithSomeValueRequest(req *http.Request) *http.Request { 28 | req.Header.Set(someValueRequestKey, SomeValue(req.Context())) 29 | return req 30 | } 31 | 32 | func SomeValueFromRequest(req *http.Request) string { 33 | return req.Header.Get(someValueRequestKey) 34 | } 35 | -------------------------------------------------------------------------------- /patterns/context/simple-map-keys/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | type foo int 7 | type bar int 8 | 9 | people := map[interface{}]string{ 10 | 0: "Nobody", 11 | foo(0000): "John", 12 | 0 + 0i + bar(0): "Steve", 13 | int32(0): "int32", 14 | int64(0): "int64", 15 | } 16 | fmt.Println(people) 17 | fmt.Println(people[0000]) 18 | } 19 | -------------------------------------------------------------------------------- /patterns/daisy-chain/functions-chain/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func pass(left, right chan func(), i int) { 6 | fn := <-right 7 | left <- func() { 8 | fn() 9 | fmt.Println("gopher:", i) 10 | } 11 | } 12 | 13 | func main() { 14 | leftmost := make(chan func()) 15 | left := leftmost 16 | right := leftmost 17 | 18 | for i := 0; i < 10; i++ { 19 | right = make(chan func()) 20 | go pass(left, right, i+1) 21 | left = right 22 | } 23 | 24 | right <- func() { 25 | fmt.Println("initial function") 26 | } 27 | fn := <-leftmost 28 | fn() 29 | } 30 | -------------------------------------------------------------------------------- /patterns/error-handling/error-basics/basic-wrap-unwrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | err := request() 12 | if err != nil { 13 | for err != nil { 14 | fmt.Println("err:", err) 15 | err = unwrap(err) 16 | } 17 | os.Exit(1) 18 | } 19 | fmt.Println("success") 20 | } 21 | 22 | func wrap(err error, msg string) error { 23 | return fmt.Errorf("%s: %v", msg, err) 24 | } 25 | 26 | func unwrap(err error) error { 27 | es := strings.Split(err.Error(), ":") 28 | if len(es) == 1 { 29 | return nil 30 | } 31 | return errors.New(strings.TrimSpace(strings.Join(es[1:], ":"))) 32 | } 33 | 34 | func request() error { 35 | return wrap(controller(), "error inside request") 36 | } 37 | 38 | func controller() error { 39 | return wrap(service(), "error inside controller") 40 | } 41 | 42 | func service() error { 43 | return wrap(repo(), "error inside service") 44 | } 45 | 46 | func repo() error { 47 | return errors.New("error inside repo") 48 | } 49 | -------------------------------------------------------------------------------- /patterns/error-handling/error-basics/errors-is-as/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var errNotFound = errors.New("resource not found") 9 | 10 | type customError struct { 11 | message string 12 | err error 13 | } 14 | 15 | func (e customError) Error() string { 16 | return e.message 17 | } 18 | 19 | func (e customError) Unwrap() error { 20 | return e.err 21 | } 22 | 23 | func main() { 24 | err := request() 25 | if !errors.As(err, &customError{}) { 26 | fmt.Println("something unexpected happened") 27 | return 28 | } 29 | if errors.Is(err, errNotFound) { 30 | fmt.Println("could not find resource") 31 | } 32 | } 33 | 34 | func request() error { 35 | return customError{ 36 | message: "request error", 37 | err: controller(), 38 | } 39 | } 40 | 41 | func controller() error { 42 | return fmt.Errorf("%w: %v", service(), "controller error") 43 | } 44 | 45 | func service() error { 46 | return fmt.Errorf("%w: %v", repo(), "service error") 47 | } 48 | 49 | func repo() error { 50 | return fmt.Errorf("%w: %v", errNotFound, "repo error") 51 | } 52 | -------------------------------------------------------------------------------- /patterns/error-handling/error-basics/errors-wrap-unwrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // The functions: As(), Is(), Unwrap() inside the errors package 10 | // are available to use as of Go 1.13, which was inspired from: 11 | // https://github.com/pkg/errors 12 | // For more info on new error features in Go 1.13, check out: 13 | // https://blog.golang.org/go1.13-errors 14 | func main() { 15 | err := request() 16 | if err != nil { 17 | for err != nil { 18 | fmt.Println("err:", err) 19 | err = errors.Unwrap(err) 20 | } 21 | os.Exit(1) 22 | } 23 | fmt.Println("success") 24 | } 25 | 26 | func request() error { 27 | return fmt.Errorf("request error: %w", controller()) 28 | } 29 | 30 | func controller() error { 31 | return fmt.Errorf("controller error: %w", service()) 32 | } 33 | 34 | func service() error { 35 | return fmt.Errorf("service error: %w", repo()) 36 | } 37 | 38 | func repo() error { 39 | return errors.New("repo error") 40 | } 41 | -------------------------------------------------------------------------------- /patterns/error-handling/error-basics/simple-custom-error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | err := request(-2) 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | fmt.Println("successful request") 14 | } 15 | 16 | type httpError struct { 17 | message string 18 | statusCode int 19 | } 20 | 21 | func (e httpError) Error() string { 22 | return e.message 23 | } 24 | 25 | func request(n int) error { 26 | if n < 0 { 27 | return httpError{ 28 | message: "negative number provided", 29 | statusCode: 400, 30 | } 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /patterns/error-handling/http/.gitignore: -------------------------------------------------------------------------------- 1 | bookings.db 2 | app.log 3 | -------------------------------------------------------------------------------- /patterns/error-handling/http/README.md: -------------------------------------------------------------------------------- 1 | # HTTP REST API (Error Handling) 2 | 3 | [Home](https://github.com/golang-basics/concurrency) 4 | -------------------------------------------------------------------------------- /patterns/error-handling/http/controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/julienschmidt/httprouter" 7 | 8 | "github.com/steevehook/http/logging" 9 | ) 10 | 11 | // routeParam fetches route param from context 12 | func routeParam(r *http.Request, name string) string { 13 | ctx := r.Context() 14 | psCtx := ctx.Value(httprouter.ParamsKey) 15 | ps, ok := psCtx.(httprouter.Params) 16 | 17 | if !ok { 18 | logging.Logger().Error("could not extract params from context") 19 | return "" 20 | } 21 | return ps.ByName(name) 22 | } 23 | -------------------------------------------------------------------------------- /patterns/error-handling/http/controllers/get_booking.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/steevehook/http/logging" 8 | "github.com/steevehook/http/models" 9 | "github.com/steevehook/http/transport" 10 | ) 11 | 12 | type bookingGetter interface { 13 | GetBooking(ctx context.Context, req models.GetBookingRequest) (models.Booking, error) 14 | } 15 | 16 | func getBooking(service bookingGetter) http.HandlerFunc { 17 | return func(w http.ResponseWriter, r *http.Request) { 18 | logger := logging.Logger() 19 | id := routeParam(r, idRouteParam) 20 | req := models.GetBookingRequest{ 21 | ID: id, 22 | } 23 | 24 | booking, err := service.GetBooking(r.Context(), req) 25 | if err != nil { 26 | transport.SendHTTPError(w, err) 27 | return 28 | } 29 | 30 | logger.Info("successfully fetched booking") 31 | transport.SendJSON(w, http.StatusOK, booking) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /patterns/error-handling/http/controllers/not_found.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/steevehook/http/models" 7 | "github.com/steevehook/http/transport" 8 | ) 9 | 10 | func notFound() http.HandlerFunc { 11 | return func(w http.ResponseWriter, r *http.Request) { 12 | transport.SendHTTPError(w, models.ResourceNotFoundError{}) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /patterns/error-handling/http/controllers/routes.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/julienschmidt/httprouter" 7 | ) 8 | 9 | const ( 10 | idRouteParam = "id" 11 | ) 12 | 13 | type service interface { 14 | bookingGetter 15 | bookingCreator 16 | } 17 | 18 | func NewRouter(svc service) *httprouter.Router { 19 | router := httprouter.New() 20 | 21 | router.Handler(http.MethodGet, "/bookings/:"+idRouteParam, getBooking(svc)) 22 | router.Handler(http.MethodPost, "/bookings", createBooking(svc)) 23 | router.NotFound = notFound() 24 | 25 | return router 26 | } 27 | -------------------------------------------------------------------------------- /patterns/error-handling/http/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/boltdb/bolt" 7 | "go.uber.org/zap" 8 | 9 | "github.com/steevehook/http/logging" 10 | ) 11 | 12 | func Init() (DB, error) { 13 | logger := logging.Logger() 14 | db, err := bolt.Open( 15 | "bookings.db", 16 | 0600, 17 | &bolt.Options{ 18 | Timeout: 1 * time.Second, 19 | }, 20 | ) 21 | if err != nil { 22 | logger.Error("could not open bolt database", zap.Error(err)) 23 | return DB{}, err 24 | } 25 | 26 | return DB{db}, nil 27 | } 28 | 29 | type DB struct { 30 | *bolt.DB 31 | } 32 | 33 | func (d DB) Stop() error { 34 | logger := logging.Logger() 35 | logger.Info("closing the database") 36 | 37 | err := d.Close() 38 | if err != nil { 39 | logger.Error("could not close the database", zap.Error(err)) 40 | return err 41 | } 42 | 43 | logger.Info("database was successfully closed") 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /patterns/error-handling/http/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/steevehook/http 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/boltdb/bolt v1.3.1 7 | github.com/google/uuid v1.2.0 8 | github.com/julienschmidt/httprouter v1.3.0 9 | go.uber.org/zap v1.17.0 10 | golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /patterns/error-handling/http/logging/http.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | type writerFunc func(p []byte) (n int, err error) 10 | 11 | func (w writerFunc) Write(p []byte) (n int, err error) { 12 | return w(p) 13 | } 14 | 15 | // HTTPServerLogger returns a log.Logger that logs HTTP server internal errors 16 | func HTTPServerLogger() *log.Logger { 17 | logger := Logger().With(zap.String("source", "http_server")) 18 | return log.New( 19 | writerFunc(func(p []byte) (n int, err error) { 20 | logger.Error(string(p)) 21 | return len(p), nil 22 | }), 23 | "", 24 | 0, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /patterns/error-handling/http/logging/logging.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "go.uber.org/zap/zapcore" 6 | ) 7 | 8 | var l *zap.Logger 9 | 10 | func Init() error { 11 | var logLevel zapcore.Level 12 | err := logLevel.Set("debug") 13 | if err != nil { 14 | return err 15 | } 16 | 17 | zapConfig := zap.NewProductionConfig() 18 | zapConfig.Level = zap.NewAtomicLevelAt(logLevel) 19 | zapConfig.OutputPaths = []string{"stdout", "app.log"} 20 | zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 21 | logger, err := zapConfig.Build() 22 | defer func() { 23 | _ = logger.Sync() 24 | }() 25 | l = logger 26 | return err 27 | } 28 | 29 | func Logger() *zap.Logger { 30 | return l 31 | } 32 | -------------------------------------------------------------------------------- /patterns/error-handling/http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "syscall" 7 | 8 | "github.com/steevehook/http/app" 9 | "github.com/steevehook/http/db" 10 | "github.com/steevehook/http/repositories" 11 | "github.com/steevehook/http/worker" 12 | ) 13 | 14 | func main() { 15 | d, err := db.Init() 16 | if err != nil { 17 | log.Fatalf("could not initialize database: %v", err) 18 | } 19 | 20 | repo := repositories.NewBookings(d) 21 | err = repo.Init(70) 22 | if err != nil { 23 | log.Fatalf("could not initialize repository: %v", err) 24 | } 25 | 26 | a, err := app.Init(repo) 27 | if err != nil { 28 | log.Fatalf("could not initialize application: %v", err) 29 | } 30 | 31 | w, err := worker.Init(repo) 32 | if err != nil { 33 | log.Fatalf("could not initialize worker: %v", err) 34 | } 35 | 36 | go func() { 37 | if err := a.Start(); err != nil { 38 | log.Fatalf("could not start application: %v", err) 39 | } 40 | }() 41 | go func() { 42 | if err := w.Start(); err != nil { 43 | log.Fatalf("could not start worker: %v", err) 44 | } 45 | }() 46 | 47 | app.ListenToSignals([]os.Signal{os.Interrupt, syscall.SIGTERM}, a, w, d) 48 | } 49 | -------------------------------------------------------------------------------- /patterns/error-handling/http/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const DefaultHotelID = "default_hotel_id" 8 | 9 | // Booking represents the Booking model 10 | type Booking struct { 11 | ID string `json:"id"` 12 | HotelID string `json:"hotel_id"` 13 | RoomNumber int `json:"room_number"` 14 | CreatedAt time.Time `json:"created_at"` 15 | StartsAt time.Time `json:"starts_at"` 16 | EndsAt time.Time `json:"ends_at"` 17 | } 18 | -------------------------------------------------------------------------------- /patterns/error-handling/http/models/requests.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // CreateBookingRequest represents the request for creating a booking 8 | type CreateBookingRequest struct { 9 | Start time.Time `json:"start"` // i.e 2021-06-13T09:30:00Z 10 | End time.Time `json:"end"` // i.e 2021-06-14T09:30:00Z 11 | HotelID string `json:"hotel_id"` // i.e default_hotel_id 12 | } 13 | 14 | // GetBookingRequest represents the request for fetching a booking 15 | type GetBookingRequest struct { 16 | ID string `json:"id"` 17 | } 18 | -------------------------------------------------------------------------------- /patterns/error-handling/logging/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var errInvalidAge = errors.New("person's age must be between 1-100") 8 | 9 | type HTTPError struct { 10 | Message string `json:"message"` 11 | Err error `json:"-"` 12 | } 13 | 14 | func (e HTTPError) Error() string { 15 | return e.Message 16 | } 17 | 18 | func (e HTTPError) Unwrap() error { 19 | return e.Err 20 | } 21 | -------------------------------------------------------------------------------- /patterns/error-handling/logging/logging.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func loggerInit() { 10 | log.SetOutput(os.Stdout) 11 | log.SetFlags(log.Ltime | log.LUTC) 12 | log.SetPrefix("[LOG] ") 13 | } 14 | 15 | func logInfo(format string, args ...interface{}) { 16 | logWithPrefix("INFO", format, args) 17 | } 18 | 19 | func logDebug(format string, args ...interface{}) { 20 | logWithPrefix("DEBUG", format, args) 21 | } 22 | 23 | func logError(format string, args ...interface{}) { 24 | logWithPrefix("ERROR", format, args) 25 | } 26 | 27 | func logWithPrefix(prefix, format string, args []interface{}) { 28 | log.SetPrefix(fmt.Sprintf("[%s] ", prefix)) 29 | log.Printf(format, args...) 30 | } 31 | -------------------------------------------------------------------------------- /patterns/fan-in-out/inefficient/main.go: -------------------------------------------------------------------------------- 1 | //go:generate go run ../../codegen/main.go -tpl=repeatfn -type=int 2 | //go:generate go run ../../codegen/main.go -tpl=take -type=int 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | done := make(chan struct{}) 13 | defer close(done) 14 | random := func() int { 15 | return rand.Intn(50000000) 16 | } 17 | start := time.Now() 18 | 19 | randIntStream := RepeatFn(done, random) 20 | fmt.Println("primes:") 21 | for prime := range Take(done, primeFinder(done, randIntStream), 10) { 22 | fmt.Println("prime:", prime) 23 | } 24 | fmt.Printf("search took: %v", time.Since(start)) 25 | } 26 | 27 | func primeFinder(done <-chan struct{}, intStream <-chan int) <-chan int { 28 | primeStream := make(chan int) 29 | go func() { 30 | defer close(primeStream) 31 | for integer := range intStream { 32 | integer -= 1 33 | prime := true 34 | for divisor := integer - 1; divisor > 1; divisor-- { 35 | if integer%divisor == 0 { 36 | prime = false 37 | break 38 | } 39 | } 40 | 41 | if prime { 42 | select { 43 | case <-done: 44 | return 45 | case primeStream <- integer: 46 | } 47 | } 48 | } 49 | }() 50 | return primeStream 51 | } 52 | -------------------------------------------------------------------------------- /patterns/fan-in-out/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/fanin" 7 | "concurrency/patterns/fanout" 8 | "concurrency/patterns/generator" 9 | ) 10 | 11 | var ( 12 | intGen = generator.OddIntGen 13 | fanOut = fanout.FanOut 14 | fanIn = fanin.FanIn 15 | ) 16 | 17 | func main() { 18 | done := make(chan struct{}) 19 | defer close(done) 20 | 21 | odd := intGen(20) 22 | c1 := fanOut(done, odd) 23 | c2 := fanOut(done, odd) 24 | c3 := fanOut(done, odd) 25 | 26 | for v := range fanIn(done, c1, c2, c3) { 27 | fmt.Println("value:", v) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /patterns/fanin/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/fanin" 7 | "concurrency/patterns/generator" 8 | ) 9 | 10 | func main() { 11 | done := make(chan struct{}) 12 | defer close(done) 13 | 14 | odd := generator.OddIntGen(5) 15 | even := generator.EvenIntGen(5) 16 | hex := generator.HexIntGen(5) 17 | out := fanin.FanIn(done, odd, even, hex) 18 | 19 | for n := range out { 20 | fmt.Println("fanned number:", n) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /patterns/fanin/fanin.go: -------------------------------------------------------------------------------- 1 | package fanin 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // FanIn reads from multiple channels and writes into 1 final channel 8 | // The FAN-IN aka Multiplexing pattern states that a function receives multiple channels as inputs 9 | // It reads each input and sends all the values into 1 final output channel 10 | func FanIn(done chan struct{}, inputs ...<-chan int) <-chan int { 11 | out := make(chan int) 12 | var wg sync.WaitGroup 13 | wg.Add(len(inputs)) 14 | 15 | for _, in := range inputs { 16 | go func(numbers <-chan int) { 17 | defer wg.Done() 18 | for n := range numbers { 19 | select { 20 | case <-done: 21 | return 22 | case out <- n: 23 | } 24 | } 25 | }(in) 26 | } 27 | go func() { 28 | wg.Wait() 29 | close(out) 30 | }() 31 | 32 | return out 33 | } 34 | -------------------------------------------------------------------------------- /patterns/fanout/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/fanout" 7 | "concurrency/patterns/generator" 8 | ) 9 | 10 | func main() { 11 | done := make(chan struct{}) 12 | defer close(done) 13 | 14 | odd := generator.OddIntGen(10) 15 | c1 := fanout.FanOut(done, odd) 16 | c2 := fanout.FanOut(done, odd) 17 | c3 := fanout.FanOut(done, odd) 18 | c4 := fanout.FanOut(done, odd) 19 | 20 | display(c1, "c1") 21 | display(c2, "c2") 22 | display(c3, "c3") 23 | display(c4, "c4") 24 | } 25 | 26 | func display(in <-chan int, label string) { 27 | for v := range in { 28 | fmt.Printf("value %s: %d\n", label, v) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /patterns/fanout/fanout.go: -------------------------------------------------------------------------------- 1 | package fanout 2 | 3 | // FanOut reads all values from a given input channel and sends each value to a resulting channel 4 | // The FAN-OUT pattern states that multiple invocation of this function 5 | // will generate multiple go routines to read from the same channel till the input channel is closed 6 | // This allows for better work distribution 7 | func FanOut(done chan struct{}, input <-chan int) <-chan int { 8 | out := make(chan int) 9 | go func() { 10 | for v := range input { 11 | select { 12 | case <-done: 13 | return 14 | case out <- v: 15 | } 16 | } 17 | close(out) 18 | }() 19 | return out 20 | } 21 | -------------------------------------------------------------------------------- /patterns/generator/cmd/main.go: -------------------------------------------------------------------------------- 1 | // The generator pattern is pretty simple and it resembles that 2 | // there is a function which generates a channel of values 3 | // and returns it at the end, while values are pushed by go routine(s) 4 | // the channel must eventually close to avoid dead locks 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "concurrency/patterns/generator" 12 | ) 13 | 14 | func main() { 15 | for evenInt := range generator.EvenIntGen(5) { 16 | fmt.Println("even int:", evenInt) 17 | } 18 | for oddInt := range generator.OddIntGen(5) { 19 | fmt.Println("odd int:", oddInt) 20 | } 21 | for hexInt := range generator.HexIntGen(5) { 22 | fmt.Println("hex int:", hexInt) 23 | } 24 | for word := range generator.WordGen(5) { 25 | fmt.Println("word:", word) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /patterns/generators/benchmarks/typed_vs_generic_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "testing" 5 | 6 | "concurrency/patterns/generators" 7 | ) 8 | 9 | var ( 10 | toInt = generators.ToInt 11 | repeat = generators.Repeat 12 | intRepeat = generators.IntRepeat 13 | take = generators.Take 14 | intTake = generators.IntTake 15 | ) 16 | 17 | func BenchmarkGenericGenerators(b *testing.B) { 18 | done := make(chan struct{}) 19 | defer close(done) 20 | 21 | for i := 0; i < b.N; i++ { 22 | for range toInt(done, take(done, repeat(done, 1), 10)) { 23 | } 24 | } 25 | } 26 | 27 | func BenchmarkTypedGenerators(b *testing.B) { 28 | done := make(chan struct{}) 29 | defer close(done) 30 | 31 | for i := 0; i < b.N; i++ { 32 | for range intTake(done, intRepeat(done, 1), 10) { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /patterns/generators/repeat-fn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "concurrency/patterns/generators" 8 | ) 9 | 10 | func main() { 11 | done := make(chan struct{}) 12 | defer close(done) 13 | 14 | randFn := func() interface{} { return rand.Int() } 15 | for num := range generators.Take(done, generators.RepeatFn(done, randFn), 10) { 16 | fmt.Println(num) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /patterns/generators/repeat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "concurrency/patterns/generators" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | done := make(chan struct{}) 11 | go func() { 12 | select { 13 | case <-time.After(3 * time.Second): 14 | close(done) 15 | } 16 | }() 17 | 18 | for num := range generators.Repeat(done, 1, 2, 3) { 19 | fmt.Printf("%d ", num) 20 | time.Sleep(50 * time.Millisecond) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /patterns/generators/take/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/generators" 7 | ) 8 | 9 | func main() { 10 | done := make(chan struct{}) 11 | defer close(done) 12 | 13 | for num := range generators.Take(done, generators.Repeat(done, 1), 10) { 14 | fmt.Printf("%v ", num) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /patterns/generators/typed_generators.go: -------------------------------------------------------------------------------- 1 | package generators 2 | 3 | // IntRepeat generates an infinite number of repeated values till stopped via the done channel 4 | func IntRepeat(done <-chan struct{}, values ...int) <-chan int { 5 | valueStream := make(chan int) 6 | go func() { 7 | defer close(valueStream) 8 | for { 9 | for _, v := range values { 10 | select { 11 | case <-done: 12 | return 13 | case valueStream <- v: 14 | } 15 | } 16 | } 17 | }() 18 | return valueStream 19 | } 20 | 21 | // IntTake extracts a sub stream of values out of the value stream till it reaches a certain amount or stopped 22 | func IntTake(done <-chan struct{}, valueStream <-chan int, num int) <-chan int { 23 | takeStream := make(chan int) 24 | go func() { 25 | defer close(takeStream) 26 | for i := 0; i < num; i++ { 27 | select { 28 | case <-done: 29 | return 30 | case takeStream <- <-valueStream: 31 | } 32 | } 33 | }() 34 | return takeStream 35 | } 36 | -------------------------------------------------------------------------------- /patterns/healing-unhealthy-goroutines/or.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func or(channels ...<-chan struct{}) <-chan struct{} { 4 | switch len(channels) { 5 | case 0: 6 | return nil 7 | case 1: 8 | return channels[0] 9 | } 10 | 11 | orDone := make(chan struct{}) 12 | go func() { 13 | defer close(orDone) 14 | switch len(channels) { 15 | case 2: 16 | select { 17 | case <-channels[0]: 18 | case <-channels[1]: 19 | } 20 | default: 21 | select { 22 | case <-channels[0]: 23 | case <-channels[1]: 24 | case <-channels[2]: 25 | case <-or(append(channels[3:], orDone)...): 26 | } 27 | } 28 | }() 29 | return orDone 30 | } 31 | -------------------------------------------------------------------------------- /patterns/heartbeats/1-to-1-heartbeats/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | done := make(chan struct{}) 10 | defer close(done) 11 | 12 | heartbeat, results := work(done, 1, 2, 3) 13 | // if we don't wait for the first heartbeat 14 | // we won't receive any values do to timeout 15 | <-heartbeat 16 | fmt.Println("now we can begin ranging") 17 | for { 18 | select { 19 | case res, ok := <-results: 20 | if !ok { 21 | return 22 | } 23 | fmt.Println("result", res) 24 | case <-time.After(time.Second): 25 | fmt.Println("timed out") 26 | return 27 | } 28 | } 29 | } 30 | 31 | func work(done chan struct{}, numbers ...int) (chan struct{}, chan int) { 32 | heartbeat, out := make(chan struct{}), make(chan int) 33 | go func() { 34 | defer close(heartbeat) 35 | defer close(out) 36 | 37 | time.Sleep(2 * time.Second) 38 | 39 | for _, number := range numbers { 40 | select { 41 | case heartbeat <- struct{}{}: 42 | default: 43 | } 44 | 45 | select { 46 | case <-done: 47 | return 48 | case out <- number: 49 | } 50 | } 51 | }() 52 | return heartbeat, out 53 | } 54 | -------------------------------------------------------------------------------- /patterns/ping-pong/main.go: -------------------------------------------------------------------------------- 1 | // The ping pong pattern is very simple and useful in a lot of cases. 2 | // It basically does a ping pong in terms of concurrency. 3 | // The data is ping-ponged between multiple go routines back an forth 4 | // There's constantly some go routine receiving data (PING) and re-passing (PONG) 5 | // to another go routine listening on the same data. 6 | // Huge thanks to divan.dev. Check out the full resource here: https://divan.dev/posts/go_concurrency_visualize/ 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | table := make(chan int) 17 | go player(table, 1) 18 | go player(table, 2) 19 | go player(table, 3) 20 | 21 | table <- 0 22 | time.Sleep(1 * time.Second) 23 | fmt.Println(<-table) 24 | } 25 | 26 | func player(table chan int, playerNo int) { 27 | for { 28 | ball := <-table 29 | fmt.Println("got ball:", ball, "from player:", playerNo) 30 | ball++ 31 | time.Sleep(100 * time.Millisecond) 32 | table <- ball 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /patterns/pipeline/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "concurrency/patterns/pipeline" 7 | ) 8 | 9 | var ( 10 | gen = pipeline.Gen 11 | inc = pipeline.Inc 12 | dec = pipeline.Dec 13 | sq = pipeline.Sq 14 | ) 15 | 16 | func main() { 17 | done := make(chan struct{}) 18 | defer close(done) 19 | 20 | // 1, 2, 3 21 | nums := gen(done, 1, 2, 3) 22 | // 2, 3, 4 23 | incremented := inc(done, nums) 24 | // 4, 9, 16 25 | squared := sq(done, incremented) 26 | // 3, 8, 15 27 | res := dec(done, squared) 28 | for n := range res { 29 | fmt.Println(n) 30 | } 31 | 32 | fmt.Println("the same exact result using nested calls") 33 | for n := range dec(done, sq(done, inc(done, gen(done, 1, 2, 3)))) { 34 | fmt.Println(n) 35 | } 36 | 37 | fmt.Println("the same exact result using chaining") 38 | for n := range pipeline.New(1, 2, 3).Increment().Square().Decrement().Result() { 39 | fmt.Println(n) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /patterns/pipeline/digest-tree/digestion/digestion.go: -------------------------------------------------------------------------------- 1 | package digestion 2 | 3 | import ( 4 | "crypto/md5" 5 | ) 6 | 7 | type MD5Result map[string][md5.Size]byte 8 | 9 | // result represents the product of reading and summing a file using MD5. 10 | type result struct { 11 | path string 12 | sum [md5.Size]byte 13 | err error 14 | } 15 | -------------------------------------------------------------------------------- /patterns/pipeline/digest-tree/digestion/simple_digestion.go: -------------------------------------------------------------------------------- 1 | package digestion 2 | 3 | import ( 4 | "crypto/md5" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | // MD5AllSimple reads all the files in the file tree rooted at root and returns a map 11 | // from file path to the MD5 sum of the file's contents. If the directory walk 12 | // fails or any read operation fails, MD5All returns an error. 13 | func MD5AllSimple(root string) (MD5Result, error) { 14 | m := MD5Result{} 15 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 16 | if err != nil { 17 | return err 18 | } 19 | // check if current path is a directory 20 | // at least . will be a directory 21 | if !info.Mode().IsRegular() { 22 | return nil 23 | } 24 | 25 | data, err := ioutil.ReadFile(path) 26 | if err != nil { 27 | return err 28 | } 29 | m[path] = md5.Sum(data) 30 | return nil 31 | }) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return m, nil 36 | } 37 | -------------------------------------------------------------------------------- /patterns/pipeline/digest-tree/files/file1.txt: -------------------------------------------------------------------------------- 1 | file1 2 | -------------------------------------------------------------------------------- /patterns/pipeline/digest-tree/files/file2.txt: -------------------------------------------------------------------------------- 1 | file2 2 | -------------------------------------------------------------------------------- /patterns/pipeline/digest-tree/files/file3.txt: -------------------------------------------------------------------------------- 1 | file3 2 | -------------------------------------------------------------------------------- /patterns/pipeline/digest-tree/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "sort" 8 | 9 | "concurrency/patterns/pipeline/digest-tree/digestion" 10 | ) 11 | 12 | func main() { 13 | // Calculate the MD5 sum of all files under the specified directory, 14 | // then print the results sorted by path name. 15 | if len(os.Args) < 2 { 16 | log.Fatal("no root directory provided") 17 | } 18 | m, err := digestion.MD5AllBoundedParallelism(os.Args[1]) 19 | if err != nil { 20 | fmt.Println(err) 21 | return 22 | } 23 | 24 | var paths []string 25 | for path := range m { 26 | paths = append(paths, path) 27 | } 28 | 29 | sort.Strings(paths) 30 | for _, path := range paths { 31 | fmt.Printf("%x %s\n", m[path], path) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /patterns/pipeline/stream-vs-batch/stream_vs_batch_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // to run the benchmarks, cd into the stream-vs-batch directory and run 8 | // go test -bench=. -benchtime=3s 9 | func BenchmarkStreamPipeline(b *testing.B) { 10 | b.ReportAllocs() 11 | numbers := gen(1_000_000) 12 | for i := 0; i < b.N; i++ { 13 | for _, num := range numbers { 14 | sMultiply(sAdd(sMultiply(num, 2), 1), 2) 15 | } 16 | } 17 | } 18 | 19 | // to run the benchmarks, cd into the stream-vs-batch directory and run 20 | // go test -bench=. -benchtime=3s 21 | func BenchmarkBatchPipeline(b *testing.B) { 22 | numbers := gen(1_000_000) 23 | b.ReportAllocs() 24 | for i := 0; i < b.N; i++ { 25 | for range bMultiply(bAdd(bMultiply(numbers, 2), 1), 2) { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /patterns/quit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | quit := make(chan os.Signal) 14 | signal.Notify(quit, syscall.SIGINT, os.Interrupt) 15 | 16 | err := worker(quit) 17 | 18 | select { 19 | case e := <-err: 20 | fmt.Println("error happened in worker:", e) 21 | default: 22 | return 23 | } 24 | } 25 | 26 | func worker(quit chan os.Signal) chan error { 27 | ticker := time.NewTicker(500 * time.Millisecond) 28 | timeout := time.NewTimer(3 * time.Second) 29 | for { 30 | select { 31 | case <-timeout.C: 32 | err := make(chan error, 1) 33 | err <- errors.New("something wrong happened") 34 | return err 35 | case <-ticker.C: 36 | fmt.Println("doing some work") 37 | case <-quit: 38 | cleanup() 39 | fmt.Println("exiting") 40 | return nil 41 | } 42 | } 43 | } 44 | 45 | func cleanup() { 46 | fmt.Println("doing some cleanup before exiting") 47 | } 48 | -------------------------------------------------------------------------------- /patterns/rate-limiting/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/steevehook/limiting 2 | 3 | go 1.16 4 | 5 | require golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect 6 | -------------------------------------------------------------------------------- /patterns/rate-limiting/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs= 2 | golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 3 | -------------------------------------------------------------------------------- /patterns/rate-limiting/token-bucket/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | done := make(chan struct{}) 11 | defer close(done) 12 | 13 | bucket := newTokenBucket(done, time.Second, 3) 14 | 15 | var wg sync.WaitGroup 16 | wg.Add(10) 17 | for i := 0; i < 10; i++ { 18 | go func(i int) { 19 | defer wg.Done() 20 | bucket.Wait() 21 | fmt.Println("request", i) 22 | }(i + 1) 23 | } 24 | 25 | wg.Wait() 26 | } 27 | 28 | func newTokenBucket(done chan struct{}, r time.Duration, b int) tokenBucket { 29 | tokens := make(chan time.Time, b) 30 | for i := 0; i < b; i++ { 31 | tokens <- time.Now() 32 | } 33 | go func() { 34 | for { 35 | select { 36 | case <-done: 37 | return 38 | case t := <-time.Tick(r): 39 | tokens <- t 40 | } 41 | } 42 | }() 43 | return tokenBucket{ 44 | rate: r, 45 | tokens: tokens, 46 | } 47 | } 48 | 49 | type tokenBucket struct { 50 | rate time.Duration 51 | tokens chan time.Time 52 | } 53 | 54 | func (t tokenBucket) Wait() { 55 | select { 56 | case <-t.tokens: 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /patterns/replicated-requests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | done := make(chan struct{}) 12 | result := make(chan int) 13 | 14 | var wg sync.WaitGroup 15 | for i := 0; i < 10; i++ { 16 | wg.Add(1) 17 | go request(done, result, i+1, &wg) 18 | } 19 | 20 | first := <-result 21 | close(done) 22 | wg.Wait() 23 | 24 | fmt.Printf("received an answer from request: #%d\n", first) 25 | } 26 | 27 | func request(done chan struct{}, result chan<- int, id int, wg *sync.WaitGroup) { 28 | defer wg.Done() 29 | 30 | started := time.Now() 31 | latency := time.Duration(1+rand.Intn(5)) * time.Second 32 | select { 33 | case <-done: 34 | // we normally return here, 35 | // but the below time print is negligible 36 | case <-time.After(latency): 37 | } 38 | 39 | select { 40 | case <-done: 41 | // we normally return here, 42 | // but the below time print is negligible 43 | case result <- id: 44 | } 45 | 46 | took := time.Since(started) 47 | if took < latency { 48 | took = latency 49 | } 50 | fmt.Printf("request #%d took: \t%v\n", id, took) 51 | } 52 | -------------------------------------------------------------------------------- /patterns/server/main.go: -------------------------------------------------------------------------------- 1 | // The server pattern is based on the simple idea of infinite loop 2 | // that listens for connections and runs each connection in a go routine. 3 | // Huge thanks to divan.dev. Check out the full resource here: https://divan.dev/posts/go_concurrency_visualize/ 4 | 5 | package main 6 | 7 | import "net" 8 | 9 | func handler(c net.Conn) { 10 | _, _ = c.Write([]byte("ok")) 11 | _ = c.Close() 12 | } 13 | 14 | func main() { 15 | l, err := net.Listen("tcp", ":8080") 16 | if err != nil { 17 | panic(err) 18 | } 19 | for { 20 | c, err := l.Accept() 21 | if err != nil { 22 | continue 23 | } 24 | go handler(c) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /patterns/timers-tickers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | tick := time.NewTicker(100 * time.Millisecond) 10 | timeout := time.NewTimer(3 * time.Second) 11 | for { 12 | select { 13 | case t := <-tick.C: 14 | fmt.Println("tick", t) 15 | case <-timeout.C: 16 | fmt.Println("timeout") 17 | return 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pool/allocations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var objectsCreated int 10 | pool := &sync.Pool{ 11 | New: func() interface{} { 12 | objectsCreated++ 13 | // 1KB slice of byte 14 | mem := make([]byte, 1024) 15 | return &mem 16 | }, 17 | } 18 | 19 | // seed the pool with 4KB 20 | pool.Put(pool.New()) 21 | pool.Put(pool.New()) 22 | pool.Put(pool.New()) 23 | pool.Put(pool.New()) 24 | 25 | var wg sync.WaitGroup 26 | for i := 0; i < 1024*1024; i++ { 27 | wg.Add(1) 28 | go func() { 29 | defer wg.Done() 30 | mem := pool.Get().(*[]byte) 31 | defer pool.Put(mem) 32 | }() 33 | } 34 | 35 | wg.Wait() 36 | fmt.Println("number of created objects in pool:", objectsCreated) 37 | } 38 | -------------------------------------------------------------------------------- /pool/benchmarks/big_objects_pool_vs_allocation_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | type bigObject struct { 10 | bigString string 11 | } 12 | 13 | func newBigObject() interface{} { 14 | return bigObject{ 15 | bigString: strings.Repeat("a", 1e9), 16 | } 17 | } 18 | 19 | func BenchmarkBigObjectAllocate(b *testing.B) { 20 | var bigObj bigObject 21 | // imagine we have to serve N clients 22 | // every time we serve a client we need a giant object to operate with 23 | // here we inefficiently create this object for every client 24 | // which allocates a ton of memory and also gives GC a hard time cleaning up 25 | // thus affecting the overall performance 26 | for i := 0; i < b.N; i++ { 27 | bigObj = newBigObject().(bigObject) 28 | bigObj = bigObj 29 | } 30 | } 31 | 32 | func BenchmarkBigObjectPool(b *testing.B) { 33 | pool := &sync.Pool{ 34 | New: newBigObject, 35 | } 36 | // kinda works weird with go routines 37 | for i := 0; i < b.N; i++ { 38 | obj := pool.Get() 39 | pool.Put(obj) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pool/benchmarks/pool_vs_allocations_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | // cd benchmarks 9 | // go test -bench=. 10 | func BenchmarkPool(b *testing.B) { 11 | var p sync.Pool 12 | b.RunParallel(func(pb *testing.PB) { 13 | for pb.Next() { 14 | p.Put(1) 15 | p.Get() 16 | } 17 | }) 18 | } 19 | 20 | func BenchmarkAllocation(b *testing.B) { 21 | b.RunParallel(func(pb *testing.PB) { 22 | for pb.Next() { 23 | i := 0 24 | i = i 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /pool/benchmarks/write_gzip_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io" 7 | "io/ioutil" 8 | "sync" 9 | "testing" 10 | ) 11 | 12 | const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque molestie." 13 | 14 | func BenchmarkWriteGzipWithPool(b *testing.B) { 15 | pool := sync.Pool{ 16 | New: func() interface{} { 17 | return gzip.NewWriter(ioutil.Discard) 18 | }, 19 | } 20 | b.ResetTimer() 21 | b.ReportAllocs() 22 | for n := 0; n < b.N; n++ { 23 | data := bytes.NewReader([]byte(lorem)) 24 | writer := pool.Get().(*gzip.Writer) 25 | _ = writer.Flush() 26 | _, _ = io.Copy(writer, data) 27 | pool.Put(writer) 28 | } 29 | } 30 | 31 | func BenchmarkWriteGzipWithoutPool(b *testing.B) { 32 | b.ReportAllocs() 33 | for n := 0; n < b.N; n++ { 34 | data := bytes.NewReader([]byte(lorem)) 35 | writer := gzip.NewWriter(ioutil.Discard) 36 | _, _ = io.Copy(writer, data) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pool/concurrent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // sync.Pool is a concurrent safe type 9 | // so feel free to run this program with -race flag 10 | // go run -race main.go 11 | 12 | // Also do not make any false assumptions about the Get relation to Set 13 | // to break it to you here are some comment from the source code 14 | // ------------------------------------------------------------- 15 | // Get may choose to ignore the pool and treat it as empty. 16 | // Callers should not assume any relation between values passed to Put and 17 | // the values returned by Get. 18 | func main() { 19 | var wg sync.WaitGroup 20 | p := sync.Pool{} 21 | p.New = func() interface{} { 22 | return "nothing" 23 | } 24 | 25 | wg.Add(3) 26 | go func() { 27 | p.Put("object 1") 28 | wg.Done() 29 | }() 30 | go func() { 31 | p.Put("object 2") 32 | wg.Done() 33 | }() 34 | go func() { 35 | p.Put("object 3") 36 | wg.Done() 37 | }() 38 | wg.Wait() 39 | 40 | wg.Add(3) 41 | go func() { 42 | fmt.Println(p.Get()) 43 | wg.Done() 44 | }() 45 | go func() { 46 | fmt.Println(p.Get()) 47 | wg.Done() 48 | }() 49 | go func() { 50 | fmt.Println(p.Get()) 51 | wg.Done() 52 | }() 53 | wg.Wait() 54 | 55 | fmt.Println(p.Get()) 56 | } 57 | -------------------------------------------------------------------------------- /pool/gc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | // Pools are always cleared before Garbage Collector kicks in 10 | // Have a look inside the source code 11 | // https://github.com/golang/go/blob/master/src/runtime/mgc.go#L1547 12 | // https://github.com/golang/go/blob/master/src/sync/pool.go#L233 13 | func main() { 14 | pool := &sync.Pool{ 15 | New: func() interface{} { 16 | fmt.Println("create object") 17 | return struct{}{} 18 | }, 19 | } 20 | 21 | var wg sync.WaitGroup 22 | for i := 0; i < 10; i++ { 23 | runtime.GC() 24 | wg.Add(1) 25 | go func(i int) { 26 | defer wg.Done() 27 | fmt.Println("go routine", i) 28 | obj := pool.Get() 29 | pool.Put(obj) 30 | }(i) 31 | } 32 | wg.Wait() 33 | 34 | runtime.GC() 35 | fmt.Println("after gc") 36 | obj := pool.Get() 37 | pool.Put(obj) 38 | } 39 | -------------------------------------------------------------------------------- /pool/object-reuse/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | pool := &sync.Pool{ 10 | New: func() interface{} { 11 | fmt.Println("create object") 12 | return struct{}{} 13 | }, 14 | } 15 | 16 | var wg sync.WaitGroup 17 | for i := 0; i < 10; i++ { 18 | wg.Add(1) 19 | go func(i int) { 20 | defer wg.Done() 21 | fmt.Println("go routine", i) 22 | obj := pool.Get() 23 | pool.Put(obj) 24 | }(i) 25 | } 26 | wg.Wait() 27 | } 28 | -------------------------------------------------------------------------------- /pool/queue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type person struct { 9 | name string 10 | } 11 | 12 | func main() { 13 | pool := &sync.Pool{ 14 | New: func() interface{} { 15 | return nil 16 | }, 17 | } 18 | 19 | pool.Put(&person{name: "John"}) 20 | pool.Put(&person{name: "Amy"}) 21 | pool.Put(&person{name: "Steve"}) 22 | 23 | for { 24 | p, ok := pool.Get().(*person) 25 | if !ok { 26 | fmt.Println("we're closed") 27 | return 28 | } 29 | fmt.Println("serving:", p.name) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pool/server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io/ioutil" 5 | "net" 6 | "testing" 7 | ) 8 | 9 | func init() { 10 | // to avoid recreating servers that listen on the same address 11 | // inside each benchmark 12 | plainServer().Wait() 13 | poolServer().Wait() 14 | } 15 | 16 | // to run the benchmark run the following 17 | // go test -benchtime=3s -bench=. 18 | func BenchmarkPlainServer(b *testing.B) { 19 | for i := 0; i < b.N; i++ { 20 | conn, err := net.Dial("tcp", ":8080") 21 | if err != nil { 22 | b.Fatalf("could not dial host: %v", err) 23 | } 24 | if _, err := ioutil.ReadAll(conn); err != nil { 25 | b.Fatalf("could not read from connection: %v", err) 26 | } 27 | _ = conn.Close() 28 | } 29 | } 30 | 31 | // to run the benchmark run the following 32 | // go test -benchtime=3s -bench=. 33 | func BenchmarkPoolServer(b *testing.B) { 34 | for i := 0; i < b.N; i++ { 35 | conn, err := net.Dial("tcp", ":9090") 36 | if err != nil { 37 | b.Fatalf("could not dial host: %v", err) 38 | } 39 | if _, err := ioutil.ReadAll(conn); err != nil { 40 | b.Fatalf("could not read from connection: %v", err) 41 | } 42 | _ = conn.Close() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /presentations/1_introduction-to-concurrency/Introduction to Concurrency.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/1_introduction-to-concurrency/Introduction to Concurrency.key -------------------------------------------------------------------------------- /presentations/1_introduction-to-concurrency/Introduction to Concurrency.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/1_introduction-to-concurrency/Introduction to Concurrency.pdf -------------------------------------------------------------------------------- /presentations/1_introduction-to-concurrency/Introduction to Concurrency.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/1_introduction-to-concurrency/Introduction to Concurrency.pptx -------------------------------------------------------------------------------- /presentations/2_3_4_waitgroups/WaitGroups.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/2_3_4_waitgroups/WaitGroups.key -------------------------------------------------------------------------------- /presentations/2_3_4_waitgroups/WaitGroups.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/2_3_4_waitgroups/WaitGroups.pdf -------------------------------------------------------------------------------- /presentations/2_3_4_waitgroups/WaitGroups.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/2_3_4_waitgroups/WaitGroups.pptx -------------------------------------------------------------------------------- /presentations/5_6_atomics/Atomics.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/5_6_atomics/Atomics.key -------------------------------------------------------------------------------- /presentations/5_6_atomics/Atomics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/5_6_atomics/Atomics.pdf -------------------------------------------------------------------------------- /presentations/5_6_atomics/Atomics.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/5_6_atomics/Atomics.pptx -------------------------------------------------------------------------------- /presentations/7_mutexes/Distributed Database.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/7_mutexes/Distributed Database.key -------------------------------------------------------------------------------- /presentations/7_mutexes/Mutexes.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/7_mutexes/Mutexes.key -------------------------------------------------------------------------------- /presentations/7_mutexes/Mutexes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/7_mutexes/Mutexes.pdf -------------------------------------------------------------------------------- /presentations/7_mutexes/Mutexes.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/7_mutexes/Mutexes.pptx -------------------------------------------------------------------------------- /presentations/Concurrency - DRAFT.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-basics/concurrency/ecf116e2005b30e4c12ccd61a53c92fc8645838d/presentations/Concurrency - DRAFT.key -------------------------------------------------------------------------------- /presentations/README.md: -------------------------------------------------------------------------------- 1 | # Presentations 2 | 3 | - [Concurrency in Go #1 - Introduction to Concurrency](https://github.com/golang-basics/concurrency/raw/master/presentations/1_introduction-to-concurrency) 4 | - [Concurrency in Go #2, #3, #4 - WaitGroups](https://github.com/golang-basics/concurrency/raw/master/presentations/2_3_4_waitgroups) 5 | - [Concurrency in Go #5, #6 - Atomic(s)](https://github.com/golang-basics/concurrency/raw/master/presentations/5_6_atomics) 6 | 7 | [Home](https://github.com/golang-basics/concurrency) 8 | -------------------------------------------------------------------------------- /profiling/README.md: -------------------------------------------------------------------------------- 1 | # Profiling 2 | 3 | ### Examples 4 | 5 | - [Basic pprof](https://github.com/golang-basics/concurrency/blob/master/profiling/basic-pprof/main.go) 6 | 7 | [Home](https://github.com/golang-basics/concurrency) 8 | -------------------------------------------------------------------------------- /profiling/basic-pprof/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | } 5 | -------------------------------------------------------------------------------- /s3/README.md: -------------------------------------------------------------------------------- 1 | # AWS S3 Bucket Clone 2 | 3 | [Home](https://github.com/golang-basics/concurrency) 4 | -------------------------------------------------------------------------------- /s3/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "fmt" 4 | 5 | func Init() { 6 | fmt.Println("initializing app") 7 | } 8 | -------------------------------------------------------------------------------- /s3/controllers/copy_objects.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /s3/controllers/list.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /s3/controllers/make_bucket.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /s3/controllers/move_objects.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /s3/controllers/remove_bucket.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /s3/controllers/remove_objects.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /s3/go.mod: -------------------------------------------------------------------------------- 1 | module s3 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /s3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "s3/app" 5 | ) 6 | 7 | func main() { 8 | // consider multi AZ S3 servers 9 | // consider creating creating small token based auth + ACL 10 | app.Init() 11 | } 12 | -------------------------------------------------------------------------------- /select/README.md: -------------------------------------------------------------------------------- 1 | # `select` statement 2 | 3 | ### Examples 4 | 5 | - [Simple Example](https://github.com/golang-basics/concurrency/blob/master/select/simple/main.go) 6 | - [Empty select](https://github.com/golang-basics/concurrency/blob/master/select/empty/main.go) 7 | - [default case](https://github.com/golang-basics/concurrency/blob/master/select/default/main.go) 8 | - [for select](https://github.com/golang-basics/concurrency/blob/master/select/for-select/main.go) 9 | - [Multiple Channels Ready](https://github.com/golang-basics/concurrency/blob/master/select/multiple-channels-ready/main.go) 10 | - [Nested select](https://github.com/golang-basics/concurrency/blob/master/select/nested/main.go) 11 | - [Timeout](https://github.com/golang-basics/concurrency/blob/master/select/timeout/main.go) 12 | - [select vs switch](https://github.com/golang-basics/concurrency/blob/master/select/select-vs-switch/main.go) 13 | 14 | [Home](https://github.com/golang-basics/concurrency) 15 | -------------------------------------------------------------------------------- /select/default/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // sometimes we never know when and if a channel is ready 9 | // a good practice is to always have a fallback 10 | // a fallback can be a timeout or a default case 11 | // to prevent the select statement from blocking 12 | // a default case works very well with an infinite loop 13 | // allowing other work to be done, before a channel becomes ready 14 | func main() { 15 | done := make(chan struct{}) 16 | go func() { 17 | time.Sleep(5 * time.Second) 18 | close(done) 19 | }() 20 | 21 | work := 0 22 | defer func() { 23 | fmt.Println("achieved", work, "cycles of work") 24 | }() 25 | 26 | for { 27 | select { 28 | case <-done: 29 | fmt.Println("done working") 30 | return 31 | default: 32 | work++ 33 | fmt.Println("working...") 34 | time.Sleep(time.Second) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /select/empty/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | select {} 7 | fmt.Println("I never get printed") 8 | } 9 | -------------------------------------------------------------------------------- /select/for-select/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int) 10 | go write(ch) 11 | 12 | timeout := time.NewTimer(3 * time.Second) 13 | ticker := time.NewTicker(500 * time.Millisecond) 14 | for { 15 | select { 16 | case at := <-ticker.C: 17 | fmt.Println("tick", at) 18 | case <-timeout.C: 19 | fmt.Println("time is up") 20 | return 21 | case v := <-ch: 22 | fmt.Println("wrote:", v) 23 | } 24 | } 25 | } 26 | 27 | func write(ch chan int) { 28 | for i := 0; i < 100; i++ { 29 | ch <- i 30 | time.Sleep(200 * time.Millisecond) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /select/multiple-channels-ready/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | count1, count2 := 0, 0 7 | stream1, stream2 := make(chan struct{}), make(chan struct{}) 8 | close(stream1) 9 | close(stream2) 10 | 11 | for i := 0; i < 1000; i++ { 12 | select { 13 | case <-stream1: 14 | count1++ 15 | case <-stream2: 16 | count2++ 17 | } 18 | } 19 | 20 | fmt.Println("count1:", count1) 21 | fmt.Println("count2:", count2) 22 | } 23 | -------------------------------------------------------------------------------- /select/nested/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | done, ping := make(chan struct{}), make(chan struct{}) 11 | results := make(chan int) 12 | defer close(done) 13 | 14 | var wg sync.WaitGroup 15 | wg.Add(2) 16 | go func() { 17 | defer wg.Done() 18 | select { 19 | case <-done: 20 | return 21 | case <-ping: 22 | fmt.Println("pong") 23 | } 24 | }() 25 | 26 | go func() { 27 | defer wg.Done() 28 | select { 29 | case <-done: 30 | return 31 | case results <- 1: 32 | select { 33 | case ping <- struct{}{}: 34 | fmt.Println("ping") 35 | default: 36 | fmt.Println("default") 37 | } 38 | } 39 | }() 40 | 41 | select { 42 | case <-done: 43 | return 44 | case r := <-results: 45 | fmt.Println("result", r) 46 | case <-time.After(time.Second): 47 | fmt.Println("timed out") 48 | } 49 | 50 | wg.Wait() 51 | } 52 | -------------------------------------------------------------------------------- /select/select-vs-switch/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | howSwitchWorks() 7 | howSelectWorks() 8 | } 9 | 10 | func howSwitchWorks() { 11 | // switch falls through every case till one case is true 12 | // the rest of the cases become unreachable 13 | switch { 14 | case boolean(false, "1"): 15 | fmt.Println("case 1") 16 | case boolean(false, "2"): 17 | fmt.Println("case 2") 18 | case boolean(true, "3"): 19 | fmt.Println("case 3") 20 | case boolean(true, "4"): 21 | // this case is unreachable 22 | fmt.Println("case 4") 23 | } 24 | } 25 | 26 | func boolean(b bool, label string) bool { 27 | fmt.Println(label) 28 | return b 29 | } 30 | 31 | func howSelectWorks() { 32 | // select does not fall through the cases 33 | // instead it checks all of them simultaneously 34 | // and randomly picks the one case who's most ready 35 | select { 36 | case <-channel("1"): 37 | fmt.Println("channel 1") 38 | case <-channel("2"): 39 | fmt.Println("channel 2") 40 | case <-channel("3"): 41 | fmt.Println("channel 2") 42 | case <-channel("4"): 43 | fmt.Println("channel 4") 44 | } 45 | } 46 | 47 | func channel(label string) chan struct{} { 48 | fmt.Println(label) 49 | out := make(chan struct{}) 50 | close(out) 51 | return out 52 | } 53 | -------------------------------------------------------------------------------- /select/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int) 7 | go write(ch) 8 | 9 | select { 10 | case <-ch: 11 | fmt.Println("wrote to ch") 12 | } 13 | } 14 | 15 | func write(ch chan int) { 16 | ch <- 1 17 | } 18 | -------------------------------------------------------------------------------- /select/timeout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // sometimes we never know when and if a channel is ready 9 | // a good practice is to always have a fallback 10 | // a fallback can be a timeout or a default case 11 | // to prevent the select statement from blocking 12 | func main() { 13 | done := make(chan struct{}) 14 | select { 15 | case <-done: 16 | fmt.Println("done") 17 | case <-time.After(time.Second): 18 | fmt.Println("timed out") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /testing/README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ### Examples 4 | 5 | - [Basic Testing](https://github.com/golang-basics/concurrency/blob/master/testing/basic-testing/main.go) 6 | - [Basic Benchmarking](https://github.com/golang-basics/concurrency/blob/master/testing/basic-benchmarking/main.go) 7 | 8 | [Home](https://github.com/golang-basics/concurrency) 9 | -------------------------------------------------------------------------------- /testing/basic-benchmarking/efficient_vs_inefficient_sum_test.go: -------------------------------------------------------------------------------- 1 | // To run all the benchmarks inside the current directory: 2 | // go test -bench=. 3 | // To run all the benchmarks inside the current directory for some time: 4 | // go test -bench=. -benchtime=3s 5 | package basic_testing 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func EfficientSum(a, b int) int { 12 | return a + b 13 | } 14 | 15 | func InefficientSum(a, b int) int { 16 | res := make(chan int, 1) 17 | res <- a + b 18 | return <-res 19 | } 20 | 21 | // Every benchmark must be stored in a file with the extension _test.go 22 | // Every benchmark is a function which begins with BenchmarkXxx 23 | // Every benchmark function has the signature BenchmarkXxx(b *testing.B) 24 | func BenchmarkEfficientSum(b *testing.B) { 25 | b.ReportAllocs() 26 | for i := 0; i < b.N; i++ { 27 | EfficientSum(i, i+1) 28 | } 29 | } 30 | 31 | func BenchmarkInefficientSum(b *testing.B) { 32 | b.ReportAllocs() 33 | for i := 0; i < b.N; i++ { 34 | InefficientSum(i, i+1) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /testing/basic-testing/sum_test.go: -------------------------------------------------------------------------------- 1 | package basic_testing 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Sum(a, b int) int { 8 | return a + b 9 | } 10 | 11 | // All testing files must end with _test.go 12 | // All test cases must be functions that begin with TestXxx 13 | // The function signature is TestXxx(t *testing.T) 14 | // To run all tests in current directory: go test . 15 | // To run all tests in current directory, with detailed info: go test -v . 16 | 17 | // Everything else in a test case is pretty much go code + some sugar functions provided by Go. 18 | // You can also use a testing library such as testify with many more helpers & assertions 19 | // https://github.com/stretchr/testify 20 | func TestSum(t *testing.T) { 21 | res := Sum(1, 2) 22 | if res != 3 { 23 | t.Fatalf("expected %d to be %d", res, 3) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /threads/README.md: -------------------------------------------------------------------------------- 1 | # Threads 2 | 3 | ### Examples 4 | 5 | - [CPU Hog](https://github.com/golang-basics/concurrency/blob/master/threads/cpu-hog/main.go) 6 | - [LockOSThread - Go](https://github.com/golang-basics/concurrency/blob/master/threads/lock-os-threads/go/main.go) 7 | - [LockOSThread - CGo](https://github.com/golang-basics/concurrency/blob/master/threads/lock-os-threads/cgo/main.go) 8 | - [LockOSThread - Benchmarks](https://github.com/golang-basics/concurrency/blob/master/threads/lock-os-threads/benchmarks/benchmarks_test.go) 9 | - [C Multithreading](https://github.com/golang-basics/concurrency/blob/master/threads/c-multithreading/main.c) 10 | 11 | [Home](https://github.com/golang-basics/concurrency) 12 | -------------------------------------------------------------------------------- /threads/c-multithreading/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define NUM_THREADS 10 7 | 8 | // clang -o exec main.c 9 | // gcc -o exec main.c 10 | // ./exec 11 | void *PrintHello(void *threadid) { 12 | long tid; 13 | tid = (long)threadid; 14 | sleep(1); 15 | printf("thread %ld: printing\n", tid); 16 | sleep(15); 17 | pthread_exit(NULL); 18 | } 19 | 20 | int main () { 21 | pthread_t threads[NUM_THREADS]; 22 | int rc; 23 | int i; 24 | for(i = 0; i < NUM_THREADS; i++) { 25 | printf("main() : creating thread: %d\n", i+1); 26 | rc = pthread_create(&threads[i], NULL, PrintHello, (void *) (size_t) i+1); 27 | if (rc) { 28 | printf("Error: unable to create thread: %d\n", rc); 29 | exit(-1); 30 | } 31 | } 32 | 33 | pthread_exit(NULL); 34 | } 35 | -------------------------------------------------------------------------------- /threads/cpu-hog/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | fmt.Println(os.Getpid()) 10 | for { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /threads/lock-os-threads/cgo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #cgo CFLAGS: -g -Wall 4 | // #include 5 | // #include "work.h" 6 | import "C" 7 | import ( 8 | "fmt" 9 | "runtime" 10 | "sync" 11 | ) 12 | 13 | func init() { 14 | runtime.LockOSThread() 15 | } 16 | 17 | func main() { 18 | thread := C.struct_Thread{ 19 | id: 1, 20 | } 21 | 22 | var wg sync.WaitGroup 23 | wg.Add(2) 24 | go func() { 25 | defer wg.Done() 26 | fmt.Println("go routine") 27 | }() 28 | go func() { 29 | defer wg.Done() 30 | C.work(&thread) 31 | }() 32 | 33 | wg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /threads/lock-os-threads/cgo/work.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void *threadPrint(void *threadid) { 8 | long tid; 9 | tid = (long)threadid; 10 | sleep(1); 11 | printf("thread %ld: printing\n", tid); 12 | sleep(3); 13 | printf("thread %ld: exiting\n", tid); 14 | pthread_exit(NULL); 15 | } 16 | 17 | int work (struct Thread *t) { 18 | pthread_t thread; 19 | int rc; 20 | rc = pthread_create(&thread, NULL, threadPrint, (void *) (size_t) t->id); 21 | if (rc) { 22 | printf("Error: unable to create thread: %d\n", rc); 23 | exit(-1); 24 | } 25 | pthread_join(thread, NULL); 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /threads/lock-os-threads/cgo/work.h: -------------------------------------------------------------------------------- 1 | #ifndef _WORK_H 2 | #define _WORK_H 3 | 4 | struct Thread { 5 | int id; 6 | }; 7 | 8 | int work(struct Thread *t); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /waitgroups/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | // 1. Call Add() with the number of required operations to be waited on 10 | // 2. Call Done() inside each go routine (to be waited on) 11 | // 3. Call Wait() where you want to wait for the execution of all go routines 12 | 13 | // RULES 14 | // 1. Done() MUST be called as many times as Add() 15 | // 2. If calls to Done() are less than calls to Add(), it will result in a deadlock 16 | // 3. If calls to Done() are more than calls to Add, it will result in panic 17 | // 4. Calling Wait() without calling Add() will return immediately 18 | // 5. WaitGroup MUST always be passed by reference (as pointer) 19 | // 6. Calling another Wait() before the previous one returns results in panic 20 | var wg sync.WaitGroup 21 | wg.Add(3) 22 | go func() { 23 | fmt.Println("1") 24 | wg.Done() 25 | }() 26 | go func() { 27 | fmt.Println("2") 28 | wg.Done() 29 | }() 30 | go func() { 31 | fmt.Println("3") 32 | wg.Done() 33 | }() 34 | wg.Wait() 35 | } 36 | -------------------------------------------------------------------------------- /waitgroups/benchmarks/add1_vs_addmany_test.go: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | // run all the benchmarks in the current directory 9 | // cd benchmarks 10 | // go test -bench=. 11 | func BenchmarkAddOne(b *testing.B) { 12 | var count int 13 | var wg sync.WaitGroup 14 | for i := 0; i < b.N; i++ { 15 | wg.Add(1) 16 | go func() { 17 | defer wg.Done() 18 | count++ 19 | }() 20 | } 21 | wg.Wait() 22 | } 23 | 24 | // run all the benchmarks in the current directory 25 | // cd benchmarks 26 | // go test -bench=. 27 | func BenchmarkAddMany(b *testing.B) { 28 | var count int 29 | var wg sync.WaitGroup 30 | wg.Add(b.N) 31 | for i := 0; i < b.N; i++ { 32 | go func() { 33 | defer wg.Done() 34 | count++ 35 | }() 36 | } 37 | wg.Wait() 38 | } 39 | -------------------------------------------------------------------------------- /waitgroups/deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Calling Done more times than Add panics 8 | // Calling Done less times than Add results in deadlock 9 | func main() { 10 | var wg sync.WaitGroup 11 | wg.Add(1) 12 | wg.Wait() 13 | } 14 | -------------------------------------------------------------------------------- /waitgroups/done-too-many-times/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func main() { 6 | var wg sync.WaitGroup 7 | // will result in panic 8 | wg.Done() 9 | } 10 | -------------------------------------------------------------------------------- /waitgroups/goroutines-order/different-workloads/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 11 | time.Sleep(300 * time.Millisecond) 12 | w.WriteHeader(http.StatusOK) 13 | }) 14 | log.Fatal(http.ListenAndServe(":8080", nil)) 15 | } 16 | -------------------------------------------------------------------------------- /waitgroups/goroutines-order/preserve-order/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var wg sync.WaitGroup 11 | 12 | wg.Add(1) 13 | go task1(&wg) 14 | wg.Wait() 15 | 16 | wg.Add(2) 17 | go task2(&wg) 18 | go task3(&wg) 19 | wg.Wait() 20 | 21 | wg.Add(1) 22 | go task4(&wg) 23 | wg.Wait() 24 | 25 | wg.Add(1) 26 | go task5(&wg) 27 | wg.Wait() 28 | } 29 | 30 | func task1(wg *sync.WaitGroup) { 31 | defer wg.Done() 32 | time.Sleep(time.Second) 33 | fmt.Println("task 1") 34 | } 35 | 36 | func task2(wg *sync.WaitGroup) { 37 | defer wg.Done() 38 | time.Sleep(time.Second) 39 | fmt.Println("task 2") 40 | } 41 | 42 | func task3(wg *sync.WaitGroup) { 43 | defer wg.Done() 44 | 45 | time.Sleep(time.Second) 46 | fmt.Println("task 3") 47 | } 48 | 49 | func task4(wg *sync.WaitGroup) { 50 | defer wg.Done() 51 | time.Sleep(time.Second) 52 | fmt.Println("task 4") 53 | } 54 | 55 | func task5(wg *sync.WaitGroup) { 56 | defer wg.Done() 57 | time.Sleep(time.Second) 58 | fmt.Println("task 5") 59 | } 60 | -------------------------------------------------------------------------------- /waitgroups/goroutines-order/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | for i := 0; i < 10; i++ { 11 | wg.Add(1) 12 | go func(i int) { 13 | defer wg.Done() 14 | fmt.Println("go routine", i+1) 15 | }(i) 16 | } 17 | wg.Wait() 18 | } 19 | -------------------------------------------------------------------------------- /waitgroups/limit-goroutines/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type request func() 9 | 10 | func main() { 11 | // make it a map to simulate randomness of requests 12 | // when ranging over them 13 | requests := map[int]request{} 14 | for i := 1; i <= 100; i++ { 15 | f := func(n int) request { 16 | return func() { 17 | fmt.Println("request", n) 18 | } 19 | } 20 | requests[i] = f(i) 21 | } 22 | 23 | var wg sync.WaitGroup 24 | max := 10 25 | for i := 0; i < len(requests); i += max { 26 | for j := i; j < i+max; j++ { 27 | wg.Add(1) 28 | go func(r request) { 29 | defer wg.Done() 30 | r() 31 | }(requests[j+1]) 32 | } 33 | wg.Wait() 34 | fmt.Println(max, "requests processed") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /waitgroups/no-add/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | wg.Wait() 11 | fmt.Println("Wait() executed immediately") 12 | } 13 | -------------------------------------------------------------------------------- /waitgroups/passed-by-value/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var wg sync.WaitGroup 10 | wg.Add(1) 11 | go work(wg) 12 | wg.Wait() 13 | } 14 | 15 | func work(wg sync.WaitGroup) { 16 | defer wg.Done() 17 | fmt.Println("work is done") 18 | } 19 | -------------------------------------------------------------------------------- /waitgroups/rate-limiting/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | func main() { 12 | total, max := 10, 3 13 | var wg sync.WaitGroup 14 | for i := 0; i < total; i += max { 15 | limit := max 16 | if i+max > total { 17 | limit = total - i 18 | } 19 | 20 | wg.Add(limit) 21 | for j := 0; j < limit; j++ { 22 | go func(j int) { 23 | defer wg.Done() 24 | conn, err := net.Dial("tcp", ":8080") 25 | if err != nil { 26 | log.Fatalf("could not dial: %v", err) 27 | } 28 | bs, err := ioutil.ReadAll(conn) 29 | if err != nil { 30 | log.Fatalf("could not read from conn: %v", err) 31 | } 32 | if string(bs) != "success" { 33 | log.Fatalf("request error, request: %d", i+1+j) 34 | } 35 | 36 | fmt.Printf("request %d: success\n", i+1+j) 37 | }(j) 38 | } 39 | wg.Wait() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /waitgroups/rate-limiting/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | li, err := net.Listen("tcp", ":8080") 12 | if err != nil { 13 | log.Fatalf("could not create listener: %v", err) 14 | } 15 | 16 | var connections int32 17 | for { 18 | conn, err := li.Accept() 19 | if err != nil { 20 | continue 21 | } 22 | connections++ 23 | 24 | go func(con net.Conn) { 25 | defer func() { 26 | _ = con.Close() 27 | atomic.AddInt32(&connections, -1) 28 | }() 29 | if atomic.LoadInt32(&connections) > 3 { 30 | return 31 | } 32 | 33 | // simulate heavy work 34 | time.Sleep(time.Second) 35 | _, err := conn.Write([]byte("success")) 36 | if err != nil { 37 | log.Fatalf("could not write to connection: %v", err) 38 | } 39 | }(conn) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /waitgroups/waitgroup-implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | ) 7 | 8 | type waitGroup struct { 9 | counter int64 10 | } 11 | 12 | func (wg *waitGroup) Add(n int64) { 13 | atomic.AddInt64(&wg.counter, n) 14 | } 15 | 16 | func (wg *waitGroup) Done() { 17 | atomic.AddInt64(&wg.counter, -1) 18 | if atomic.LoadInt64(&wg.counter) < 0 { 19 | panic("negative wait group counter") 20 | } 21 | } 22 | 23 | func (wg *waitGroup) Wait() { 24 | for { 25 | if atomic.LoadInt64(&wg.counter) == 0 { 26 | return 27 | } 28 | } 29 | } 30 | 31 | func main() { 32 | var wg waitGroup 33 | wg.Add(2) 34 | go func() { 35 | defer wg.Done() 36 | fmt.Println("go routine 1") 37 | }() 38 | go func() { 39 | defer wg.Done() 40 | fmt.Println("go routine 2") 41 | }() 42 | wg.Wait() 43 | fmt.Printf("all go routines are done") 44 | } 45 | -------------------------------------------------------------------------------- /waitgroups/wg-reuse/loop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | func main() { 8 | var wg sync.WaitGroup 9 | for i := 0; i < 100; i++ { 10 | // Imagine all go routines at once spawn 11 | // All at once add 3 12 | // couple of go routines call Wait and return fast 13 | // other go routines call Wait but return slower 14 | // other go routines call Wait while the slow go routines have not yet returned 15 | 16 | // the code below also has a data race 17 | go func() { 18 | wg.Add(3) 19 | go func() { 20 | wg.Done() 21 | }() 22 | go func() { 23 | wg.Done() 24 | }() 25 | go func() { 26 | wg.Done() 27 | }() 28 | wg.Wait() 29 | }() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /waitgroups/wg-reuse/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // This is a small extras from the official docs 9 | // -------------------------------------------- 10 | // Note that calls with a positive delta that occur when the counter is zero 11 | // must happen before a Wait. Calls with a negative delta, or calls with a 12 | // positive delta that start when the counter is greater than zero, may happen 13 | // at any time. 14 | func main() { 15 | var wg sync.WaitGroup 16 | wg.Add(1) 17 | go func() { 18 | time.Sleep(time.Millisecond) 19 | wg.Add(-1) 20 | wg.Add(1) 21 | }() 22 | wg.Wait() 23 | } 24 | -------------------------------------------------------------------------------- /waitgroups/with-waitgroup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("number of cores:", runtime.NumCPU()) 12 | var wg sync.WaitGroup 13 | wg.Add(10) 14 | now := time.Now() 15 | for i := 0; i < 10; i++ { 16 | go work(&wg, i+1) 17 | } 18 | wg.Wait() 19 | fmt.Println("elapsed:", time.Since(now)) 20 | fmt.Println("main is done") 21 | } 22 | 23 | func work(wg *sync.WaitGroup, id int) { 24 | defer wg.Done() 25 | time.Sleep(100 * time.Millisecond) 26 | fmt.Println("task", id, "is done") 27 | } 28 | -------------------------------------------------------------------------------- /waitgroups/without-waitgroup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | // Go Scheduler will create 16 threads 11 | // which in theory will be able to execute 12 | // 16 blocking go routines in parallel 13 | // totalling ~100ms 14 | fmt.Println("number of cores:", runtime.NumCPU()) 15 | for i := 0; i < 10; i++ { 16 | go work(i + 1) 17 | } 18 | time.Sleep(100 * time.Millisecond) 19 | fmt.Println("main is done") 20 | } 21 | 22 | func work(id int) { 23 | time.Sleep(100 * time.Millisecond) 24 | fmt.Println("task", id, "is done") 25 | } 26 | --------------------------------------------------------------------------------