├── README.md ├── exercise ├── deadlock │ ├── go.mod │ └── ex1141.go ├── ex531 │ ├── go.mod │ └── ex531.go ├── ex532 │ ├── go.mod │ └── ex532.go ├── ex93 │ ├── go.mod │ ├── ex935.go │ └── ex931.go ├── catfile │ ├── go.mod │ └── catfile.go ├── dowork │ ├── go.mod │ └── dowork.go ├── gamesync │ ├── go.mod │ └── gamesync.go ├── grepfile │ ├── go.mod │ └── grepfile.go ├── letterfreq │ ├── go.mod │ ├── letterfreq.go │ └── mutexletterfreq.go ├── numfactor │ ├── go.mod │ └── numfactor.go ├── patterns │ ├── go.mod │ ├── ex1031.go │ ├── ex1033.go │ ├── workerpools.go │ ├── loop_level_parallelism.go │ ├── pipeline.go │ └── forkjoin.go ├── selfsync │ ├── go.mod │ ├── drain.go │ ├── print.go │ ├── barrier.go │ ├── fanin.go │ ├── futexlock.go │ ├── take.go │ ├── waitgroup.go │ ├── condchannel.go │ ├── broadcast.go │ ├── channel.go │ ├── selfsync.go │ ├── spinlock.go │ └── semaphore.go ├── writerpref │ ├── go.mod │ └── writerpref.go ├── hellochannel │ ├── go.mod │ ├── helloselect.go │ ├── hellochanneldirection.go │ ├── hellochannel.go │ ├── hellobuffchannel.go │ └── helloselfchannel.go ├── matmulex634 │ ├── go.mod │ └── matmul.go ├── selectscenario │ ├── go.mod │ ├── ex832.go │ ├── ex831.go │ ├── quitchannel.go │ ├── timeout.go │ ├── writechannel.go │ ├── conditionalquit.go │ ├── ex833.go │ ├── selectnil.go │ ├── firstclasschannel.go │ ├── broadcast.go │ ├── defaultcase.go │ ├── faninfanout.go │ └── channelpipeline.go ├── asset │ └── index.html ├── semadowork │ ├── go.mod │ ├── barrierdowork.go │ ├── wgdowork.go │ ├── staticwgdowork.go │ └── semadowork.go ├── go.mod └── cmd │ └── main.go └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # Concurrent Go snippets 2 | -------------------------------------------------------------------------------- /exercise/deadlock/go.mod: -------------------------------------------------------------------------------- 1 | module deadlock 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/ex531/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/ex531 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/ex532/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/ex532 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/ex93/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/ex93 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/catfile/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/catfile 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/dowork/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/dowork 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/gamesync/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/gamesync 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/grepfile/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/grepfile 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/letterfreq/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/letterfreq 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/numfactor/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/numfactor 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/patterns/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/patterns 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/selfsync/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/selfsync 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/writerpref/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/writerpref 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/hellochannel/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/hellochannel 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/matmulex634/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/matmulex634 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/selectscenario/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/selectscenario 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /exercise/asset/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

hello index page

8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /exercise/semadowork/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/semadowork 2 | 3 | go 1.23.3 4 | 5 | replace github.com/D-K-E/concurrent-go/selfsync => ../selfsync 6 | 7 | require github.com/D-K-E/concurrent-go/selfsync v0.0.0-00010101000000-000000000000 8 | -------------------------------------------------------------------------------- /exercise/selfsync/drain.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | 4 | func Drain[ChannelType any](quit <-chan int, input <-chan ChannelType) <-chan ChannelType { 5 | // 6 | output := make(chan ChannelType) 7 | go func() { 8 | defer close(output) 9 | var msg ChannelType 10 | isOpen := true 11 | for isOpen { 12 | select { 13 | case msg, isOpen = (<-input): 14 | _ = msg 15 | case <-quit: 16 | return 17 | } 18 | } 19 | }() 20 | return output 21 | } 22 | -------------------------------------------------------------------------------- /exercise/selfsync/print.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | func Print[ChannelType any](quit <-chan int, 4 | input <-chan ChannelType, 5 | ) <-chan ChannelType { 6 | output := make(chan ChannelType) 7 | go func() { 8 | defer close(output) 9 | var msg ChannelType 10 | isOpen := true 11 | for isOpen { 12 | select { 13 | case msg, isOpen = (<-input): 14 | if isOpen { 15 | fmt.Println(msg) 16 | } 17 | case <-quit: 18 | return 19 | } 20 | } 21 | }() 22 | return output 23 | } 24 | -------------------------------------------------------------------------------- /exercise/ex93/ex935.go: -------------------------------------------------------------------------------- 1 | package ex93 2 | 3 | import ( 4 | "fmt" 5 | 6 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 7 | ) 8 | 9 | /* 10 | Connect the components developed in exercises 1 to 4 together in a main() 11 | function using the following pseudocode: 12 | */ 13 | 14 | func Ex935Main() { 15 | quit := make(chan int) 16 | defer close(quit) 17 | squares := GenerateSquares(quit) 18 | taken := selfsync.TakeUntil(func(s int) bool { return s <= 10000 }, 19 | quit, squares) 20 | // 21 | printed := selfsync.Print(quit, taken) 22 | <-selfsync.Drain(quit, printed) 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | -------------------------------------------------------------------------------- /exercise/writerpref/writerpref.go: -------------------------------------------------------------------------------- 1 | package writerpref 2 | 3 | // 4 | import ( 5 | "fmt" 6 | "time" 7 | 8 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 9 | ) 10 | 11 | func WriterPrefMain() { 12 | rwMut := selfsync.NewMutex() 13 | for i := 0; i < 2; i++ { 14 | go func() { // starts 2 goroutines 15 | for { // repeats forever 16 | rwMut.ReadLock() // 17 | time.Sleep(1 * time.Second) 18 | fmt.Println("read done") 19 | rwMut.ReadUnlock() 20 | } 21 | }() 22 | } 23 | time.Sleep(1 * time.Second) 24 | rwMut.WriteLock() // tries to acquire the writer lock from main routine 25 | fmt.Println("write finished") 26 | } 27 | -------------------------------------------------------------------------------- /exercise/hellochannel/helloselect.go: -------------------------------------------------------------------------------- 1 | package hellochannel 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func sendMsgAfter(seconds time.Duration) <-chan string { 9 | msg := make(chan string) 10 | go func() { 11 | time.Sleep(seconds) 12 | msg <- "hello" 13 | }() 14 | return msg 15 | } 16 | 17 | func HelloSelectMain() { 18 | msg := sendMsgAfter(3 * time.Second) 19 | for { 20 | select { // picks whichever case is possible 21 | // if none are available applies default branch 22 | case m := (<-msg): 23 | fmt.Println("received", m) 24 | return 25 | default: 26 | fmt.Println("no message received") 27 | time.Sleep(1 * time.Second) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /exercise/hellochannel/hellochanneldirection.go: -------------------------------------------------------------------------------- 1 | package hellochannel 2 | 3 | // very simple channel direction program 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | func msgReceiver(messages <-chan int) { 11 | msg := 0 12 | for msg != -1 { 13 | msg = (<-messages) 14 | fmt.Println(time.Now().Format("15:04:05"), "Received:", msg) 15 | } 16 | } 17 | 18 | func msgSender(messages chan<- int) { 19 | for i := 1; ; i++ { 20 | fmt.Println(time.Now().Format("15:04:05"), "Sending", i) 21 | messages <- i 22 | time.Sleep(1 * time.Second) 23 | } 24 | } 25 | 26 | func HelloDirectionChannelMain() { 27 | msgChannel := make(chan int, 3) 28 | go msgReceiver(msgChannel) 29 | go msgSender(msgChannel) 30 | time.Sleep(5 * time.Second) 31 | } 32 | -------------------------------------------------------------------------------- /exercise/dowork/dowork.go: -------------------------------------------------------------------------------- 1 | package dowork 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func doWork(id int) { 10 | fmt.Printf("work %d started at %s\n", id, time.Now().Format("15:04:05")) 11 | time.Sleep(1 * time.Second) 12 | fmt.Printf("work %d finished at %s\n", id, time.Now().Format("15:04:05")) 13 | } 14 | 15 | func seqMain() { 16 | for i := 0; i < 5; i++ { 17 | doWork(i) 18 | } 19 | } 20 | 21 | func parallelMain() { 22 | for i := 0; i < 5; i++ { 23 | go doWork(i) 24 | } 25 | time.Sleep(2 * time.Second) 26 | } 27 | 28 | func DoWorkMain() { 29 | isParallel := flag.Bool("is_parallel", false, "a bool var") 30 | flag.Parse() 31 | if *isParallel { 32 | parallelMain() 33 | } else { 34 | seqMain() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /exercise/semadowork/barrierdowork.go: -------------------------------------------------------------------------------- 1 | package semadowork 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 8 | ) 9 | 10 | func doWorkAndWait(name string, timeToWork int, barrier *selfsync.Barrier) { 11 | start := time.Now() 12 | for { 13 | fmt.Println(time.Since(start), name, "is running") 14 | time.Sleep(time.Duration(timeToWork) * time.Second) 15 | fmt.Println(time.Since(start), name, "is waiting on barrier") 16 | barrier.Wait() // waits for others to catch up before proceeding to next line 17 | } 18 | } 19 | 20 | func BarrierMain() { 21 | nbMembers := 2 22 | barrier := selfsync.NewBarrier(nbMembers) 23 | go doWorkAndWait("Mahmut", 3, barrier) 24 | go doWorkAndWait("Ahmet", 8, barrier) 25 | time.Sleep(100 * time.Second) 26 | } 27 | -------------------------------------------------------------------------------- /exercise/selectscenario/ex832.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | // exercise 8.3.2 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | func generateNumbers() chan int { 11 | output := make(chan int) 12 | go func() { 13 | for { 14 | output <- rand.Intn(10) 15 | time.Sleep(200 * time.Millisecond) 16 | } 17 | }() 18 | return output 19 | } 20 | 21 | func Ex832Main() { 22 | timeOut := 2 * time.Second 23 | messages := generateNumbers() 24 | timeout := time.After(timeOut) 25 | check := false 26 | for !check { 27 | select { 28 | case msg := (<-messages): 29 | fmt.Println("Message received:", msg) 30 | case tNow := (<-timeout): 31 | fmt.Println("Timed out. Waited until:", tNow.Format("15:04:05")) 32 | close(messages) 33 | check = true 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /exercise/ex93/ex931.go: -------------------------------------------------------------------------------- 1 | package ex93 2 | 3 | import "fmt" 4 | 5 | /* 6 | Write a generator goroutine similar to listing 9.2 that, instead of generating 7 | URL strings, generates an infinite stream of square numbers (1, 4, 9, 16, 25 . 8 | . .) on an output channel. Here is the signature: 9 | */ 10 | func GenerateSquares(quit <-chan int) <-chan int { 11 | // 12 | squares := make(chan int) 13 | go func() { 14 | defer close(squares) 15 | for i := 0; true; i++ { 16 | i2 := i * i 17 | squares <- i2 18 | } 19 | }() 20 | return squares 21 | } 22 | 23 | func GenerateSquaresMain() { 24 | qu := make(chan int) 25 | defer close(qu) 26 | resultChannel := GenerateSquares(qu) 27 | 28 | // consume result channel 29 | for msg := range resultChannel { 30 | // consume 31 | fmt.Println(msg) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /exercise/semadowork/wgdowork.go: -------------------------------------------------------------------------------- 1 | package semadowork 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func doworkWaitGroup(id int, waitGroup *sync.WaitGroup) { 11 | i := rand.Intn(4) // sleeps for random seconds to simulate a random task 12 | time.Sleep(time.Duration(i) * time.Second) 13 | fmt.Println(id, "done working after", i, "seconds") 14 | waitGroup.Done() // signals that goroutine has finished its task 15 | } 16 | 17 | func WaitGroupDoWorkMain() { 18 | // 19 | wg := sync.WaitGroup{} // create wait group 20 | nbWaitGroup := 4 // group size 21 | wg.Add(nbWaitGroup) // create group with group size 22 | for i := 0; i < nbWaitGroup; i++ { 23 | go doworkWaitGroup(i, &wg) 24 | } 25 | wg.Wait() // wait until all members of the wait group calls for Done 26 | fmt.Println("All complete") 27 | } 28 | -------------------------------------------------------------------------------- /exercise/semadowork/staticwgdowork.go: -------------------------------------------------------------------------------- 1 | package semadowork 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 9 | ) 10 | 11 | func doWorkStaticWaitGroup(id int, waitGroup *selfsync.StaticWaitGroup) { 12 | i := rand.Intn(4) // sleeps for random seconds to simulate a random task 13 | time.Sleep(time.Duration(i) * time.Second) 14 | fmt.Println(id, "done working after", i, "seconds") 15 | waitGroup.Done() 16 | } 17 | 18 | func StaticWaitGroupDoWorkMain() { 19 | // 20 | nbWaitGroup := 4 // group size 21 | wg := selfsync.NewStaticGroup(nbWaitGroup) // create wait group 22 | for i := 0; i < nbWaitGroup; i++ { 23 | go doWorkStaticWaitGroup(i, wg) 24 | } 25 | wg.Wait() // wait until all members of the wait group calls for Done 26 | fmt.Println("All complete") 27 | } 28 | -------------------------------------------------------------------------------- /exercise/hellochannel/hellochannel.go: -------------------------------------------------------------------------------- 1 | package hellochannel 2 | 3 | // very simple channel example 4 | import "fmt" 5 | 6 | func receiverString(messages chan string) { 7 | msg := "" 8 | for msg != "STOP" { 9 | msg = (<-messages) /* the operator `<-` fetches a string from the 10 | string channel. The paranthesis are not required but it makes it 11 | similar to (*x) operator where we dereference a pointer 12 | */ 13 | fmt.Println("received", msg, "!") 14 | } 15 | } 16 | 17 | func HelloChannelMain() { 18 | // 19 | msgChannel := make(chan string) 20 | go receiverString(msgChannel) 21 | fmt.Println("Sending HELLO") 22 | msgChannel <- "HELLO" 23 | 24 | fmt.Println("Sending ONE") 25 | msgChannel <- "ONE" 26 | 27 | fmt.Println("Sending THREE") 28 | msgChannel <- "THREE" 29 | 30 | fmt.Println("Sending STOP") 31 | msgChannel <- "STOP" 32 | } 33 | -------------------------------------------------------------------------------- /exercise/selfsync/barrier.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import "sync" 4 | 5 | type Barrier struct { 6 | size int // total number of participants to barrier 7 | waitCount int /* counter variable representing the number of currently 8 | suspended executions */ 9 | cond *sync.Cond 10 | } 11 | 12 | // creates a new barrier 13 | func NewBarrier(size int) *Barrier { 14 | m := sync.Mutex{} 15 | condVar := sync.NewCond(&m) 16 | barrier := Barrier{size: size, waitCount: 0, cond: condVar} 17 | return &barrier 18 | } 19 | 20 | func (b *Barrier) Wait() { 21 | b.cond.L.Lock() // protect access to wait count 22 | b.waitCount += 1 23 | 24 | // wait count had been reached, so all routines fulfilled their tasks 25 | if b.waitCount == b.size { 26 | b.waitCount = 0 27 | b.cond.Broadcast() 28 | } else { 29 | b.cond.Wait() // not reached so we wait 30 | } 31 | b.cond.L.Unlock() 32 | } 33 | -------------------------------------------------------------------------------- /exercise/hellochannel/hellobuffchannel.go: -------------------------------------------------------------------------------- 1 | package hellochannel 2 | 3 | // very simple channel example 4 | import ( 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func receiverInt(messages chan int, wg *sync.WaitGroup) { 11 | msg := 0 12 | for msg != -1 { 13 | time.Sleep(1 * time.Second) 14 | msg = (<-messages) 15 | fmt.Println("Received:", msg) 16 | } 17 | wg.Done() 18 | } 19 | 20 | func HelloBufferedChannelMain() { 21 | // 22 | msgChannel := make(chan int, 3) // create a new channel with buffer of 3 messages 23 | wg := sync.WaitGroup{} 24 | wg.Add(1) 25 | go receiverInt(msgChannel, &wg) 26 | // 27 | for i := 1; i <= 6; i++ { 28 | size := len(msgChannel) 29 | fmt.Printf("%s Sending: %d. Buffer size: %d\n", 30 | time.Now().Format("15:04:05"), i, size) 31 | msgChannel <- i 32 | } 33 | msgChannel <- (-1) 34 | wg.Wait() // wait until the other wait groups are done 35 | } 36 | -------------------------------------------------------------------------------- /exercise/selfsync/fanin.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import "sync" 4 | 5 | // fan in function merges multiple channel outputs into a single one. The 6 | // necessary prerequisite for this is to make sure that we close the common 7 | // output channel after we had done dealing with the incoming channels 8 | func FanIn[ChannelType any](quit <-chan int, 9 | incomeChannels ...<-chan ChannelType, 10 | ) chan ChannelType { 11 | wg := sync.WaitGroup{} 12 | wg.Add(len(incomeChannels)) 13 | output := make(chan ChannelType) 14 | for index, channel := range incomeChannels { 15 | go func(chnl <-chan ChannelType, indx int) { 16 | defer wg.Done() 17 | for j := range chnl { 18 | select { 19 | case output <- j: 20 | case <-quit: 21 | return 22 | } 23 | } 24 | }(channel, index) 25 | } 26 | 27 | // now wait for everything to complete 28 | go func() { 29 | wg.Wait() 30 | close(output) 31 | }() 32 | return output 33 | } 34 | -------------------------------------------------------------------------------- /exercise/catfile/catfile.go: -------------------------------------------------------------------------------- 1 | package catfile 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "time" 8 | ) 9 | 10 | func read(filename string) (string, error) { 11 | data, err := os.ReadFile(filename) 12 | if err != nil { 13 | return "", err 14 | } 15 | value := string(data[:]) 16 | return value, nil 17 | } 18 | 19 | func toConsole(filename string) { 20 | str, err := read(filename) 21 | if err == nil { 22 | fmt.Printf("file content:\n %s", str) 23 | } else { 24 | fmt.Printf("couldn't found %s", filename) 25 | } 26 | } 27 | 28 | func CatFileMain() { 29 | var filename1 *string = flag.String("filename1", "", "a file name") 30 | var filename2 *string = flag.String("filename2", "", "a file name") 31 | flag.Parse() 32 | var files [2]string 33 | files[0] = *filename1 34 | files[1] = *filename2 35 | for i := 0; i < 2; i++ { 36 | filename := files[i] 37 | go toConsole(filename) 38 | time.Sleep(1 * time.Second) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /exercise/patterns/ex1031.go: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "sync" 9 | ) 10 | 11 | // fold like loop parallelism with dependent inner loop using wait group 12 | func FoldLikeLoopParallelismWithDependentInnerLoopWaitGroupMain() { 13 | dir := os.Args[1] 14 | files, _ := os.ReadDir(dir) 15 | sha := sha256.New() 16 | var prev, next *sync.WaitGroup // from solutions 17 | for _, file := range files { 18 | if !file.IsDir() { 19 | next = &sync.WaitGroup{} 20 | next.Add(1) 21 | go func(filename string, prev, next *sync.WaitGroup) { 22 | fp := filepath.Join(dir, filename) 23 | hash := FHash(fp) 24 | if prev != nil { 25 | prev.Wait() 26 | } 27 | sha.Write(hash) 28 | next.Done() 29 | }(file.Name(), prev, next) 30 | prev = next // notice we are piping channels successively 31 | } 32 | } 33 | // drain next 34 | next.Wait() 35 | 36 | fmt.Printf("%x\n", sha.Sum(nil)) 37 | } 38 | -------------------------------------------------------------------------------- /exercise/selectscenario/ex831.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | // exercise 8.3.1 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | func generateTemp() chan int { 12 | out := make(chan int) 13 | 14 | go func() { 15 | temp := 50 16 | for { 17 | out <- temp 18 | temp += rand.Intn(3) - 1 19 | time.Sleep(200 * time.Millisecond) 20 | } 21 | }() 22 | return out 23 | } 24 | 25 | func outputTemp(input chan int) { 26 | go func() { 27 | for { 28 | msg := (<-input) 29 | fmt.Println("current temp", msg) 30 | time.Sleep(2 * time.Second) 31 | } 32 | }() 33 | } 34 | 35 | func Ex831Main() { 36 | inputChannel := make(chan int) 37 | outputChannel := generateTemp() 38 | msg := (<-outputChannel) 39 | outputTemp(inputChannel) 40 | for i := 0; i < 10; i++ { 41 | select { 42 | case msg = (<-outputChannel): 43 | fmt.Println("sent temp", msg) 44 | case inputChannel <- msg: 45 | 46 | } 47 | time.Sleep(1 * time.Second) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /exercise/selectscenario/quitchannel.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | /* this scenario concerns how to use quit channel pattern. 8 | Quit channel pattern implies that we have a function that consumes from 9 | multiple channels until the quit channel signals that all consumption must be 10 | terminated 11 | */ 12 | 13 | // we have a function that prints generated numbers 10 times. After that we 14 | // call the quits 15 | func printNumbers(numbers <-chan int, quit chan int) { 16 | go func() { 17 | for i := 0; i < 10; i++ { 18 | number := (<-numbers) 19 | fmt.Println(number) 20 | } 21 | close(quit) 22 | }() 23 | } 24 | 25 | func QuitChannelMain() { 26 | numbers := make(chan int) 27 | quit := make(chan int) 28 | printNumbers(numbers, quit) 29 | 30 | next := 0 31 | for i := 1; ; i++ { 32 | next += i 33 | select { 34 | case numbers <- next: 35 | case <-quit: 36 | fmt.Println("Quitting number generation") 37 | return 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /exercise/selfsync/futexlock.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import ( 4 | "sync/atomic" 5 | "syscall" 6 | ) 7 | 8 | /* 9 | Futex stands for fast user space mutex. Although it is not really a mutex, it 10 | is a wait queue primitive that can be accessed from user space. By user space, 11 | we basically mean by our program, not necessarily by the operating system's 12 | scheduler. It gives us the ability to suspend or awaken executions on certain 13 | addresses. 14 | 15 | There are two functions involved with this capacity: 16 | - futex_wait 17 | - futex_wake 18 | 19 | Usually these are named somewhat differently depending on the operating 20 | system, but their functionality is mostly same. 21 | The wait suspends the execution of the caller and sends it to the back of 22 | the wait queue. The wake wakes up a given number of suspended executions that 23 | are waiting on a memory address 24 | */ 25 | 26 | /* 27 | futex_wait: 28 | */ 29 | func futex_wait(address *int32, value int32) { 30 | // 31 | var val_ptr uintptr = uintptr(value) 32 | address_ptr := (uintptr)(address) 33 | syscall.Syscall(uintptr(syscall.SYS_FUTEX), address, val_ptr) 34 | } 35 | -------------------------------------------------------------------------------- /exercise/letterfreq/letterfreq.go: -------------------------------------------------------------------------------- 1 | package letterfreq 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | const allLetters = "qwertyuiop@asdfghjkl;:]zxcvbnm,./\\1234567890-^ふあうわん" 11 | 12 | func countLetters(url string, frequency []int) error { 13 | // 14 | resp, err := http.Get(url) 15 | if err != nil { 16 | return err 17 | } 18 | defer resp.Body.Close() 19 | if resp.StatusCode != 200 { 20 | panic("Server returning error status code: " + resp.Status) 21 | } 22 | body, err := io.ReadAll(resp.Body) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | for _, b := range body { 28 | c := strings.ToLower(string(b)) 29 | cIndex := strings.Index(allLetters, c) 30 | if cIndex >= 0 { 31 | frequency[cIndex] += 1 32 | } 33 | } 34 | fmt.Println("Completed:", url) 35 | return nil 36 | } 37 | 38 | func LetterFreqMain() { 39 | freq := make([]int, len(allLetters)) 40 | 41 | for i := 1000; i <= 1030; i++ { 42 | url := fmt.Sprintf("https://rfc-editor.org/rfc/rfc%d.txt", i) 43 | countLetters(url, freq) 44 | } 45 | 46 | for i, c := range allLetters { 47 | fmt.Printf("%c-%d ", c, freq[i]) 48 | } 49 | fmt.Println("Done") 50 | } 51 | -------------------------------------------------------------------------------- /exercise/numfactor/numfactor.go: -------------------------------------------------------------------------------- 1 | package numfactor 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | ) 8 | 9 | func findFactor(number int) []int { 10 | result := make([]int, 0) 11 | for i := 1; i < number; i++ { 12 | if (number % i) == 0 { 13 | result = append(result, i) 14 | } 15 | } 16 | return result 17 | } 18 | 19 | func NumFactorMain() { 20 | resultCh := make(chan []int) 21 | go func() { 22 | resultCh <- findFactor(3129402) 23 | }() 24 | 25 | nextResult := findFactor(3920147) 26 | fmt.Println("next result") 27 | fmt.Println(nextResult) 28 | firstResult := (<-resultCh) 29 | fmt.Println(firstResult) 30 | } 31 | 32 | func printResult(resultCh <-chan []int) { 33 | for r := range resultCh { 34 | fmt.Println(r) 35 | } 36 | } 37 | 38 | func randFactor(resultCh chan<- []int, waitG *sync.WaitGroup) { 39 | num := rand.Intn(900032) 40 | resultCh <- findFactor(num) 41 | waitG.Done() 42 | } 43 | 44 | func NumFactorMain2() { 45 | resultCh := make(chan []int) 46 | wg := sync.WaitGroup{} 47 | go printResult(resultCh) 48 | for i := 0; i < 10; i++ { 49 | wg.Add(1) 50 | go randFactor(resultCh, &wg) 51 | } 52 | wg.Wait() 53 | close(resultCh) 54 | } 55 | -------------------------------------------------------------------------------- /exercise/gamesync/gamesync.go: -------------------------------------------------------------------------------- 1 | package gamesync 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func playerHandler(cond *sync.Cond, playersRemaining *int, playerId int) { 10 | // lock the mutex of the condition variable to avoid race condition 11 | cond.L.Lock() 12 | fmt.Println(playerId, ": Connected") 13 | remaining := *playersRemaining 14 | remaining-- 15 | *playersRemaining = remaining 16 | // no other player are remaining time to unblock all the waiting threads 17 | if *playersRemaining == 0 { 18 | cond.Broadcast() 19 | } 20 | 21 | // wait until all players have connected 22 | for *playersRemaining > 0 { 23 | fmt.Println(playerId, ": Waiting for more players") 24 | cond.Wait() 25 | } 26 | // unlock all goroutines and resume execution 27 | cond.L.Unlock() 28 | fmt.Println("All players are connected. Ready player", playerId) 29 | } 30 | 31 | func GameSyncMain() { 32 | varMutex := sync.Mutex{} 33 | 34 | // declaring conditional variable 35 | cond := sync.NewCond(&varMutex) 36 | 37 | playersInGame := 4 38 | for playerId := 0; playerId < 4; playerId++ { 39 | go playerHandler(cond, &playersInGame, playerId) 40 | time.Sleep(1 * time.Second) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /exercise/selectscenario/timeout.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | // this snippet shows delaying unblocking the main thread for a specified 10 | // number of time. So far, we had seen blocking/unblocking mechanism that are 11 | // defined based on the completion of various functions like in wait groups 12 | // which waits until certain operations are complete, or barriers which waits 13 | // before certain operations start. This snippet shows how to wait for an 14 | // operation given time using channels. 15 | 16 | func sendMsgAfter(seconds time.Duration) <-chan string { 17 | messages := make(chan string) 18 | 19 | go func() { 20 | time.Sleep(seconds) 21 | messages <- "hello" 22 | }() 23 | return messages 24 | } 25 | 26 | func TimeoutMain() { 27 | t, _ := strconv.Atoi("4") 28 | messages := sendMsgAfter(3 * time.Second) 29 | timeOut := time.Duration(t) * time.Second 30 | fmt.Printf("Waiting for message for %d seconds...\n", t) 31 | select { 32 | case msg := (<-messages): 33 | fmt.Println("Message received:", msg) 34 | case tNow := (<-time.After(timeOut)): 35 | fmt.Println("Timed out. Waited until:", tNow.Format("15:04:05")) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /exercise/selfsync/take.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | // take or counter based quitting pattern shows how to quit conditionally a 4 | // pipeline. 5 | 6 | func Take[ChannelType any](quit chan int, 7 | counter int, input <-chan ChannelType, 8 | ) <-chan ChannelType { 9 | output := make(chan ChannelType) 10 | go func() { 11 | defer close(output) 12 | isOpen := true 13 | var msg ChannelType 14 | for counter > 0 && isOpen { 15 | select { 16 | case msg, isOpen = (<-input): 17 | if isOpen { 18 | output <- msg 19 | counter-- 20 | } 21 | case <-quit: 22 | return 23 | } 24 | } 25 | if counter == 0 { 26 | close(quit) 27 | } 28 | }() 29 | return output 30 | } 31 | 32 | func TakeUntil[ChannelType any](f func(ChannelType) bool, 33 | quit chan int, input <-chan ChannelType, 34 | ) <-chan ChannelType { 35 | output := make(chan ChannelType) 36 | go func() { 37 | isOpen := true 38 | var msg ChannelType 39 | for f(msg) && isOpen { 40 | select { 41 | case msg, isOpen = (<-input): 42 | if isOpen { 43 | output <- msg 44 | } 45 | case <-quit: 46 | return 47 | } 48 | } 49 | if !f(msg) { 50 | close(output) 51 | } 52 | }() 53 | return output 54 | } 55 | -------------------------------------------------------------------------------- /exercise/selfsync/waitgroup.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import "sync" 4 | 5 | type StaticWaitGroup struct { 6 | semaphore *Semaphore 7 | } 8 | 9 | func NewStaticGroup(size int) *StaticWaitGroup { 10 | sema := NewSemaphore(1 - size) 11 | wg := StaticWaitGroup{semaphore: sema} 12 | return &wg 13 | } 14 | 15 | func (wg *StaticWaitGroup) Wait() { 16 | wg.semaphore.Acquire() 17 | } 18 | 19 | func (wg *StaticWaitGroup) Done() { 20 | wg.semaphore.Release() 21 | } 22 | 23 | type DynamicWaitGroup struct { 24 | groupSize int 25 | cond *sync.Cond 26 | } 27 | 28 | func NewDynamicGroup() *DynamicWaitGroup { 29 | mut := sync.Mutex{} 30 | cond := sync.NewCond(&mut) 31 | wg := DynamicWaitGroup{cond: cond, groupSize: 0} 32 | return &wg 33 | } 34 | 35 | func (wg *DynamicWaitGroup) AddMember(delta int) { 36 | wg.cond.L.Lock() 37 | wg.groupSize += delta 38 | wg.cond.L.Unlock() 39 | } 40 | 41 | func (wg *DynamicWaitGroup) WaitMembers() { 42 | wg.cond.L.Lock() 43 | for wg.groupSize > 0 { 44 | wg.cond.Wait() 45 | } 46 | wg.cond.L.Unlock() 47 | } 48 | 49 | func (wg *DynamicWaitGroup) TaskDone() { 50 | wg.cond.L.Lock() 51 | wg.groupSize-- 52 | if wg.groupSize == 0 { 53 | wg.cond.Broadcast() 54 | } 55 | wg.cond.L.Unlock() 56 | } 57 | -------------------------------------------------------------------------------- /exercise/ex531/ex531.go: -------------------------------------------------------------------------------- 1 | // exercise 1 at section 5.3 2 | package ex531 3 | 4 | // n listing 5.4, Stingy’s goroutine is signaling on the condition variable 5 | // every time we add money to the bank account. Can you change the function so 6 | // that it signals only when there is $50 or more in the account? 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | func stingy(money *int, cond *sync.Cond) { 16 | for i := 0; i < 1000000; i++ { 17 | cond.L.Lock() 18 | *money += 10 19 | if *money >= 50 { 20 | cond.Signal() 21 | } 22 | cond.L.Unlock() 23 | } 24 | fmt.Println("Stingy done") 25 | } 26 | 27 | func spendy(money *int, cond *sync.Cond) { 28 | for i := 0; i < 2000000; i++ { 29 | cond.L.Lock() 30 | for *money < 50 { 31 | cond.Wait() 32 | } 33 | *money -= 50 34 | if *money < 0 { 35 | fmt.Println("Money is negative") 36 | os.Exit(1) 37 | } 38 | cond.L.Unlock() 39 | } 40 | fmt.Println("Spendy done") 41 | } 42 | 43 | func Ex531Main() { 44 | money := 100 45 | mutex := sync.Mutex{} 46 | cond := sync.NewCond(&mutex) 47 | go stingy(&money, cond) 48 | go spendy(&money, cond) 49 | time.Sleep(2 * time.Second) 50 | mutex.Lock() 51 | fmt.Println("money in bank account: ", money) 52 | mutex.Unlock() 53 | } 54 | -------------------------------------------------------------------------------- /exercise/letterfreq/mutexletterfreq.go: -------------------------------------------------------------------------------- 1 | package letterfreq 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | func countLettersMutex(url string, frequency []int, mutex *sync.RWMutex) error { 13 | // 14 | resp, err := http.Get(url) 15 | if err != nil { 16 | return err 17 | } 18 | defer resp.Body.Close() 19 | if resp.StatusCode != 200 { 20 | panic("Server returning error status code: " + resp.Status) 21 | } 22 | body, err := io.ReadAll(resp.Body) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | mutex.Lock() 28 | for _, b := range body { 29 | c := strings.ToLower(string(b)) 30 | cIndex := strings.Index(allLetters, c) 31 | if cIndex >= 0 { 32 | frequency[cIndex] += 1 33 | } 34 | } 35 | mutex.Unlock() 36 | fmt.Println("Completed:", url) 37 | return nil 38 | } 39 | 40 | func LetterFreqMutexMain() { 41 | freq := make([]int, len(allLetters)) 42 | 43 | mutex := sync.RWMutex{} 44 | 45 | for i := 1000; i <= 1030; i++ { 46 | url := fmt.Sprintf("https://rfc-editor.org/rfc/rfc%d.txt", i) 47 | go countLettersMutex(url, freq, &mutex) 48 | } 49 | time.Sleep(5 * time.Second) 50 | 51 | for i, c := range allLetters { 52 | fmt.Printf("%c-%d ", c, freq[i]) 53 | } 54 | fmt.Println("Done") 55 | } 56 | -------------------------------------------------------------------------------- /exercise/selectscenario/writechannel.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | ) 8 | 9 | /* 10 | The goal of this snippet is to show how select statement can be used to switch 11 | between reading and writing between various channels. This is very similar to 12 | assembly line where you have a stream of input and you want to move certain 13 | items in one line to another line. 14 | */ 15 | 16 | func primesOnly(inputs <-chan int) <-chan int { 17 | results := make(chan int) // will contain prime numbers 18 | go func() { 19 | for c := range inputs { 20 | isPrime := c != 1 21 | for i := 2; i <= int(math.Sqrt(float64(c))); i++ { 22 | reminder := c % i 23 | if reminder == 0 { 24 | isPrime = false 25 | break 26 | } 27 | } 28 | if isPrime { 29 | results <- c // if prime push to results 30 | } 31 | } 32 | }() 33 | return results 34 | } 35 | 36 | func PrimesOnlyMain() { 37 | numberChannel := make(chan int) 38 | primeResult := primesOnly(numberChannel) 39 | for i := 0; i < 100; { 40 | select { 41 | case numberChannel <- rand.Intn(100000000) + 1: // adds random number 42 | case primeNumber := (<-primeResult): 43 | fmt.Println("found a prime number:", primeNumber) 44 | i++ 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /exercise/selfsync/condchannel.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | // condition variable based channel implementation 4 | import ( 5 | "container/list" 6 | "sync" 7 | ) 8 | 9 | type CondChannel[ChannelType any] struct { 10 | cond *sync.Cond // 11 | maxSize int 12 | buffer *list.List // linked list as a queue data structure 13 | } 14 | 15 | func NewCondChannel[ChannelType any](capacity int) *CondChannel[ChannelType] { 16 | mut := sync.Mutex{} 17 | cc := sync.NewCond(&mut) 18 | channel := CondChannel[ChannelType]{ 19 | maxSize: capacity, 20 | cond: cc, 21 | buffer: list.New(), 22 | } 23 | return &channel 24 | } 25 | 26 | func (c *CondChannel[ChannelType]) Send(message ChannelType) { 27 | c.cond.L.Lock() /* adds a message to the buffer queue while 28 | protecting against race conditions by using a mutex */ 29 | for c.buffer.Len() == c.maxSize { 30 | c.cond.Wait() 31 | } 32 | c.buffer.PushBack(message) 33 | c.cond.Broadcast() 34 | c.cond.L.Unlock() 35 | } 36 | 37 | func (c *CondChannel[ChannelType]) Receive() ChannelType { 38 | c.cond.L.Lock() 39 | c.maxSize++ 40 | c.cond.Broadcast() 41 | for c.buffer.Len() == 0 { 42 | c.cond.Wait() 43 | } 44 | c.maxSize-- 45 | v := c.buffer.Remove(c.buffer.Front()).(ChannelType) 46 | c.cond.L.Unlock() 47 | return v 48 | } 49 | -------------------------------------------------------------------------------- /exercise/deadlock/ex1141.go: -------------------------------------------------------------------------------- 1 | package deadlock 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Player struct { 9 | name string 10 | score int 11 | mutex sync.Mutex 12 | } 13 | 14 | func incrementScores(players []*Player, increment int, wg *sync.WaitGroup) { 15 | for _, player := range players { 16 | player.mutex.Lock() 17 | } 18 | for _, player := range players { 19 | player.score += increment 20 | } 21 | for _, player := range players { 22 | player.mutex.Unlock() 23 | } 24 | wg.Done() 25 | } 26 | 27 | func incrementScoresV2(players []*Player, increment int, wg *sync.WaitGroup) { 28 | for _, player := range players { 29 | player.mutex.Lock() 30 | player.score += increment 31 | player.mutex.Unlock() 32 | } 33 | wg.Done() 34 | } 35 | 36 | func IncrementScoreMain() { 37 | p1 := Player{name: "map", score: 10, mutex: sync.Mutex{}} 38 | p2 := Player{name: "nap", score: 20, mutex: sync.Mutex{}} 39 | p3 := Player{name: "nar", score: 15, mutex: sync.Mutex{}} 40 | players := []*Player{&p1, &p2, &p3} 41 | increments := []int{2, 39, 1, 22, 12, 32, 3, 2, 1, 4, 5, 6, 76, 4, 34, 2, 9, 23, 20} 42 | wg := sync.WaitGroup{} 43 | for _, increment := range increments { 44 | wg.Add(1) 45 | go incrementScores(players, increment, &wg) 46 | } 47 | wg.Wait() 48 | fmt.Println("done") 49 | } 50 | -------------------------------------------------------------------------------- /exercise/grepfile/grepfile.go: -------------------------------------------------------------------------------- 1 | package grepfile 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func read(filename string) (string, error) { 12 | data, err := os.ReadFile(filename) 13 | if err != nil { 14 | return "", err 15 | } 16 | value := string(data[:]) 17 | return value, nil 18 | } 19 | 20 | func searchFile(searchTxt string, fileContent string) bool { 21 | res := strings.Contains(fileContent, searchTxt) 22 | return res 23 | } 24 | 25 | func toConsole(searchTxt string, filename string) { 26 | str, err := read(filename) 27 | if err == nil { 28 | res := searchFile(searchTxt, str) 29 | fmt.Printf("file %s contains %s: %t\n", filename, searchTxt, res) 30 | } else { 31 | fmt.Printf("couldn't found %s", filename) 32 | } 33 | } 34 | 35 | func GrepFileMain() { 36 | searchTxtVar := flag.String("search", "my", "search text") 37 | filename1 := flag.String("filename1", "", "a file name") 38 | filename2 := flag.String("filename2", "", "a file name") 39 | flag.Parse() 40 | var files [3]string 41 | files[0] = *searchTxtVar 42 | files[1] = *filename1 43 | files[2] = *filename2 44 | for i := 1; i < 3; i++ { 45 | searchTxt := files[0] 46 | filename := files[i] 47 | go toConsole(searchTxt, filename) 48 | time.Sleep(1 * time.Second) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /exercise/selfsync/broadcast.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | // broadcasting or worker pattern replicates the messages for multiple output 4 | // channels. It is the opposite of FanIn pattern where we merge multiple 5 | // inputs to single output 6 | 7 | // Create multiple channels 8 | func CreateAll[ChannelType any](quit <-chan int, 9 | input <-chan ChannelType, n int, 10 | ) []chan ChannelType { 11 | channels := make([]chan ChannelType, n) 12 | for i := range channels { 13 | channels[i] = make(chan ChannelType) 14 | } 15 | return channels 16 | } 17 | 18 | // close multiple channels 19 | func CloseAll[ChannelType any](channels ...chan ChannelType) { 20 | for _, out := range channels { 21 | close(out) 22 | } 23 | } 24 | 25 | // broadcast input channel to multiple output channels 26 | func Broadcast[ChannelType any](quit <-chan int, 27 | input <-chan ChannelType, n int, 28 | ) []chan ChannelType { 29 | outputs := CreateAll(quit, input, n) 30 | go func() { 31 | defer CloseAll(outputs...) 32 | var channelMsg ChannelType 33 | isOpen := true 34 | for isOpen { 35 | select { 36 | case channelMsg, isOpen = (<-input): 37 | if isOpen { 38 | for _, out := range outputs { 39 | out <- channelMsg 40 | } 41 | } 42 | case <-quit: 43 | return 44 | } 45 | } 46 | }() 47 | return outputs 48 | } 49 | -------------------------------------------------------------------------------- /exercise/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/D-K-E/concurrent-go/exercise 2 | 3 | go 1.23.3 4 | 5 | replace github.com/D-K-E/concurrent-go/dowork => ./dowork 6 | 7 | require github.com/D-K-E/concurrent-go/ch12 v0.0.0-00010101000000-000000000000 8 | 9 | replace github.com/D-K-E/concurrent-go/catfile => ./catfile 10 | 11 | replace github.com/D-K-E/concurrent-go/grepfile => ./grepfile 12 | 13 | replace github.com/D-K-E/concurrent-go/letterfreq => ./letterfreq 14 | 15 | replace github.com/D-K-E/concurrent-go/gamesync => ./gamesync 16 | 17 | replace github.com/D-K-E/concurrent-go/writerpref => ./writerpref 18 | 19 | replace github.com/D-K-E/concurrent-go/selfsync => ./selfsync 20 | 21 | replace github.com/D-K-E/concurrent-go/semadowork => ./semadowork 22 | 23 | replace github.com/D-K-E/concurrent-go/ex531 => ./ex531 24 | 25 | replace github.com/D-K-E/concurrent-go/ex532 => ./ex532 26 | 27 | replace github.com/D-K-E/concurrent-go/matmulex634 => ./matmulex634 28 | 29 | replace github.com/D-K-E/concurrent-go/hellochannel => ./hellochannel 30 | 31 | replace github.com/D-K-E/concurrent-go/numfactor => ./numfactor 32 | 33 | replace github.com/D-K-E/concurrent-go/selectscenario => ./selectscenario 34 | 35 | replace github.com/D-K-E/concurrent-go/ex93 => ./ex93 36 | 37 | replace github.com/D-K-E/concurrent-go/patterns => ./patterns 38 | 39 | replace github.com/D-K-E/concurrent-go/deadlock => ./deadlock 40 | 41 | replace github.com/D-K-E/concurrent-go/ch12 => ./ch12 42 | -------------------------------------------------------------------------------- /exercise/semadowork/semadowork.go: -------------------------------------------------------------------------------- 1 | package semadowork 2 | 3 | import ( 4 | "fmt" 5 | 6 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 7 | ) 8 | 9 | func doWork(semaphore *selfsync.Semaphore) { 10 | fmt.Println("Work started") 11 | fmt.Println("Work finished") 12 | semaphore.Release() 13 | } 14 | 15 | func doWorkWeighted(semaphore *selfsync.WeightedSemaphore, nbChildPermits int) { 16 | fmt.Println("Work started") 17 | fmt.Println("Work finished") 18 | semaphore.ReleasePermit(nbChildPermits) 19 | } 20 | 21 | func SemaphoreDoWorkMain() { 22 | semaphore := selfsync.NewGenericSemaphore[selfsync.Semaphore](0) 23 | for i := 0; i < 50; i++ { 24 | go doWork(semaphore) // starts goroutine passing a reference to semaphore 25 | fmt.Println("Waiting for child goroutine") 26 | semaphore.Acquire() // waits for available permit on the semaphore 27 | // indicating the task is complete 28 | fmt.Println("Child goroutine finished") 29 | } 30 | } 31 | 32 | func WeightedSemaphoreDoWorkMain() { 33 | semaphore := selfsync.NewGenericSemaphore[selfsync.WeightedSemaphore](0) 34 | nbChildPermits := 2 35 | for i := 0; i < 50; i++ { 36 | go doWorkWeighted(semaphore, nbChildPermits) // starts goroutine passing a reference to semaphore 37 | fmt.Println("Waiting for child goroutine") 38 | semaphore.AcquirePermit(nbChildPermits) // waits for available permit on the semaphore 39 | // indicating the task is complete 40 | fmt.Println("Child goroutine finished") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /exercise/selectscenario/conditionalquit.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | 6 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 7 | ) 8 | 9 | /* 10 | A demonstration of conditional quit pattern. We create a counter to stop all 11 | work when a condition on counter (counter == 0) is met 12 | */ 13 | 14 | func ConditionalQuitMain() { 15 | quit := make(chan int) 16 | quitWords := make(chan int) // create a separate quit channel for condition 17 | defer close(quit) 18 | /* notice that we are closing the final quit 19 | pipeline explicitly, since the other one is going to be closed by the 20 | condition 21 | */ 22 | urls := generateUrls(quitWords) 23 | const nbDownloaders = 20 24 | pages := make([]<-chan string, nbDownloaders) 25 | for i := 0; i < nbDownloaders; i++ { 26 | // download all the pages 27 | pages[i] = downloadPages(quitWords, urls) 28 | } 29 | 30 | // join all the pages to a single channel 31 | merged := selfsync.FanIn(quitWords, pages...) 32 | words := extractWords(quitWords, merged) 33 | // take first 10000 words for the pipeline 34 | myWords := selfsync.Take(quitWords, 10000, words) 35 | 36 | multiWords := selfsync.Broadcast(quit, myWords, 2) // create 2 workers 37 | longest := longestWords(quit, multiWords[0]) 38 | mostFrequent := frequentWords(quit, multiWords[1]) 39 | 40 | // consume result channel 41 | fmt.Println("longest words are") 42 | for msg := range longest { 43 | fmt.Println(msg) 44 | } 45 | fmt.Println("most frequent words are") 46 | for msg := range mostFrequent { 47 | // consume 48 | fmt.Println(msg) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /exercise/selfsync/channel.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | ) 7 | 8 | type Channel[ChannelType any] struct { 9 | capacitySema *Semaphore /* capacity semaphore to block sender when buffer is full */ 10 | sizeSema *Semaphore /* buffer size semaphore to block sender when 11 | buffer is empty */ 12 | mutex sync.Mutex // Mutex protecting our shared data list structure 13 | buffer *list.List // linked list as a queue data structure 14 | } 15 | 16 | func NewChannel[ChannelType any](capacity int) *Channel[ChannelType] { 17 | channel := Channel[ChannelType]{ 18 | capacitySema: NewSemaphore(capacity), 19 | sizeSema: NewSemaphore(0), 20 | buffer: list.New(), 21 | } 22 | return &channel 23 | } 24 | 25 | func (c *Channel[ChannelType]) Send(message ChannelType) { 26 | c.capacitySema.Acquire() // acquire one permit from the capacity semaphore 27 | c.mutex.Lock() /* adds a message to the buffer queue while 28 | protecting against race conditions by using a mutex */ 29 | c.buffer.PushBack(message) 30 | c.mutex.Unlock() 31 | c.sizeSema.Release() // Release one permit from the buffer size semaphore 32 | } 33 | 34 | func (c *Channel[ChannelType]) Receive() ChannelType { 35 | c.capacitySema.Release() // release one permit from the capacity semaphore 36 | c.sizeSema.Acquire() // Acquires one permit from the buffer size semaphore 37 | c.mutex.Lock() 38 | v := c.buffer.Remove(c.buffer.Front()).(ChannelType) /* 39 | Removes one message from the buffer while protecting against race 40 | conditions using the mutex 41 | */ 42 | c.mutex.Unlock() 43 | return v 44 | } 45 | -------------------------------------------------------------------------------- /exercise/selectscenario/ex833.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | // exercise 8.3.3 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | func player() chan string { 12 | output := make(chan string) 13 | count := rand.Intn(100) 14 | move := []string{"UP", "DOWN", "LEFT", "RIGHT"} 15 | go func() { 16 | defer close(output) 17 | for i := 0; i < count; i++ { 18 | output <- move[rand.Intn(4)] 19 | d := time.Duration(rand.Intn(200)) 20 | time.Sleep(d * time.Millisecond) 21 | } 22 | }() 23 | return output 24 | } 25 | 26 | func Ex833Main() { 27 | outCh1 := player() 28 | outCh2 := player() 29 | outCh3 := player() 30 | outCh4 := player() 31 | remainingPlayers := 4 32 | for { 33 | select { 34 | case msg, isOpen := (<-outCh1): 35 | if !isOpen { 36 | outCh1 = nil 37 | remainingPlayers-- 38 | if remainingPlayers == 1 { 39 | return 40 | } 41 | } else { 42 | fmt.Println("player 1", msg) 43 | } 44 | case msg, isOpen := (<-outCh2): 45 | if !isOpen { 46 | outCh2 = nil 47 | remainingPlayers-- 48 | if remainingPlayers == 1 { 49 | return 50 | } 51 | } else { 52 | fmt.Println("player 2", msg) 53 | } 54 | case msg, isOpen := (<-outCh3): 55 | if !isOpen { 56 | outCh3 = nil 57 | remainingPlayers-- 58 | if remainingPlayers == 1 { 59 | return 60 | } 61 | } else { 62 | fmt.Println("player 3", msg) 63 | } 64 | case msg, isOpen := (<-outCh4): 65 | if !isOpen { 66 | outCh4 = nil 67 | remainingPlayers-- 68 | if remainingPlayers == 1 { 69 | return 70 | } 71 | } else { 72 | fmt.Println("player 4", msg) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /exercise/selectscenario/selectnil.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | // this snippet shows how to disable a select case when using select with 10 | // multiple channels. Assuming to channels are closing at different times and 11 | // we want to wait until both them are complete before we move out of select 12 | // statement, we need a mechanism to disable a case without terminating the 13 | // entire select block. Remember that closing a channel does not disable a 14 | // case, it will keep using the default value of the type of the channel, so 15 | // we need something to effectively block the channel 16 | 17 | func generateAmounts(n int) <-chan int { 18 | amounts := make(chan int) 19 | go func() { 20 | defer close(amounts) 21 | for i := 0; i < n; i++ { 22 | amounts <- rand.Intn(100) + i 23 | time.Sleep(100 * time.Millisecond) // write some random value at 24 | // this interval 25 | } 26 | }() 27 | return amounts 28 | } 29 | 30 | func SelectNilMain() { 31 | sales := generateAmounts(50) 32 | expenses := generateAmounts(40) 33 | 34 | endOfDayAmount := 0 35 | for sales != nil || expenses != nil { 36 | select { 37 | case sale, isOpen := (<-sales): 38 | if isOpen { 39 | fmt.Println("sale of", sale) 40 | endOfDayAmount += sale 41 | } else { 42 | // channel is closed so we assigned nil and block this case 43 | // forever from executing 44 | sales = nil 45 | } 46 | case expense, isOpen := (<-expenses): 47 | if isOpen { 48 | fmt.Println("expense of", expense) 49 | endOfDayAmount -= expense 50 | } else { 51 | // channel is closed so we assigned nil and block this case 52 | // forever from executing 53 | expenses = nil 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /exercise/selectscenario/firstclasschannel.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | /* 8 | A demonstration of first class channels pattern. Basically we can use channels 9 | recursively to grow the pipeline dynamically. 10 | */ 11 | 12 | func primeMultiFilter(numbers <-chan int, quit chan<- int) { 13 | var right chan int 14 | p := (<-numbers) // get the first number from the channel 15 | fmt.Println("number received:", p) 16 | for n := range numbers { // read next numbers from the channel 17 | 18 | /* make sure that the next number is not multiple of 19 | previous */ 20 | if (n % p) != 0 { 21 | // we found a number but we don't have an output channel 22 | if right == nil { 23 | right = make(chan int) 24 | 25 | // notice that we found a number, now we create a new channel 26 | // to push the number and continue with the search 27 | go primeMultiFilter(right, quit) 28 | } 29 | // now we push the number 30 | right <- n 31 | } 32 | } 33 | if right == nil { 34 | // finished going through all the numbers, yet we were unable to 35 | // instantiate the channel, meaning that all the subsequent numbers 36 | // are multiple of the previous number 37 | close(quit) 38 | } else { 39 | // 40 | // finished going through all the numbers, and we were able to 41 | // instantiate it, so now it is being consumed by another go routine 42 | // we can safely close it, since there will be no more numbers going 43 | // through it 44 | close(right) 45 | } 46 | } 47 | 48 | func FirstClassChannelMain() { 49 | numbers := make(chan int) 50 | quit := make(chan int) 51 | go primeMultiFilter(numbers, quit) 52 | for i := 2; i < 10000; i++ { 53 | numbers <- i 54 | } 55 | close(numbers) 56 | // now wait for quit signal 57 | <-quit 58 | for range quit { 59 | fmt.Println("quit signal received!") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /exercise/selectscenario/broadcast.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 9 | ) 10 | 11 | /* 12 | A demonstration of Broadcast/Worker pattern. We create multiple workers to 13 | work on a single output. Each worker does a different task 14 | */ 15 | 16 | func frequentWords(quit <-chan int, words <-chan string) <-chan string { 17 | mostFrequentWords := make(chan string) 18 | go func() { 19 | defer close(mostFrequentWords) 20 | freqMap := make(map[string]int) 21 | freqList := make([]string, 0) 22 | isOpen, word := true, "" 23 | for isOpen { 24 | select { 25 | case word, isOpen = (<-words): 26 | if isOpen { 27 | if freqMap[word] == 0 { 28 | freqList = append(freqList, word) 29 | } 30 | freqMap[word] += 1 31 | } 32 | case <-quit: 33 | return 34 | } 35 | } 36 | sort.Slice(freqList, func(a, b int) bool { 37 | return freqMap[freqList[a]] > freqMap[freqList[b]] 38 | }) 39 | mostFrequentWords <- strings.Join(freqList[:10], ", ") 40 | }() 41 | return mostFrequentWords 42 | } 43 | 44 | func BroadcastMain() { 45 | quit := make(chan int) 46 | defer close(quit) 47 | urls := generateUrls(quit) 48 | const nbDownloaders = 20 49 | pages := make([]<-chan string, nbDownloaders) 50 | for i := 0; i < nbDownloaders; i++ { 51 | // download all the pages 52 | pages[i] = downloadPages(quit, urls) 53 | } 54 | 55 | // join all the pages to a single channel 56 | merged := selfsync.FanIn(quit, pages...) 57 | words := extractWords(quit, merged) 58 | multiWords := selfsync.Broadcast(quit, words, 2) // create 2 workers 59 | longest := longestWords(quit, multiWords[0]) 60 | mostFrequent := frequentWords(quit, multiWords[1]) 61 | 62 | // consume result channel 63 | fmt.Println("longest words are") 64 | for msg := range longest { 65 | fmt.Println(msg) 66 | } 67 | fmt.Println("most frequent words are") 68 | for msg := range mostFrequent { 69 | // consume 70 | fmt.Println(msg) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /exercise/patterns/ex1033.go: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func WebDownloadSequentialMain() { 11 | const pagesToDownload = 12 12 | totalLines := 0 13 | for i := 1000; i < 1000+pagesToDownload; i++ { 14 | url := fmt.Sprintf("https://rfc-editor.org/rfc/rfc%d.txt", i) 15 | fmt.Println("Downloading", url) 16 | resp, _ := http.Get(url) 17 | if resp.StatusCode != 200 { 18 | panic("Server error: " + resp.Status) 19 | } 20 | bodyBytes, _ := io.ReadAll(resp.Body) 21 | totalLines += strings.Count(string(bodyBytes), "\n") 22 | resp.Body.Close() 23 | } 24 | fmt.Println("Total lines", totalLines) 25 | } 26 | 27 | // task 1: prepare url 28 | func prepUrl(i int) string { 29 | url := fmt.Sprintf("https://rfc-editor.org/rfc/rfc%d.txt", i) 30 | return url 31 | } 32 | 33 | // task 2: download url 34 | func downloadPage(url string) *http.Response { 35 | fmt.Println("downloading", url) 36 | resp, _ := http.Get(url) 37 | if resp.StatusCode != 200 { 38 | return nil 39 | } 40 | return resp 41 | } 42 | 43 | // task 3: read response 44 | func readResponse(resp *http.Response) string { 45 | if resp == nil { 46 | return "" 47 | } 48 | defer resp.Body.Close() 49 | bodyBytes, _ := io.ReadAll(resp.Body) 50 | return string(bodyBytes) 51 | } 52 | 53 | // task 4: count lines 54 | func countLines(body string) int { 55 | lines := strings.Count(body, "\n") 56 | return lines 57 | } 58 | 59 | func WebDownloadConcurrentMain() { 60 | const pagesToDownload = 12 61 | input := make(chan int) 62 | quit := make(chan int) 63 | urlOut := AddOnPipe(quit, prepUrl, input) 64 | downloadOut := AddOnPipe(quit, downloadPage, urlOut) 65 | respOut := AddOnPipe(quit, readResponse, downloadOut) 66 | countOut := AddOnPipe(quit, countLines, respOut) 67 | go func() { 68 | for i := 1000; i < 1000+pagesToDownload; i++ { 69 | input <- i 70 | } 71 | }() 72 | totalLines := 0 73 | for j := 0; j < pagesToDownload; j++ { 74 | totalLines += (<-countOut) 75 | } 76 | quit <- 1 77 | fmt.Println("Total lines", totalLines) 78 | } 79 | -------------------------------------------------------------------------------- /exercise/selfsync/selfsync.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import "sync" 4 | 5 | type ReadWriteMutex struct { 6 | readerCounter int // stores the number of readers currently holding read lock 7 | writersWaiting int // stores the number of writers currently waiting 8 | writerActive bool // indicates if a writer is holding the writer lock 9 | cond *sync.Cond // 10 | } 11 | 12 | func NewMutex() *ReadWriteMutex { 13 | m := sync.Mutex{} 14 | rwmutex := ReadWriteMutex{ 15 | readerCounter: 0, 16 | writersWaiting: 0, 17 | writerActive: false, 18 | cond: sync.NewCond(&m), 19 | } 20 | return &rwmutex 21 | } 22 | 23 | func (rw *ReadWriteMutex) ReadLock() { 24 | rw.cond.L.Lock() // acquires mutex 25 | for rw.writersWaiting > 0 || rw.writerActive { 26 | rw.cond.Wait() // wait on condition variable while writers are waiting or active 27 | } 28 | rw.readerCounter++ // increment reader counter 29 | rw.cond.L.Unlock() // release mutex 30 | } 31 | 32 | func (rw *ReadWriteMutex) WriteLock() { 33 | rw.cond.L.Lock() // acquires mutex 34 | rw.writersWaiting++ // increments the writer's waiting counter 35 | for rw.readerCounter > 0 || rw.writerActive { 36 | rw.cond.Wait() // waits on condition variable as long as there are 37 | // readers or an active writer 38 | } 39 | rw.writersWaiting-- // once wait is over, decrements the writer's waiting 40 | // counter 41 | rw.writerActive = true // once the wait is over, marks writer active flag 42 | 43 | rw.cond.L.Unlock() // releases mutex 44 | } 45 | 46 | func (rw *ReadWriteMutex) ReadUnlock() { 47 | rw.cond.L.Lock() // acquires mutex 48 | rw.readerCounter-- // decrements reader's counter by 1 49 | if rw.readerCounter == 0 { 50 | rw.cond.Broadcast() // sends broadcast if the goroutine is the last 51 | // remaining reader 52 | } 53 | rw.cond.L.Unlock() 54 | } 55 | 56 | func (rw *ReadWriteMutex) WriteUnlock() { 57 | rw.cond.L.Lock() 58 | rw.writerActive = false // unmarks writer active flag 59 | rw.cond.Broadcast() // sends signals to all goroutines 60 | rw.cond.L.Unlock() 61 | } 62 | -------------------------------------------------------------------------------- /exercise/selfsync/spinlock.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | // spin lock implementation 4 | import ( 5 | "runtime" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | /* 11 | This is an atomic function that checks whether swapping the value of `ptr` is 12 | possible. If `ptr` value is same as old value, then it is possible to swap. 13 | The function would swap the value of `ptr` with `newvalue` and outputs true. 14 | If `ptr` value is not same as old value, then the swap is not possible and 15 | function outputs false. We use a CompareAndSwapInt32 from atomic 16 | */ 17 | func SwapIfEqualInt32(ptr *int32, oldValue, newValue int32) bool { 18 | result := atomic.CompareAndSwapInt32(ptr, oldValue, newValue) 19 | return result 20 | } 21 | 22 | func SwapIfEqualBool(ptr *atomic.Bool, oldValue, newValue bool) bool { 23 | result := ptr.CompareAndSwap(oldValue, newValue) 24 | return result 25 | } 26 | 27 | type SpinLockInt32 int32 28 | 29 | func (s *SpinLockInt32) Lock() { 30 | lockAsInt := (*int32)(s) // cast to int32 pointer 31 | 32 | // if swap occurs we call the scheduler to give execution time 33 | // to other schedules 34 | for !SwapIfEqualInt32(lockAsInt, 0, 1) { 35 | runtime.Gosched() 36 | } 37 | } 38 | 39 | func (s *SpinLockInt32) Unlock() { 40 | lockAsInt := (*int32)(s) // cast to int32 pointer 41 | atomic.StoreInt32(lockAsInt, 0) 42 | } 43 | 44 | func NewSpinLock() sync.Locker { 45 | var lock SpinLockInt32 46 | return &lock 47 | } 48 | 49 | type SpinLockBool atomic.Bool 50 | 51 | func (s *SpinLockBool) Lock() { 52 | lockAsBool := (*atomic.Bool)(s) // cast to bool pointer 53 | 54 | // if swap occurs we call the scheduler to give execution time 55 | // to other schedules 56 | for !SwapIfEqualBool(lockAsBool, false, true) { 57 | runtime.Gosched() 58 | } 59 | } 60 | 61 | func (s *SpinLockBool) Unlock() { 62 | lockAsBool := (*atomic.Bool)(s) // cast to bool pointer 63 | lockAsBool.Store(false) 64 | } 65 | 66 | func (s *SpinLockBool) TryLock() bool { 67 | lockAsBool := (*atomic.Bool)(s) // cast to bool pointer 68 | isLocked := SwapIfEqualBool(lockAsBool, false, true) 69 | return isLocked 70 | } 71 | -------------------------------------------------------------------------------- /exercise/hellochannel/helloselfchannel.go: -------------------------------------------------------------------------------- 1 | package hellochannel 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "sync" 7 | "time" 8 | 9 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 10 | ) 11 | 12 | func receiverStringSelf(messages *selfsync.Channel[string]) { 13 | msg := "" 14 | for msg != "STOP" { 15 | msg = messages.Receive() 16 | fmt.Println("received", msg, "!") 17 | } 18 | } 19 | 20 | func receiverStringSelfCond(messages *selfsync.CondChannel[string]) { 21 | msg := "" 22 | for msg != "STOP" { 23 | msg = messages.Receive() 24 | fmt.Println("received", msg, "!") 25 | } 26 | } 27 | 28 | func sendStringSelf(messages *selfsync.Channel[string], 29 | wg *sync.WaitGroup, nbMessages int, 30 | ) { 31 | for i := 0; i < nbMessages; i++ { 32 | fmt.Println(time.Now().Format("15:04:05"), "Sending", i) 33 | istr := strconv.Itoa(i) 34 | messages.Send(istr) 35 | time.Sleep(1 * time.Second) 36 | } 37 | wg.Done() 38 | } 39 | 40 | func sendStringSelfCond(messages *selfsync.CondChannel[string], 41 | wg *sync.WaitGroup, nbMessages int, 42 | ) { 43 | for i := 0; i < nbMessages; i++ { 44 | fmt.Println(time.Now().Format("15:04:05"), "Sending", i) 45 | istr := strconv.Itoa(i) 46 | messages.Send(istr) 47 | time.Sleep(1 * time.Second) 48 | } 49 | wg.Done() 50 | } 51 | 52 | func HelloSelfChannelMain() { 53 | // 54 | msgChannel := selfsync.NewChannel[string](3) // buffer size of 3 55 | wg := sync.WaitGroup{} 56 | go receiverStringSelf(msgChannel) 57 | 58 | nbMessages := [2]int{3, 6} 59 | for _, value := range nbMessages { 60 | wg.Add(1) 61 | go sendStringSelf(msgChannel, &wg, value) 62 | } 63 | wg.Wait() 64 | msgChannel.Send("STOP") 65 | time.Sleep(1 * time.Second) 66 | } 67 | 68 | func HelloSelfChannelMain2() { 69 | // 70 | msgChannel := selfsync.NewCondChannel[string](3) // buffer size of 3 71 | wg := sync.WaitGroup{} 72 | go receiverStringSelfCond(msgChannel) 73 | 74 | nbMessages := [2]int{3, 6} 75 | for _, value := range nbMessages { 76 | wg.Add(1) 77 | go sendStringSelfCond(msgChannel, &wg, value) 78 | } 79 | wg.Wait() 80 | msgChannel.Send("STOP") 81 | time.Sleep(1 * time.Second) 82 | } 83 | -------------------------------------------------------------------------------- /exercise/patterns/workerpools.go: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | /* 4 | snippet demonstrating worker pool pattern. 5 | 6 | The worker pool has a simple mechanism, there is a queue and there are a fixed 7 | number of workers. As work comes to queue, its size is increased. As work gets 8 | send to workers, the size of the queue decreases. When queue reaches its 9 | maximum size, we stop accepting work. 10 | 11 | This is a very common pattern in real-life scenarios. You have a limited 12 | number of resources for a task, and you want to balance out the incoming work 13 | to existing resources. 14 | */ 15 | 16 | import ( 17 | "fmt" 18 | "net" 19 | "os" 20 | "regexp" 21 | ) 22 | 23 | var r, _ = regexp.Compile("GET (.+) HTTP/1.1\r\n") 24 | 25 | func handleHttpRequest(conn net.Conn) { 26 | buff := make([]byte, 1024) 27 | 28 | size, _ := conn.Read(buff) 29 | if r.Match(buff[:size]) { 30 | // if request is valid proceed with work 31 | file, err := os.ReadFile(fmt.Sprintf("../asset/%s", 32 | r.FindSubmatch(buff[:size])[1])) 33 | if err == nil { 34 | conn.Write([]byte(fmt.Sprintf("HTTP/1.1/200 OK\r\nContent-Length: %d\r\n\r\n", len(file)))) 35 | conn.Write(file) 36 | } else { 37 | conn.Write([]byte("HTTP/1.1 404 Not Found\r\n\r\nNot Found")) 38 | } 39 | } else { 40 | conn.Write([]byte("HTTP/1.1 500 Internal Server Error\r\n\r\n")) 41 | } 42 | conn.Close() 43 | } 44 | 45 | func StartHttpWorkers(n int, incomingConnections <-chan net.Conn) { 46 | for i := 0; i < n; i++ { 47 | go func() { 48 | for request := range incomingConnections { 49 | handleHttpRequest(request) 50 | } 51 | }() 52 | } 53 | } 54 | 55 | func WorkerPoolMain() { 56 | incomingConn := make(chan net.Conn) 57 | nbWorkers := 3 58 | StartHttpWorkers(nbWorkers, incomingConn) 59 | server, _ := net.Listen("tcp", "localhost:8888") 60 | defer server.Close() 61 | for { 62 | conn, _ := server.Accept() 63 | select { 64 | case incomingConn <- conn: // send work to queue 65 | default: 66 | fmt.Println("Server is busy") 67 | conn.Write([]byte("HTTP/1.1 429 Too Many Requests\r\n\r\n" + 68 | "Busy\n")) 69 | conn.Close() 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /exercise/selectscenario/defaultcase.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "time" 7 | ) 8 | 9 | /* 10 | this scenario concerns performing concurrent computations on default case 11 | and stopping all concurrent actions using select statement 12 | */ 13 | 14 | const ( 15 | passToGuess = "nopass" 16 | chars = " abcdefghijklmnopqrstuvwxyz" 17 | charlen = len(chars) 18 | ) 19 | 20 | // helper function to convert integer to string. By working with integers we 21 | // can divide the search into multiple ranges 22 | func toBaseChar(n int) string { 23 | result := "" 24 | for n > 0 { 25 | result = string(chars[n%charlen]) + result 26 | n /= charlen 27 | } 28 | return result 29 | } 30 | 31 | func guessPass(from int, upto int, stop chan int, result chan string, 32 | ) { 33 | // iterate from variable to upto variable 34 | for guessNb := from; guessNb < upto; guessNb++ { 35 | select { 36 | case _, isOpen := (<-stop): 37 | fmt.Printf("Stopped at %d [%d,%d)\n", guessNb, from, upto) 38 | if isOpen { 39 | close(stop) 40 | } 41 | return 42 | default: 43 | 44 | // transform number to string 45 | guessedPass := toBaseChar(guessNb) 46 | 47 | // check if it matches to the password we are trying to guess 48 | // normally we would not know passToGuess but would attempt to 49 | // access to the resource with the guessedPass 50 | if guessedPass == passToGuess { 51 | result <- guessedPass 52 | // send signal to stop channel 53 | close(stop) 54 | return 55 | } 56 | } 57 | } 58 | fmt.Printf("Not found between [%d,%d)\n", from, upto) 59 | } 60 | 61 | func SelectScenarioDefaultCaseMain() { 62 | stopChannel := make(chan int) 63 | 64 | passChannel := make(chan string) 65 | 66 | stepSize := 10000000 67 | 68 | maxSize := int(math.Pow(float64(len(chars)), float64(len(passToGuess)))) 69 | 70 | for i := 1; i < maxSize; i += stepSize { 71 | go guessPass(i, i+stepSize, stopChannel, passChannel) 72 | //) 73 | } 74 | 75 | // notice that this blocks main thread until passChannel is populated with 76 | // a message. If the pass channel doesn't get anything, all threads go to 77 | // sleep and we have a deadlock 78 | for msg := range passChannel { 79 | fmt.Println("password found", msg) 80 | close(passChannel) 81 | } 82 | time.Sleep(5 * time.Second) 83 | } 84 | -------------------------------------------------------------------------------- /exercise/selectscenario/faninfanout.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 9 | ) 10 | 11 | /* 12 | A demonstration of Fan In - Fan Out pattern. Fan In means we are merging 13 | multiple channel outputs to a single channel. Fan out means, we are 14 | distributing the load to a fixed sized threads/routines. 15 | */ 16 | 17 | func FanInFanOutMain() { 18 | quit := make(chan int) 19 | defer close(quit) 20 | urls := generateUrls(quit) 21 | const nbDownloaders = 20 22 | pages := make([]<-chan string, nbDownloaders) 23 | for i := 0; i < nbDownloaders; i++ { 24 | // download all the pages 25 | pages[i] = downloadPages(quit, urls) 26 | } 27 | 28 | // join all the pages to a single channel 29 | merged := selfsync.FanIn(quit, pages...) 30 | words := extractWords(quit, merged) 31 | 32 | // consume result channel 33 | for msg := range words { 34 | // consume 35 | fmt.Println(msg) 36 | } 37 | } 38 | 39 | func longestWords(quit <-chan int, words <-chan string) <-chan string { 40 | longWords := make(chan string) 41 | go func() { 42 | defer close(longWords) 43 | uniqueWordsMaps := make(map[string]bool) 44 | uniqueWords := make([]string, 0) 45 | isOpen, word := true, "" 46 | for isOpen { 47 | select { 48 | case word, isOpen = (<-words): 49 | if isOpen && !uniqueWordsMaps[word] { 50 | uniqueWordsMaps[word] = true 51 | uniqueWords = append(uniqueWords, word) 52 | } 53 | case <-quit: 54 | return 55 | } 56 | } 57 | sort.Slice(uniqueWords, func(a, b int) bool { 58 | return len(uniqueWords[a]) > len(uniqueWords[b]) 59 | }) 60 | longWords <- strings.Join(uniqueWords[:10], ", ") 61 | }() 62 | return longWords 63 | } 64 | 65 | func FanInFanOutMainV2() { 66 | quit := make(chan int) 67 | defer close(quit) 68 | urls := generateUrls(quit) 69 | const nbDownloaders = 20 70 | pages := make([]<-chan string, nbDownloaders) 71 | for i := 0; i < nbDownloaders; i++ { 72 | // download all the pages 73 | pages[i] = downloadPages(quit, urls) 74 | } 75 | 76 | // join all the pages to a single channel 77 | merged := selfsync.FanIn(quit, pages...) 78 | words := extractWords(quit, merged) 79 | results := longestWords(quit, words) 80 | 81 | // consume result channel 82 | for msg := range results { 83 | // consume 84 | fmt.Println(msg) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /exercise/matmulex634/matmul.go: -------------------------------------------------------------------------------- 1 | package matmul 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "sync" 8 | // "time" 9 | 10 | selfsync "github.com/D-K-E/concurrent-go/selfsync" 11 | ) 12 | 13 | // concurrent matrix 14 | func rowMultiplyWg(matA map[int][]int, matB map[int][]int, row int, 15 | result map[int][]int, 16 | waitGroup *sync.WaitGroup, 17 | mutex *selfsync.ReadWriteMutex, 18 | ) error { 19 | defer waitGroup.Done() 20 | matrixSize := len(matA) 21 | if matrixSize != len(matB) { 22 | return errors.New("Not a square matrix") 23 | } 24 | mutex.WriteLock() 25 | nslice := make([]int, matrixSize) 26 | result[row] = nslice 27 | for col := 0; col < matrixSize; col++ { 28 | sum := 0 29 | for i := 0; i < matrixSize; i++ { 30 | sum += matA[row][i] * matB[i][col] 31 | } 32 | result[row][col] = sum 33 | } 34 | mutex.WriteUnlock() 35 | return nil 36 | } 37 | 38 | // sequential matrix 39 | func rowMultiply(matA map[int][]int, matB map[int][]int, row int, 40 | result map[int][]int, 41 | ) error { 42 | matrixSize := len(matA) 43 | if matrixSize != len(matB) { 44 | return errors.New("Not a square matrix") 45 | } 46 | nslice := make([]int, matrixSize) 47 | result[row] = nslice 48 | for col := 0; col < matrixSize; col++ { 49 | sum := 0 50 | for i := 0; i < matrixSize; i++ { 51 | sum += matA[row][i] * matB[i][col] 52 | } 53 | result[row][col] = sum 54 | } 55 | return nil 56 | } 57 | 58 | func generateRandMat(size int) map[int][]int { 59 | m := make(map[int][]int) 60 | for i := 0; i < size; i++ { 61 | nslice := make([]int, size) 62 | for j := 0; j < size; j++ { 63 | nslice[j] = rand.Intn(10) 64 | } 65 | m[i] = nslice 66 | } 67 | return m 68 | } 69 | 70 | func MatMulMain() { 71 | size := 5 72 | matA := generateRandMat(size) 73 | matB := generateRandMat(size) 74 | result := make(map[int][]int, size) 75 | nresult := make(map[int][]int, size) 76 | wg := sync.WaitGroup{} 77 | nmutex := selfsync.NewMutex() 78 | for row := 0; row < size; row++ { 79 | wg.Add(1) 80 | go rowMultiplyWg(matA, matB, row, result, &wg, nmutex) 81 | } 82 | wg.Wait() 83 | 84 | // print everything to console 85 | fmt.Println("--------------") 86 | for row := 0; row < size; row++ { 87 | fmt.Println(matA[row], matB[row], result[row]) 88 | } 89 | for row := 0; row < size; row++ { 90 | rowMultiply(matA, matB, row, nresult) 91 | } 92 | fmt.Println("--------------") 93 | for row := 0; row < size; row++ { 94 | fmt.Println(matA[row], matB[row], nresult[row]) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /exercise/ex532/ex532.go: -------------------------------------------------------------------------------- 1 | package ex532 2 | 3 | // Change the game-sync listings 5.8 and 5.9 so that, still using condition 4 | // variables, the players wait for a fixed number of seconds. If the players 5 | // haven’t all joined within this time, the goroutines should stop waiting and 6 | // let the game start without all the players. Hint: try using another 7 | // goroutine with an expiry timer. 8 | 9 | import ( 10 | "fmt" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | func playerHandler(cond *sync.Cond, playersRemaining *int, playerId int, 16 | gameHasStarted *bool, 17 | ) { 18 | // lock the mutex of the condition variable to avoid race condition 19 | cond.L.Lock() // lock since gameHasStarted and playersRemaining are shared 20 | if !(*gameHasStarted) { 21 | fmt.Println(playerId, ": Connected") 22 | remaining := *playersRemaining 23 | remaining-- 24 | *playersRemaining = remaining 25 | } 26 | // no other player are remaining time to unblock all the waiting threads 27 | if *playersRemaining == 0 { 28 | cond.Broadcast() 29 | } 30 | 31 | // wait until all players have connected or time has elapsed 32 | for *playersRemaining > 0 && !(*gameHasStarted) { 33 | fmt.Println(playerId, ": Waiting for more players") 34 | cond.Wait() 35 | /* calling wait on condition variable releases lock 36 | automatically on its mutex and suspends the current execution. 37 | */ 38 | } 39 | // unlock all goroutines and resume execution 40 | cond.L.Unlock() 41 | if *gameHasStarted { 42 | fmt.Println("Game has started: ", playerId) 43 | } else { 44 | fmt.Println("All players are connected. Ready player", playerId) 45 | } 46 | } 47 | 48 | func isExpired(start *time.Time, threshold float32) bool { 49 | elapsed := time.Since(*start).Seconds() 50 | return float32(elapsed) >= threshold 51 | } 52 | 53 | func expiryTimer(cond *sync.Cond, start *time.Time, 54 | threshold float32, gameHasStarted *bool, 55 | ) { 56 | for !isExpired(start, threshold) { 57 | // while not expired let other goroutine's work 58 | } 59 | 60 | // time is expired time to move on to game 61 | cond.L.Lock() // lock since gameHasStarted is shared 62 | fmt.Println("time's up! Starting game!") 63 | *gameHasStarted = true 64 | cond.Broadcast() // signal all waiting threads to proceed with their business 65 | cond.L.Unlock() 66 | } 67 | 68 | func Ex532Main() { 69 | varMutex := sync.Mutex{} 70 | 71 | // declaring conditional variable 72 | cond := sync.NewCond(&varMutex) 73 | 74 | start := time.Now() 75 | var threshold float32 = 3.2 76 | gameHasStarted := false 77 | go expiryTimer(cond, &start, threshold, &gameHasStarted) 78 | time.Sleep(2 * time.Second) 79 | 80 | playersInGame := 4 81 | for playerId := 0; playerId < 4; playerId++ { 82 | go playerHandler(cond, &playersInGame, playerId, &gameHasStarted) 83 | time.Sleep(1 * time.Second) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /exercise/patterns/loop_level_parallelism.go: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | ) 11 | 12 | /* 13 | snippet demonstrating how to do loop level parallelism pattern. 14 | 15 | There are two common ways to decompose a program: 16 | - By data 17 | - By task 18 | 19 | Loop level parallelism concerns mostly data decomposition. You have some kind 20 | of container and you want to apply some sort of function to it and create a 21 | new container with the transformed data. `xs = Map(f, ys)` kind of scenario 22 | where you map function `f` to iterable `ys`. Optionally you can have some kind 23 | of fold scenario where the next iteration depends on the previous one: 24 | `xs = Fold(Acc, f, ys)` where `Acc` is an accumulation variable (it can be an 25 | integer or another list entirely), `f` is a function and `ys` is the 26 | container. 27 | */ 28 | 29 | // first we'll look at a map scenario, where `f` will compute the hash of a 30 | // file, given its path: 31 | 32 | func FHash(filepath string) []byte { 33 | file, _ := os.Open(filepath) 34 | defer file.Close() 35 | sha := sha256.New() 36 | io.Copy(sha, file) 37 | shaSum := sha.Sum(nil) 38 | return shaSum 39 | } 40 | 41 | func MapLikeLoopParallelismWithIndependentInnerLoopMain() { 42 | dir := os.Args[1] 43 | files, _ := os.ReadDir(dir) 44 | wg := sync.WaitGroup{} 45 | for _, file := range files { 46 | if !file.IsDir() { 47 | wg.Add(1) 48 | go func(filename string) { 49 | fp := filepath.Join(dir, filename) 50 | hash := FHash(fp) 51 | fmt.Printf("%s - %x\n", filename, hash) 52 | wg.Done() 53 | }(file.Name()) 54 | } 55 | } 56 | wg.Wait() 57 | } 58 | 59 | /* 60 | The second scenario is fold like scenario. This time we'll compute the hash of 61 | a folder using the hash of files inside it. The tricky bit is the fact that 62 | order of iteration would affect the folder hash. The solution to this is to 63 | run independent parts concurrently and compute dependent parts using 64 | synchronisation mechanisms. 65 | */ 66 | 67 | func FoldLikeLoopParallelismWithDependentInnerLoopMain() { 68 | dir := os.Args[1] 69 | files, _ := os.ReadDir(dir) 70 | sha := sha256.New() 71 | var prev, next chan int 72 | for _, file := range files { 73 | if !file.IsDir() { 74 | next = make(chan int) 75 | go func(filename string, prev, next chan int) { 76 | fp := filepath.Join(dir, filename) 77 | hash := FHash(fp) 78 | if prev != nil { 79 | <-prev // if not first go routine wait for previous 80 | } 81 | sha.Write(hash) 82 | next <- 0 83 | }(file.Name(), prev, next) 84 | prev = next // notice we are piping channels successively 85 | } 86 | } 87 | // drain next 88 | <-next 89 | /*for n := range next { 90 | _ = n 91 | }*/ 92 | fmt.Printf("%x\n", sha.Sum(nil)) 93 | } 94 | -------------------------------------------------------------------------------- /exercise/selectscenario/channelpipeline.go: -------------------------------------------------------------------------------- 1 | package selectscenario 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | /* 12 | this scenario concerns how to use pipeline pattern with channels. 13 | The idea is that upon receiving an quit channel as input we return an output 14 | channel, and the function that consumes the output channel stops consumption 15 | using quit channel. Sounds complicated, but quite easy in code 16 | */ 17 | 18 | // generates urls we can think of this as the pipeline channel. Currently it 19 | // has a quit channel and an output channel, but we'll see in subsequent 20 | // versions of this function other channels that are spawned from it 21 | func generateUrls(quit <-chan int) <-chan string { 22 | urls := make(chan string) 23 | go func() { 24 | defer close(urls) 25 | for i := 100; i <= 130; i++ { 26 | url := fmt.Sprintf("https://rfc-editor.org/rfc/rfc%d.txt", i) 27 | select { 28 | case urls <- url: 29 | case (<-quit): 30 | return 31 | } 32 | } 33 | }() 34 | return urls 35 | } 36 | 37 | func ChannelPipelineMainV1() { 38 | qu := make(chan int) 39 | defer close(qu) 40 | resultChannel := generateUrls(qu) 41 | 42 | // consume result channel 43 | for msg := range resultChannel { 44 | // consume 45 | fmt.Println(msg) 46 | } 47 | } 48 | 49 | func downloadPages(quit <-chan int, urls <-chan string) <-chan string { 50 | pages := make(chan string) 51 | go func() { 52 | defer close(pages) 53 | isOpen := true 54 | url := "" 55 | for isOpen { 56 | select { 57 | case url, isOpen = (<-urls): 58 | if isOpen { 59 | resp, _ := http.Get(url) 60 | if resp.StatusCode != 200 { 61 | panic("Server's error: " + resp.Status) 62 | } 63 | body, _ := io.ReadAll(resp.Body) 64 | pages <- string(body) 65 | resp.Body.Close() 66 | } 67 | case <-quit: 68 | return 69 | } 70 | } 71 | }() 72 | return pages 73 | } 74 | 75 | func ChannelPipelineMainV2() { 76 | quit := make(chan int) 77 | defer close(quit) 78 | urls := generateUrls(quit) 79 | pages := downloadPages(quit, urls) 80 | 81 | // consume result channel 82 | for msg := range pages { 83 | // consume 84 | fmt.Println(msg) 85 | } 86 | } 87 | 88 | func extractWords(quit <-chan int, pages <-chan string) <-chan string { 89 | words := make(chan string) 90 | go func() { 91 | // 92 | defer close(words) 93 | wordRegex := regexp.MustCompile(`[a-zA-Z]+`) 94 | isOpen := true 95 | pg := "" 96 | for isOpen { 97 | select { 98 | case pg, isOpen = (<-pages): 99 | if isOpen { 100 | for _, word := range wordRegex.FindAllString(pg, -1) { 101 | words <- strings.ToLower(word) 102 | } 103 | } 104 | case <-quit: 105 | return 106 | } 107 | } 108 | }() 109 | return words 110 | } 111 | 112 | func ChannelPipelineMainV3() { 113 | quit := make(chan int) 114 | defer close(quit) 115 | urls := generateUrls(quit) 116 | pages := downloadPages(quit, urls) 117 | words := extractWords(quit, pages) 118 | 119 | // consume result channel 120 | for msg := range words { 121 | // consume 122 | fmt.Println(msg) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /exercise/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // catfile "github.com/D-K-E/concurrent-go/catfile" 5 | // grepfile "github.com/D-K-E/concurrent-go/grepfile" 6 | // dowork "github.com/D-K-E/concurrent-go/dowork" 7 | // letterfreq "github.com/D-K-E/concurrent-go/letterfreq" 8 | // gamesync "github.com/D-K-E/concurrent-go/gamesync" 9 | // writerpref "github.com/D-K-E/concurrent-go/writerpref" 10 | // semadowork "github.com/D-K-E/concurrent-go/semadowork" 11 | // ex531 "github.com/D-K-E/concurrent-go/ex531" 12 | // ex532 "github.com/D-K-E/concurrent-go/ex532" 13 | // semadowork "github.com/D-K-E/concurrent-go/semadowork" 14 | // ex "github.com/D-K-E/concurrent-go/matmulex634" 15 | // hellochannel "github.com/D-K-E/concurrent-go/hellochannel" 16 | // numfactor "github.com/D-K-E/concurrent-go/numfactor" 17 | // hellochannel "github.com/D-K-E/concurrent-go/hellochannel" 18 | // selectscenario "github.com/D-K-E/concurrent-go/selectscenario" 19 | // ex93 "github.com/D-K-E/concurrent-go/ex93" 20 | // patterns "github.com/D-K-E/concurrent-go/patterns" 21 | // deadlock "github.com/D-K-E/concurrent-go/deadlock" 22 | ch12 "github.com/D-K-E/concurrent-go/ch12" 23 | ) 24 | 25 | func main() { 26 | // letterfreq.LetterFreqMutexMain() 27 | // letterfreq.LetterFreqMain() 28 | // gamesync.GameSyncMain() 29 | // writerpref.WriterPrefMain() 30 | // semadowork.SemaphoreDoWorkMain() 31 | // ex531.Ex531Main() 32 | // ex532.Ex532Main() 33 | // semadowork.WeightedSemaphoreDoWorkMain() 34 | // semadowork.WaitGroupDoWorkMain() 35 | // semadowork.StaticWaitGroupDoWorkMain() 36 | // semadowork.BarrierMain() 37 | // ex.MatMulMain() 38 | // hellochannel.HelloChannelMain() 39 | // hellochannel.HelloBufferedChannelMain() 40 | // hellochannel.HelloDirectionChannelMain() 41 | // numfactor.NumFactorMain() 42 | // numfactor.NumFactorMain2() 43 | // hellochannel.HelloSelfChannelMain() 44 | // hellochannel.HelloSelfChannelMain2() 45 | // hellochannel.HelloSelectMain() 46 | // selectscenario.SelectScenarioDefaultCaseMain() 47 | // selectscenario.TimeoutMain() 48 | // selectscenario.PrimesOnlyMain() 49 | // selectscenario.SelectNilMain() 50 | // selectscenario.Ex831Main() 51 | // selectscenario.Ex832Main() 52 | // selectscenario.Ex833Main() 53 | // selectscenario.QuitChannelMain() 54 | // selectscenario.ChannelPipelineMainV1() 55 | // selectscenario.ChannelPipelineMainV3() 56 | // selectscenario.FanInFanOutMain() 57 | // selectscenario.FanInFanOutMainV2() 58 | // selectscenario.BroadcastMain() 59 | // selectscenario.ConditionalQuitMain() 60 | // selectscenario.FirstClassChannelMain() 61 | // ex93.GenerateSquaresMain() 62 | // ex93.Ex935Main() 63 | // patterns.MapLikeLoopParallelismWithIndependentInnerLoopMain() 64 | // patterns.FoldLikeLoopParallelismWithDependentInnerLoopMain() 65 | // patterns.ForkJoinSynchronizedMain() 66 | // patterns.ForkJoinMain() 67 | // patterns.WorkerPoolMain() 68 | // patterns.CakePrepSequentialMain() 69 | // patterns.CakePrepConcurrentMain() 70 | // patterns.FoldLikeLoopParallelismWithDependentInnerLoopWaitGroupMain() 71 | // patterns.WebDownloadSequentialMain() 72 | // patterns.WebDownloadConcurrentMain() 73 | // deadlock.IncrementScoreMain() 74 | // ch12.AtomicStingySpendyMain() 75 | ch12.LetterFreqMain() 76 | } 77 | -------------------------------------------------------------------------------- /exercise/selfsync/semaphore.go: -------------------------------------------------------------------------------- 1 | package selfsync 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | // Semaphore allows a fixed number of permits that enable concurrent 9 | // executions to access shared resources. A mutex ensures that only a single 10 | // goroutine has exclusive access, whereas a semaphore ensures that at most N 11 | // goroutines have access 12 | type Semaphore struct { 13 | permits int // permits remaining on the semaphore 14 | cond *sync.Cond 15 | } 16 | 17 | func NewSemaphore(n int) *Semaphore { 18 | m := sync.Mutex{} 19 | sema := Semaphore{ 20 | permits: n, 21 | cond: sync.NewCond(&m), 22 | } 23 | return &sema 24 | } 25 | 26 | // organizes access to resources based on permits 27 | func (rw *Semaphore) Acquire() { 28 | rw.cond.L.Lock() // acquires mutex to protect permits variable 29 | for rw.permits <= 0 { // waits until there is a permit available 30 | rw.cond.Wait() 31 | } 32 | rw.permits-- 33 | rw.cond.L.Unlock() 34 | } 35 | 36 | func (rw *Semaphore) Release() { 37 | rw.cond.L.Lock() 38 | rw.permits++ 39 | rw.cond.Signal() 40 | // we signal that there is a permit available for possible acquisition 41 | rw.cond.L.Unlock() 42 | } 43 | 44 | // Weighted semaphore is a variation on a semaphore that allows you to acquire 45 | // and release more than one permit at the same time. 46 | type WeightedSemaphore struct { 47 | permits int // permits remaining on the semaphore 48 | cond *sync.Cond 49 | } 50 | 51 | func NewGenericSemaphore[SemaphoreType WeightedSemaphore | Semaphore](n int) *SemaphoreType { 52 | m := sync.Mutex{} 53 | sema := SemaphoreType{ 54 | permits: n, 55 | cond: sync.NewCond(&m), 56 | } 57 | return &sema 58 | } 59 | 60 | func (rw *WeightedSemaphore) AcquirePermit(n int) { 61 | rw.cond.L.Lock() 62 | if rw.permits < n { // wait until we have enough permits 63 | rw.cond.Wait() 64 | } 65 | // we had waited enough, we should have enough permits at this point 66 | rw.permits -= n 67 | rw.cond.L.Unlock() 68 | } 69 | 70 | func (rw *WeightedSemaphore) ReleasePermit(n int) { 71 | rw.cond.L.Lock() 72 | rw.permits += n 73 | rw.cond.Signal() // we signal that there is n permits available for acquisition 74 | rw.cond.L.Unlock() 75 | } 76 | 77 | /* 78 | Spin semaphore is another another variation on the semaphore type using atomic 79 | variables. The permits are initialized using an atomic variable 80 | */ 81 | 82 | type SpinSemaphore struct { 83 | permits atomic.Int32 // permits remaining on the semaphore 84 | } 85 | 86 | /* 87 | During acquisition, we check if the number of permits is 0 or not, and check 88 | for swap 89 | */ 90 | func (rw *SpinSemaphore) Acquire() { 91 | for { 92 | currentVal := rw.permits.Load() 93 | cond1 := currentVal == 0 94 | isSwapped := SwapIfEqualInt32(¤tVal, currentVal, currentVal-1) 95 | if !cond1 && isSwapped { 96 | break 97 | } 98 | } 99 | } 100 | 101 | // organizes access to resources based on permits 102 | func (rw *SpinSemaphore) Release() { 103 | rw.permits.Add(1) 104 | } 105 | 106 | func NewSpinSemaphore(nbPermits int32) *SpinSemaphore { 107 | var sema SpinSemaphore 108 | sema.permits.Store(nbPermits) 109 | return &sema 110 | } 111 | -------------------------------------------------------------------------------- /exercise/patterns/pipeline.go: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | /* 9 | pipeline pattern is used when tasks are heavily dependent to one another. It 10 | is somewhat like the loop with a dependency between iterations. 11 | 12 | The interesting to note here is that we'll see that even though tasks need 13 | to follow an order (a property not very easy to achieve in a concurrent 14 | system), there is still a speedup to be gained over the sequential execution 15 | 16 | The example program would be a recipe: 17 | 18 | 1. Prepare the baking tray 19 | 2. Pour the cupcake mixture 20 | 3. Bake the mixture in oven 21 | 4. Add toppings 22 | 5. Pack cupcakes in a box for delivery 23 | 24 | */ 25 | 26 | const ( 27 | ovenTime = 2 28 | everyThingElseTime = 2 29 | ) 30 | 31 | // 1. prepare the baking tray function 32 | func PrepareTray(trayNumber int) string { 33 | fmt.Println("preparing empty tray", trayNumber) 34 | time.Sleep(everyThingElseTime * time.Second) 35 | tid := fmt.Sprintf("tray number %d", trayNumber) 36 | return tid 37 | } 38 | 39 | // 2. Pour the cupcake mixture 40 | func MixCupcake(tray string) string { 41 | fmt.Println("Pouring cupcake Mixture in", tray) 42 | time.Sleep(everyThingElseTime * time.Second) 43 | cup := fmt.Sprintf("cupcake in %s", tray) 44 | return cup 45 | } 46 | 47 | // 3. Bake the mixture in oven 48 | func Bake(mixture string) string { 49 | fmt.Println("baking", mixture) 50 | time.Sleep(ovenTime * time.Second) 51 | bakedMix := fmt.Sprintf("baked %s", mixture) 52 | return bakedMix 53 | } 54 | 55 | // 4. Add toppings 56 | func AddTopping(bakedCupcake string) string { 57 | fmt.Println("Adding topping to", bakedCupcake) 58 | time.Sleep(everyThingElseTime * time.Second) 59 | top := fmt.Sprintf("topping on %s", bakedCupcake) 60 | return top 61 | } 62 | 63 | // 5. Pack cupcakes in a box for delivery 64 | func Box(finishedCupcake string) string { 65 | fmt.Println("Boxing", finishedCupcake) 66 | time.Sleep(everyThingElseTime * time.Second) 67 | boxed := fmt.Sprintf("%s is boxed", finishedCupcake) 68 | return boxed 69 | } 70 | 71 | // sequential version of cake 72 | func CakePrepSequentialMain() { 73 | for i := 0; i < 10; i++ { 74 | tray := PrepareTray(i) 75 | mixedCupcake := MixCupcake(tray) 76 | bakedCupcake := Bake(mixedCupcake) 77 | doneCupcake := AddTopping(bakedCupcake) 78 | boxedCupcake := Box(doneCupcake) 79 | fmt.Println("accepting", boxedCupcake) 80 | } 81 | } 82 | 83 | /* 84 | add on pipe ties an input channel to an output channel and maps what comes out 85 | of input channel to what is expected by the output channel. We use the quit 86 | channel pattern to break away from the mapping 87 | */ 88 | func AddOnPipe[Input, Output any](quit <-chan int, InToOutMap func(Input) Output, in <-chan Input) chan Output { 89 | out := make(chan Output) 90 | go func() { 91 | defer close(out) 92 | for { 93 | select { 94 | case <-quit: 95 | return 96 | case input := (<-in): 97 | out <- InToOutMap(input) 98 | } 99 | } 100 | }() 101 | return out 102 | } 103 | 104 | // concurrent version of cake 105 | func CakePrepConcurrentMain() { 106 | input := make(chan int) 107 | quit := make(chan int) 108 | trayOutput := AddOnPipe(quit, PrepareTray, input) 109 | mixCupcakeOutput := AddOnPipe(quit, MixCupcake, trayOutput) 110 | bakeCupcakeOutput := AddOnPipe(quit, Bake, mixCupcakeOutput) 111 | doneCupcakeOutput := AddOnPipe(quit, AddTopping, bakeCupcakeOutput) 112 | output := AddOnPipe(quit, Box, doneCupcakeOutput) 113 | 114 | numInput := 10 115 | go func() { 116 | for i := 0; i < numInput; i++ { 117 | input <- i 118 | } 119 | }() 120 | 121 | // 122 | for j := 0; j < numInput; j++ { 123 | fmt.Println("received", <-output) 124 | } 125 | quit <- 1 // signal quit 126 | } 127 | -------------------------------------------------------------------------------- /exercise/patterns/forkjoin.go: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "math" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | /* 14 | snippet demonstrating fork-join pattern. 15 | 16 | In the loop_level_parallelism we have stated that there are mainly two ways to 17 | decompose a program: 18 | - By data 19 | - By task 20 | 21 | The fork-join pattern fits mostly to task decomposition. We have some task or 22 | a suite of tasks and we want to execute simultaneously. In the fork part of 23 | the pattern we spawn a routine for each task, and gather their results in a 24 | common channel in the join part. 25 | 26 | From selectscenarios that were covered, the fork-join pattern resembles to 27 | broadcast pattern. 28 | 29 | Our example program would find the code with the most nested blocks by 30 | searching through all the files. 31 | */ 32 | 33 | type FileContent struct { 34 | file string 35 | lines []string 36 | } 37 | 38 | // task 1: reading a file content 39 | func readLines(filename string) FileContent { 40 | f, _ := os.Open(filename) 41 | defer f.Close() 42 | 43 | scanner := bufio.NewScanner(f) 44 | lines := make([]string, 1) 45 | for scanner.Scan() { 46 | line := scanner.Text() 47 | lines = append(lines, line) 48 | } 49 | fc := FileContent{filename, lines} 50 | return fc 51 | } 52 | 53 | type FileStat interface { 54 | FileName() string 55 | Value() int 56 | } 57 | 58 | type CodeDepth struct { 59 | file string 60 | num int 61 | } 62 | 63 | func (c CodeDepth) FileName() string { 64 | return c.file 65 | } 66 | 67 | func (c CodeDepth) Value() int { return c.num } 68 | 69 | // task 2: compute code depth for given code text 70 | func deepestNestedBlock(file FileContent) CodeDepth { 71 | lines := file.lines 72 | filename := file.file 73 | maxDepth := 0 74 | level := 0 75 | for _, line := range lines { 76 | for _, c := range line { 77 | if c == '{' { 78 | level += 1 79 | mComp := math.Max(float64(maxDepth), float64(level)) 80 | maxDepth = int(mComp) 81 | } else if c == '}' { 82 | level -= 1 83 | } 84 | } 85 | } 86 | cdepth := CodeDepth{filename, maxDepth} 87 | return cdepth 88 | } 89 | 90 | type FuncNumber struct { 91 | filename string 92 | numFunc int 93 | } 94 | 95 | func (f FuncNumber) FileName() string { return f.filename } 96 | func (f FuncNumber) Value() int { return f.numFunc } 97 | 98 | // task 3: compute number of funcs for given code text 99 | func numberOfFunc(file FileContent) FuncNumber { 100 | nbFunc := 0 101 | lines := file.lines 102 | filename := file.file 103 | 104 | for _, line := range lines { 105 | if strings.Contains(line, "func ") { 106 | nbFunc += 1 107 | } 108 | } 109 | fnumber := FuncNumber{filename, nbFunc} 110 | return fnumber 111 | } 112 | 113 | /* 114 | Given these task let's first see the synchronized version of this code 115 | */ 116 | func ForkJoinSynchronizedMain() { 117 | // get root directory 118 | dir := os.Args[1] 119 | 120 | // 121 | cdepth_ := CodeDepth{"", 0} 122 | funcN := FuncNumber{"", 0} 123 | // file path walk 124 | filepath.Walk(dir, 125 | func(path string, info os.FileInfo, err error) error { 126 | // launch tasks 127 | fcontent := readLines(path) 128 | cdepth := deepestNestedBlock(fcontent) 129 | numFunc := numberOfFunc(fcontent) 130 | if cdepth.Value() > cdepth_.Value() { 131 | cdepth_ = cdepth 132 | } 133 | if numFunc.Value() > funcN.Value() { 134 | funcN = numFunc 135 | } 136 | return nil 137 | }) 138 | 139 | // print results 140 | fmt.Printf("%s has deepest nested code block of %d\n", 141 | cdepth_.FileName(), cdepth_.Value()) 142 | 143 | fmt.Printf("%s has highest number of func of %d\n", 144 | funcN.FileName(), funcN.Value()) 145 | } 146 | 147 | // now let's see how forking is done 148 | func forkReadLinesIfNeed(path string, info os.FileInfo, 149 | wg *sync.WaitGroup, 150 | codeDepths chan<- CodeDepth, 151 | funcNums chan<- FuncNumber, 152 | ) { 153 | if (!info.IsDir()) && (strings.HasSuffix(path, ".go")) { 154 | wg.Add(2) 155 | fc := readLines(path) 156 | 157 | // launch task 1 158 | go func() { 159 | cdepth := deepestNestedBlock(fc) 160 | codeDepths <- cdepth 161 | wg.Done() 162 | }() 163 | // launch task 2 164 | go func() { 165 | numf := numberOfFunc(fc) 166 | funcNums <- numf 167 | wg.Done() 168 | }() 169 | } 170 | } 171 | 172 | type Pair[FirstType, SecondType any] struct { 173 | first FirstType 174 | second SecondType 175 | } 176 | 177 | // Now we do the join part 178 | func joinFileHandler( 179 | codeDepths <-chan CodeDepth, 180 | funcNums <-chan FuncNumber, 181 | ) Pair[chan CodeDepth, chan FuncNumber] { 182 | finalCodeDepth := make(chan CodeDepth) 183 | mxDepth := CodeDepth{"", 0} 184 | go func() { 185 | for result := range codeDepths { 186 | if result.Value() > mxDepth.Value() { 187 | mxDepth = result 188 | } 189 | } 190 | finalCodeDepth <- mxDepth 191 | }() 192 | finalFuncNum := make(chan FuncNumber) 193 | mxFunc := FuncNumber{"", 0} 194 | go func() { 195 | for result := range funcNums { 196 | if result.Value() > mxFunc.Value() { 197 | mxFunc = result 198 | } 199 | } 200 | finalFuncNum <- mxFunc 201 | }() 202 | p := Pair[chan CodeDepth, chan FuncNumber]{finalCodeDepth, finalFuncNum} 203 | return p 204 | } 205 | 206 | // now let's synchronize everything 207 | 208 | // fork join main 209 | func ForkJoinMain() { 210 | dir := os.Args[1] 211 | 212 | // make partial result channels 213 | codeDepths := make(chan CodeDepth) 214 | funcNumbers := make(chan FuncNumber) 215 | 216 | // create the wait group for waiting in the synchronization functions 217 | wg1 := sync.WaitGroup{} 218 | 219 | // file path walk 220 | filepath.Walk(dir, 221 | func(path string, info os.FileInfo, err error) error { 222 | // launch tasks with forks 223 | forkReadLinesIfNeed(path, info, &wg1, codeDepths, funcNumbers) 224 | return nil 225 | }) 226 | 227 | // 228 | // join results 229 | pair := joinFileHandler( 230 | codeDepths, funcNumbers) 231 | 232 | wg1.Wait() 233 | close(codeDepths) 234 | close(funcNumbers) 235 | 236 | // now let's print the values 237 | isCodeDepthOpen, isFuncOpen := true, true 238 | var finalCodeDepth CodeDepth 239 | var finalFuncNum FuncNumber 240 | for isCodeDepthOpen || isFuncOpen { 241 | select { 242 | case finalCodeDepth, isCodeDepthOpen = (<-pair.first): 243 | if isCodeDepthOpen { 244 | fmt.Printf("%s has deepest nested code block of %d\n", 245 | finalCodeDepth.FileName(), finalCodeDepth.Value()) 246 | close(pair.first) 247 | } 248 | case finalFuncNum, isFuncOpen = (<-pair.second): 249 | if isFuncOpen { 250 | fmt.Printf("%s has the highest number of func %d\n", 251 | finalFuncNum.FileName(), finalFuncNum.Value()) 252 | close(pair.second) 253 | } 254 | } 255 | } 256 | } 257 | --------------------------------------------------------------------------------