1. go协程(go routine)git
go原生支持并发:goroutine和channel。golang
go协程是与其余函数或方法一块儿并发运行的函数和方法。go协程能够看做是轻量级线程。数据库
调用函数或者方法时,在前面加上关键字go,可让一个新的GO协程并发地运行。服务器
- l 启动一个新的协程时,协程的调用会当即返回。与函数不一样,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程以后,程序控制会当即返回到代码的下一行,忽略该协程的任何返回值。
- l 若是但愿运行其余 Go 协程,Go 主协程必须继续运行着。若是 Go 主协程终止,则程序终止,因而其余 Go 协程也不会继续运行。
2. 信道channel并发
信道能够想象成Go协程之间通讯的管道。dom
chan T 表示 T 类型的信道。信道与类型相关,只能运输该类型的数据。函数
信道的零值为 nil。信道的零值没有什么用,应该像对 map 和切片所作的那样,用 make 来定义信道。google
data := <- a // 读取信道 a a <- data // 写入信道 a
信道发送与接收默认是阻塞的。url
信道会产生死锁。当Go协程给一个信道发送数据,而没有信道接收数据时,程序触犯panic,造成死锁。spa
单向信道
sendch := make(chan<- int) // 定义单向信道,定义只写数据的信道,<-指向chan
能够把一个双向信道转换成惟送信道或者惟收信道(send only or receive only),但反过来不能够。
关闭信道
数据发送方能够关闭信道,通知接收方这个信道再也不有数据发送过来。
当从信道接收数据时,接收方能够多用一个变量来检查信道是否已经关闭。
v, ok := <- ch
若是能够信道能够接收数据,ok等于true;若是信道关闭,ok等于false。
从一个关闭的信道中读取到的值时该信道类型的零值。
range遍历信道
for range 循环用于在一个信道关闭以前,从信道接收数据。
ch := make(chan int) go producer(ch) for v := range ch { fmt.Println("Received ",v) }
3. 缓冲信道
buffered Channel只有缓冲已满的状况才会阻塞发送数据,一样只有缓冲为空时才阻塞接收数据。
ch := make(chan type, capacity) // capacity大于0
缓冲信道的容量是指信道能够存储的值的数量。缓冲信道的长度是指信道中当前排队的元素个数。
4.工做池
WaitGroup
用于等待一批 Go 协程执行结束。程序控制会一直阻塞,直到这些协程所有执行完毕。
定义:var wg sync.WaitGroup
调用:wg.Add(1)…wg.Done()…wg.Wait()
package main import ( "fmt" "sync" "time" ) func process(i int, wg *sync.WaitGroup) { fmt.Println("started Goroutine ", i) time.Sleep(2 * time.Second) fmt.Printf("Goroutine %d ended\n", i) wg.Done() } func main() { no := 3 var wg sync.WaitGroup for i := 0; i < no; i++ { wg.Add(1) go process(i, &wg) } wg.Wait() fmt.Println("All go routines finished executing") } output: started Goroutine 2 started Goroutine 0 started Goroutine 1 Goroutine 1 ended Goroutine 2 ended Goroutine 0 ended All go routines finished executing
协程中传参wg地址很是重要,wg.Done()执行完毕后主协程才知道。如果值拷贝,main函数不知道。
package main import ( "fmt" "math/rand" "sync" "time" ) type Job struct { id int randomno int } type Result struct{ job Job sumofdigits int } var jobs = make(chan Job, 10) var results = make(chan Result, 10) func sum_digits(number int)int { sum := 0 for number !=0 { i:= number%10 sum += i number = number/10 } time.Sleep(2*time.Second) return sum } func worker(wg *sync.WaitGroup){ for job := range jobs{ output := Result{job, sum_digits(job.randomno)} results <- output } wg.Done() } func create_worker_pool(num_workers int){ var wg sync.WaitGroup for i:=0; i < num_workers; i++{ wg.Add(1) go worker(&wg) } wg.Wait() close(results) } func allocate(num_jobs int){ for i := 0; i < num_jobs; i++{ randomno := rand.Intn(999) job := Job{i, randomno} jobs <- job } close(jobs) } func result(done chan bool){ for result := range results{ fmt.Printf("Job id %d, input random no %d, sum of digits %d\n", result.job.id, result.job.randomno, result.sumofdigits) } done <- true } func main(){ startTime := time.Now() num_jobs := 100 go allocate(num_jobs) done := make(chan bool) go result(done) num_workers := 10 create_worker_pool(num_workers) <-done endTime := time.Now() diff := endTime.Sub(startTime) fmt.Println("total time taken ", diff.Seconds(), "seconds") }
5.select
select
语句用于在多个发送/接收信道操做中进行选择。select
语句会一直阻塞,直到发送/接收操做准备就绪。若是有多个信道操做准备完毕,select
会随机地选取其中之一执行。该语法与 switch
相似,所不一样的是,这里的每一个 case
语句都是信道操做。
在没有 case 准备就绪时,能够执行 select
语句中的默认状况(Default Case)。这一般用于防止 select
语句一直阻塞。
package main import ( "fmt" "time" ) func server1(ch chan string) { time.Sleep(6 * time.Second) ch <- "from server1" } func server2(ch chan string) { time.Sleep(3 * time.Second) ch <- "from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) select { case s1 := <-output1: fmt.Println(s1) case s2 := <-output2: fmt.Println(s2) } }
select应用:假设咱们有一个关键性应用,须要尽快地把输出返回给用户。这个应用的数据库复制而且存储在世界各地的服务器上。咱们向两台服务器发送请求,并使用 select
语句等待相应的信道发出响应。select
会选择首先响应的服务器,而忽略其它的响应。使用这种方法,咱们能够向多个服务器发送请求,并给用户返回最快的响应了。
package main func main() { select {} }
select
语句没有任何 case,所以它会一直阻塞,致使死锁。该程序会触发 panic。
6.mutex
Mutex 用于提供一种加锁机制(Locking Mechanism),可确保在某时刻只有一个协程在临界区运行,以防止出现竞态条件。
var mutex sync.Mutex mutex.Lock() x = x + 1 mutex.Unlock()
信道处理竞态条件
ch <- true x = x + 1 <- ch
当 Go 协程须要与其余协程通讯时,可使用信道。而当只容许一个协程访问临界区时,可使用 Mutex。
7. once
sync.Once能够控制函数只能被调用一次,不会被屡次重复调用。
func (o *Once) Do(f func())
import ( "sync" ) type Watchers struct { devices map[string] string } var ( wcOnce sync.Once watchers *Watchers ) // Create a singleton WatcherCache instance func newWatchers() *Watchers { wcOnce.Do(func() { watchers = &Watchers{} }) return watchers }
sync.Once.Do(f func())能保证once只执行一次,不管以后是否更换once.Do(xx)里的方法。
package main import ( "fmt" "sync" ) func main(){ var once sync.Once once.Do(func(){ fmt.Println("Test sync Once") }) }
参考: