go通道与协程

协程

Go ,应用程序并发处理的部分被称作 goroutines(协程) ,它可以进行更有效的并发运算。

操作系统会在物理处理器上调度线程来运行,而 Go 语言的运行时会在逻辑处理器上调度

goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。

协程是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程

最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。

这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

下面将通过具体例子来说明:

    1.   
    2. import (  
    3.    "fmt"  
    4.    "time"  
    5.   )  
    6.   
    7.   
    8. func main() {  
    9.     fmt.Println("the main begin")  
    10.     go longWait()  
    11.     go shortWait()  
    12.     fmt.Println("About to sleep in main()")  
    13.     // sleep works with a Duration in nanoseconds (ns) !  
    14.     time.Sleep(10 * 1e9)  
    15.     fmt.Println("At the end of main()")  
    16. }  
    17.   
    18.   
    19. func longWait() {  
    20.     fmt.Println("Beginning longWait()")  
    21.     time.Sleep(5 * 1e9) // sleep for 5 seconds  
    22.     fmt.Println("End of longWait()")  
    23. }  
    24. func shortWait() {  
    25.     fmt.Println("Beginning shortWait()")  
    26.     time.Sleep(2 * 1e9) // sleep for 2 seconds  
    27.     fmt.Println("End of shortWait()")  

main() , longWait() shortWait() 三个函数作为独立的处理单元按顺序启动,然后开始并行运行。每一个函数都在运行的开始和结束阶段输出了消息。为了模拟他们运算的时间消耗,我们使用了time 包中的 Sleep 函数。 Sleep() 可以按照指定的时间来暂停函数或协程的执行,这里使用了纳秒(ns,符号 1e9 表示 1 10 9 次方,e=指数)他们按照我们期望的顺序打印出了消息,几乎都一样,可是我们明白这是模拟出来的,以并行的方式。我们让 main() 函数暂停 10 秒从而确定它会在另外两个协程之后结束。如果不这样(如果我们让main() 函数停止 4 ), main() 会提前结束, longWait() 则无法完成。如果我们不在main() 中等待,协程会随着程序的结束而消亡。当 main() 函数返回的时候,程序退出:它不会等待任何其他非 main 协程的结束。这就是为什么在服务器程序中,每一个请求都会启动一个协程来处理, server() 函数必须保持运行状态。通常使用一个无限循环来达到这样的目的。另外,协程是独立的处理单元,一旦陆续启动一些协程,你无法确定他们是什么时候真正开始执行的。你的代码逻辑必须独立于协程调用的顺序。

如果把go关键字去掉,则不是协程 ,运行结果为

通道(channel)

Go有一个特殊的类型, 通道(channel) ,像是通道(管道),可以通过它们发送类型化的数据在协程之间通信,可以避开所有内存共享导致的坑;通道的通信方式保证了同步性。数据通过通道:同一时间只有一个协程可以访问数据:所以不会出现数据竞争,设计如此。数据的归属(可以读写数据的能力)被传递。

通道服务于通信的两个目的:值的交换,同步的,保证了两个计算(协程)任何时候都是可知状态

var name chan datatype

var ch1 chan int

ch1 = make(chan int)

或者 ch1 := make(chan int )

函数通道: funcChan := chan func()

所以通道只能传输一种类型的数据,比如 chan int 或者 chan string ,所有的类型都可以用于通道,空接口 interface{} 也可以。甚至可以(有时非常有用)创建通道的通道。通道实际上是类型化消息的队列:使数据得以传输。它是先进先出(FIFO)结构的所以可以保证发送给他们的元素的顺序(有些人知道,通道可以比作 Unix shells 中的双向管道(tw-way pipe))。通道也是引用类型,所以我们使用 make() 函数来给它分配内存。这里先声明了一个字符串通道 ch1,然后创建了它(实例化)这个操作符直观的标示了数据的传输:信息按照箭头的方向流动。

流向通道(发送)

ch <- int1 表示:用通道 ch 发送变量 int1(双目运算符,中缀 = 发送)

从通道流出(接收),三种方式:

int2 = <- ch 表示:变量 int2 从通道 ch(一元运算的前缀操作符,前缀 = 接收)接收数据(获取

新值);假设 int2 已经声明过了,如果没有的话可以写成: int2 := <- ch 。

<- ch 可以单独调用获取通道的(下一个)值,当前值会被丢弃,但是可以用来验证,下面是一个简单的例子来说明协程之间通过通道来进行通信的机制

    1.   
    2. import (  
    3.     "fmt"  
    4.     "time"  
    5. )  
    6.   
    7.   
    8. func main() {  
    9.     ch :=make(chan string)  
    10.   
    11.     go sendData(ch)  
    12.     go getData(ch)  
    13.   
    14.     time.Sleep(1e9)  
    15. }  
    16.   
    17. func sendData(ch chan string){  
    18.     ch <- "Beijing"  
    19.     ch <- "Tokyo"  
    20.     ch <- "Washington"  
    21.     ch <- "London"  
    22.     ch <-  "Moscow"  
    23. }  
    24.   
    25. func getData(ch chan string){  
    26.     // var input string   
    27.     for input := range ch {  
    28.           
    29.         fmt.Printf("%s\n",input)  
    30.     }  
    31. } 

一个传递数据,一个接收数据,传递数据通过通道,读取通过 "range  ch“的方式可以遍历   

Select等待机制

    1.   
    2. import (  
    3.     "fmt"  
    4.     "time"  
    5. )  
    6.   
    7. func main() {  
    8.     ch1 :=make(chan int)  
    9.     ch2 :=make(chan int)  
    10.   
    11.     go pump1(ch1)  
    12.     go pump2(ch2)  
    13.   
    14.     go suck(ch1,ch2)  
    15.     time.Sleep(1e9)  
    16. }  
    17.   
    18. func pump1(ch chan int){  
    19.     for i := 0;i<10; i++ {  
    20.         ch <- i * 2  
    21.     }  
    22. }  
    23.   
    24. func pump2(ch chan int){  
    25.     for i :=0;i<10;i++{  
    26.         ch <- i+5  
    27.     }  
    28. }  
    29.   
    30. func suck(ch1,ch2 chan int) {  
    31.     for {  
    32.         select {  
    33.             case v := <-ch1:  
    34.             fmt.Printf("Received on channel 1: %d\n", v)  
    35.             case v := <-ch2:  
    36.             fmt.Printf("Received on channel 2: %d\n", v)  
    37.         }  
    38. }  
    39. }  

有 2 个通道 ch1 和 ch2 ,三个协程 pump1() 、 pump2() 和suck() 。这是一个典型的生产者消费者模式。在无限循环中, ch1 和 ch2 通过 pump1() 和pump2() 填充整数; suck() 也是在无限循环中轮询输入的,通过 select 语句获取 ch1 和ch2 的整数并输出。选择哪一个 case 取决于哪一个通道收到了信息。程序在 main 执行 1 秒后结束。

发布了123 篇原创文章 · 获赞 71 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/boke14122621/article/details/87867191