Concurrency refers to the ability of a program to execute multiple tasks simultaneously.
\n\nGo supports concurrency, providing a concise and efficient way to implement it through goroutines and channels.
\n\nGoroutines:
\n\n- \n
- The unit of concurrent execution in Go, similar to lightweight threads. \n
- Goroutine scheduling is managed by the Go runtime; users do not need to manually allocate threads. \n
- Use the
gokeyword to start a Goroutine. \n - Goroutines are non-blocking and can efficiently run thousands of them. \n
Channel:
\n\n- \n
- A mechanism in Go for communication between Goroutines. \n
- Supports synchronization and data sharing, avoiding explicit locking mechanisms. \n
- Created using the
chankeyword; data is sent and received using the<-operator. \n
Scheduler:
\n\nGo's scheduler is based on the GMP model. It assigns Goroutines to system threads for execution and efficiently manages concurrency through the coordination of M and P.
\n\n- \n
- G: Goroutine. \n
- M: System thread (Machine). \n
- P: Logical processor (Processor). \n
\n\n
Goroutine
\n\nA goroutine is a lightweight thread, and its scheduling is managed by the Go runtime.
\n\nGoroutine syntax format:
\n\ngo function name( parameter list )\n\nFor example:
\n\ngo f(x, y, z)\n\nStart a new goroutine:
\n\nf(x, y, z)\n\nGo allows using the go statement to start a new runtime thread, i.e., a goroutine, to execute a function in a different, newly created goroutine. All goroutines in the same program share the same address space.
Example
\n\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc sayHello() {\n for i := 0; i < 5; i++ {\n fmt.Println("Hello")\n time.Sleep(100 * time.Millisecond)\n }\n}\n\nfunc main() {\n go sayHello() // Start Goroutine\n for i := 0; i < 5; i++ {\n fmt.Println("Main")\n time.Sleep(100 * time.Millisecond)\n }\n}\n\nExecuting the above code, you will see the output of Main and Hello. The output has no fixed order because they are executed by two goroutines:
\n\nMain\nHello\nMain\nHello\n...\n\n\n\n
Channel
\n\nChannels are used for data transfer between Goroutines.
\n\nChannels can be used to synchronize execution and communicate between two goroutines by passing a value of a specified type.
\n\nUse the make function to create a channel, and use the <- operator to send and receive data. If no direction is specified, it is a bidirectional channel.
ch <- v // Send v to channel ch\nv := <-ch // Receive from ch and assign value to v\n\nDeclaring a channel is simple; we use the chan keyword. Channels must be created before use:
ch := make(chan int)\n\nNote: By default, channels are unbuffered. The sender sends data, and there must be a corresponding receiver to receive the data.
\n\nThe following example calculates the sum of numbers using two goroutines. After the goroutine finishes the calculation, it calculates the sum of the two results:
\n\nExample
\n\npackage main\n\nimport "fmt"\n\nfunc sum(s []int, c chan int) {\n sum := 0\n for _, v := range s {\n sum += v\n }\n c <- sum // Send sum to channel c\n}\n\nfunc main() {\n s := []int{7, 2, 8, -9, 4, 0}\n c := make(chan int)\n go sum(s[:len(s)/2], c)\n go sum(s[len(s)/2:], c)\n x, y := <-c, <-c // Receive from channel c\n fmt.Println(x, y, x+y)\n}\n\nThe output is:
\n\n-5 17 12\n\nChannel Buffer
\n\nChannels can have a buffer, specified by the second parameter of make:
ch := make(chan int, 100)\n\nBuffered channels allow the sender's data sending and the receiver's data retrieval to be asynchronous. This means the data sent by the sender can be placed in the buffer, waiting for the receiver to retrieve it, rather than requiring the receiver to get the data immediately.
\n\nHowever, since the buffer size is limited, there must still be a receiver to receive the data. Otherwise, once the buffer is full, the sender cannot send any more data.
\n\nNote: If the channel is unbuffered, the sender blocks until the receiver receives the value from the channel. If the channel is buffered, the sender blocks until the sent value is copied into the buffer; if the buffer is full, it means waiting until some receiver obtains a value. The receiver blocks until there is a value to receive.
\n\nExample
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n // Here we define a buffered channel that can store integers\n // with a buffer size of 2\n ch := make(chan int, 2)\n\n // Because ch is a buffered channel, we can send two data simultaneously\n // without needing to synchronize the read immediately\n ch <- 1\n ch <- 2\n\n // Retrieve these two data\n fmt.Println(<-ch)\n fmt.Println(<-ch)\n}\n\nThe output is:
\n\n1\n2\n\nGo Traversing and Closing Channels
\n\nGo uses the range keyword to traverse data received, similar to arrays or slices. The format is:
v, ok := <-ch\n\nIf the channel cannot receive data, ok becomes false, and the channel can be closed using the close() function.
Example
\n\npackage main\n\nimport (\n "fmt"\n)\n\nfunc fibonacci(n int, c chan int) {\n x, y := 0, 1\n for i := 0; i < n; i++ {\n c <- x\n x, y = y, x+y\n }\n close(c)\n}\n\nfunc main() {\n c := make(chan int, 10)\n go fibonacci(cap(c), c)\n\n // The range function traverses each data received from the channel.\n // Because c closes the channel after sending 10 data,\n // the range function here ends after receiving 10 data.\n // If the above c channel is not closed, the range function will not\n // end and will block when receiving the 11th data.\n for i := range c {\n fmt.Println(i)\n }\n}\n\nThe output is:
\n\n0\n1\n1\n2\n3\n5\n8\n13\n21\n34\n\nSelect Statement
\n\nThe select statement allows a goroutine to wait on multiple communication operations. select blocks until one of the cases can proceed:
Example
\n\npackage main\n\nimport "fmt"\n\nfunc fibonacci(c, quit chan int) {\n x, y := 0, 1\n for {\n select {\n case c <- x:\n x, y = y, x+y\n case <-quit:\n fmt.Println("quit")\n return\n }\n }\n}\n\nfunc main() {\n c := make(chan int)\n quit := make(chan int)\n go func() {\n for i := 0; i < 10; i++ {\n fmt.Println(<-c)\n }\n quit <- 0\n }()\n fibonacci(c, quit)\n}\n\nIn the above code, the fibonacci goroutine sends the Fibonacci sequence on channel c and exits when it receives a signal from the quit channel.
The output is:
\n\n0\n1\n1\n2\n3\n5\n8\n13\n21\n34\nquit\n\n\n\n
Using WaitGroup
\n\nsync.WaitGroup is used to wait for multiple Goroutines to complete.
Synchronizing Multiple Goroutines:
\n\nExample
\n\npackage main\n\nimport (\n "fmt"\n "sync"\n)\n\nfunc worker(id int, wg *sync.WaitGroup) {\n defer wg.Done() // Call Done() when Goroutine completes\n fmt.Printf("Worker %d startedn", id)\n fmt.Printf("Worker %d finishedn", id)\n}\n\nfunc main() {\n var wg sync.WaitGroup\n for i := 1; i <= 3; i++ {\n wg.Add(1) // Increment counter\n go worker(i, &wg)\n }\n wg.Wait() // Wait for all Goroutines to complete\n fmt.Println("All workers done")\n}\n\nThe output of the above code is:
\n\nWorker 1 started\nWorker 1 finished\nWorker 2 started\nWorker 2 finished\nWorker 3 started\nWorker 3 finished\nAll workers done\n\n\n\n
Advanced Features
\n\nBuffered Channel:
\n\nCreate a buffered Channel.
\n\nch := make(chan int, 2)\n\nContext:
\n\nUsed to control the lifecycle of Goroutines.
\n\ncontext.WithCancel, context.WithTimeout.
Mutex and RWMutex:
\n\nsync.Mutex provides a mutual exclusion lock to protect shared resources.
var mu sync.Mutex\nmu.Lock() // critical section\nmu.Unlock()\n\n\n\n
Concurrency Programming Summary
\n\nGo provides powerful concurrency support through Goroutines and Channels, simplifying the complexity of traditional thread models. Combined with schedulers and synchronization tools, high-performance concurrent programs can be easily implemented.
\n\n- \n
- Goroutines are lightweight threads, started using the
gokeyword. \n - Channels are used for communication between goroutines. \n
- Select Statement is used to wait on multiple channel operations. \n
Common Issues
\n\nDeadlock:
\n\n- \n
- Example: All Goroutines are waiting, but no data is available. \n
- Solution: Avoid infinite waiting; close channels correctly. \n
Data Race:
\n\n- \n
- Example: Multiple Goroutines access the same variable simultaneously. \n
- Solution: Use Mutex or Channel to synchronize access. \n
YouTip