1. Multiple independent tasks
package main import ( "fmt" "runtime" ) /** Concurrent multiple independent tasks */ func main() { //The number of goroutines processing tasks (the number of processing tasks is the same as the number of micro-loops waiting for all goroutines to be processed) workers := runtime.NumCPU() //use the maximum number of cups runtime.GOMAXPROCS(workers) //Task channel, all tasks are sent to this channel jobs := make(chan int, workers) //Complete chan, when the task is processed, send a data to this chan. Used to monitor that all goroutines are executed, the data type is not required done := make(chan bool, workers) //Create a gotoutine for generating tasks go generateJob(jobs) // process the task processJob(done, jobs, workers) //Wait for all goroutines to finish executing waitUntil (done, workers) } /** build task jobs chan is to receive chan only */ func generateJob(jobs chan<- int) { for i := 1; i <= 10; i++ { jobs <- i } //Close the channel after all data is sent. This method just tells the place where this channel is used, that this channel is no longer sending data, and it is not really closed. close(jobs) } /** processing tasks Only send data to done chan only receive data from jobs chan Number of goroutines created by workers */ func processJob(done chan<- bool, jobs <-chan int, workers int) { for i := 0; i < workers; i++ { go func() { //Block when there is no data in the current jobs chan until the close(jobs) method is called, or there is data for job := range jobs { // process the task fmt.Println(job) } //This goroutine is executed, and the identifier is stored in done done <- true }() } } //Wait for all goroutines to finish executing func waitUntil(done <-chan bool, workers int) { for i := 0; i < workers; i++ { <-done } }illustrate:
1) The number of goroutines processing tasks is the same as the number of waiting for all goroutines to complete the loop (waitUntil method), which is: the number of workers
2) The defined jobs channel is cached. If you want to execute it in sequence, you can not use the cache, so that it can only be processed one by one.
3) There is a method to close the jobs channel in the generateJob method, which just tells the place where this channel is used that no data has been sent to it, and there is no real closure. There is no code in the code to close the done channel, because the channel is not used where it is necessary to check if the channel is closed. In the waitUntil method, done blocking ensures that all processing is completed before the main goroutine exits.
2. Mutex (lock)
package safe import "sync" /** safety map */ type SafeMap struct { //map CountMap map[string]int //mutexes mutex *sync.RWMutex } /** create */ func NewSafeMap() *SafeMap { return *SafeMap{make(map[string]int), new(sync.RWMutex)} } /* add count */ func (sm *SafeMap) Increment(str string) { //acquire the lock sm.mutex.Lock() // release the lock defer sm.mutex.Unlock() //count sm.CountMap[str]++ } /* read value */ func (sm *SafeMap) Read(str string) int { //Get the read lock sm.mutex.RLock() // release the lock defer sm.mutex.RUnlock() //count v, e: = sm.CountMap [page] if !e { return v } else { return 0 } } package safe import "sync" /** safety map */ type SafeMap struct { //map CountMap map[string]int //mutexes mutex *sync.RWMutex } /** create */ func NewSafeMap() *SafeMap { return *SafeMap{make(map[string]int), new(sync.RWMutex)} } /* add count */ func (sm *SafeMap) Increment(str string) { //acquire the lock sm.mutex.Lock() // release the lock defer sm.mutex.Unlock() //count sm.CountMap[str]++ } /* read value */ func (sm *SafeMap) Read(str string) int { //Get the read lock sm.mutex.RLock() // release the lock defer sm.mutex.RUnlock() //count v, e: = sm.CountMap [page] if !e { return v } else { return 0 } }illustrate:
1), SafeMap is a multi-thread safe map, which creates a read-write lock through new(sync.RWMutex). When operating the map, the mutex must be locked first, and the mutex is guaranteed to be released by defer. (This is only one method written)
2) In the Read method, the acquired read lock can be more efficient.
3) A safe map can also be achieved by sending the operation on the map to a channel without cache, and then operating on the map.
3. Merge the results of multiple tasks
Example: Calculate the factorial of 50
package main import ( "fmt" "runtime" ) func main() { workers := runtime.NumCPU() //use the maximum number of cups runtime.GOMAXPROCS(workers) //task chann jobs := make(chan section, workers) //Create a gotoutine for generating tasks go generateJob(jobs) //Store the goroutine operation result of each processing task result := make(chan uint64, workers) // process the task processJob(result, jobs, workers) //calculate the product of all results factorialResult := caclResult(result, workers) fmt.Println(factorialResult) } /** build task jobs chan is to receive chan only */ func generateJob(jobs chan<- section) { jobs <- section{1, 10} jobs <- section{11, 20} jobs <- section{21, 30} jobs <- section{31, 40} jobs <- section{41, 50} //Close the channel after all data is sent. This method just tells the place where this channel is used, that this channel is no longer sending data, and it is not really closed. close(jobs) } func processJob(result chan<- uint64, jobs <-chan section, workers int) { for i := 0; i < workers; i++ { go func() { //factorial var factorial uint64 = 1 for job := range jobs { for i := job.min; i <= job.max; i++ { factorial *= uint64(i) } } //After the calculation, put the result in the result chan result <- factorial }() } } /** Calculate all results */ func caclResult(result <-chan uint64, workers int) uint64 { var factorial uint64 = 1 for i := 0; i < workers; i++ { count := <-result factorial *= count } return factorial } /** Save the maximum and minimum value of the factorial interval */ type section struct { min int max int }
illustrate:
1), is to calculate the factorial score of 50, the sample code is to calculate the product of 1..10, 11..20 ... and then calculate the product of each result
2) In generateJob, the struct of the interval to be calculated is put into the jobs channel
3) In processJob, each goroutine creates a calculation result and sends it to the result channel after the calculation is completed. result holds all calculation results.
4) The code that listens to the main goroutine is replaced with the calculation of the total result.
4. Indefinite number of goroutines
package main import ( "fmt" "runtime" "sync" "time" ) //Maximum number of goroutines const maxGoroutines = 10 //According to the amount of tasks, decide how many goroutines to use func main() { //Define an array to calculate the factorial. Assuming that the larger the value, the slower the processing (implemented by the time.Sleep(time.Duration(num) * time.Second / 10) code) factorialArray := []int{5, 8, 10, 50, 20, 11, 16, 19, 38, 20, 49, 36, 22, 33, 45, 29} //The channel to store the calculation result factorialResultChan := make(chan map[int]uint64, maxGoroutines*2) //calculate factorial go doFactorial(factorialResultChan, factorialArray) // show the result showResult(factorialResultChan) } /** Calculate factorial */ func doFactorial(factorialResultChan chan map[int]uint64, factorialArray []int) { waiter := &sync.WaitGroup{} for _, v := range factorialArray { calc(factorialResultChan, v, waiter) } waiter.Wait() close(factorialResultChan) } func calc(factorialResultChan chan map[int]uint64, num int, waiter *sync.WaitGroup) { if num < 10 || runtime.NumGoroutine() > maxGoroutines { factorialResultChan <- map[int]uint64{num: doCalc(num)} } else { waiter.Add(1) go func() { factorialResultChan <- map[int]uint64{num: doCalc(num)} waiter.Done() }() } } /** factorial calculation */ func doCalc(num int) { uint64 var r uint64 = 1 //The value is large, the processing is slow time.Sleep(time.Duration(num) * time.Second / 10) for i := 1; i <= num; i++ { r * = uint64 (i) } return r } /** Results display */ func showResult(result chan map[int]uint64) { for m := range result { for k, v := range m { fmt.Printf("%d:%d", k, v) fmt.Println("") } } }illustrate:
1) The doFactorial function is executed in a goroutine, traversing the factorialArray slices, and calculating the factorial.
The calc function is called during the traversal: if the value for calculating the factorial is less than 10, or the number of goroutines already running (runtime.NumGoroutine()) is greater than the maximum set number of goroutines, run in this goroutine,
otherwise, start a goroutine to calculate the factorial .
2) When the factorialArray slice is uncertain, it does not know how many goroutines are created by the calc method. A sync.WaitGroup is created for this purpose. Every time a goroutine is created, the sync.WaitGroup.Add() function is called once, and the sync.WaitGroup.Done() function
is called when the execution is complete. After all goroutines are set up to run, call the sync.WaitGroup.Wait() function and wait for all working goroutines to finish.
sync.WaitGroup.Wait() blocks until the number of done and added is equal.
3) When all the work goroutines are executed, the factorialResultChan channel is closed. Of course the showResult function can still read the channel until all the data has been read.