golang concurrency

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.


Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326748323&siteId=291194637