golang并发基础

1. go协程(go routine)

go原生支持并发:goroutine和channel。

go协程是与其他函数或方法一起并发运行的函数和方法。go协程可以看作是轻量级线程。

调用函数或者方法时,在前面加上关键字go,可以让一个新的GO协程并发地运行。

  • l  启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。
  • l  如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。

2. 信道channel

信道可以想象成Go协程之间通信的管道。

chan T 表示 T 类型的信道。信道与类型相关,只能运输该类型的数据。

信道的零值为 nil。信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make 来定义信道。

data := <- a // 读取信道 a  
a <- data // 写入信道 a

信道发送与接收默认是阻塞的。

信道会产生死锁。当Go协程给一个信道发送数据,而没有信道接收数据时,程序触犯panic,形成死锁。

单向信道

sendch := make(chan<- int)    // 定义单向信道

可以把一个双向信道转换成唯送信道或者唯收信道(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。

参考:

1.   https://studygolang.com/subject/2

猜你喜欢

转载自www.cnblogs.com/embedded-linux/p/11128844.html
今日推荐