精华内容
参与话题
问答
  • goroutine

    千次阅读 2018-08-21 16:51:50
    goroutine 并发通信 并发概念   回到在 Windows 和 Linux 出现之前的古老年代,程序员在开发程序时并没有并发的概念,因为命令式程序设计语言是以串行为基础的,程序会顺序执行每一条指令,整个程序只有一个...

    并发概念

      回到在 Windows 和 Linux 出现之前的古老年代,程序员在开发程序时并没有并发的概念,因为命令式程序设计语言是以串行为基础的,程序会顺序执行每一条指令,整个程序只有一个执行上下文,即一个调用栈,一个堆。并发则意味着程序在运行时有多个执行上下文,对应着多个调用栈。我们知道每一个进程在运行时,都有自己的调用栈和堆,有一个完整的上下文,而操作系统在调度进程的时候,会保存被调度进程的上下文环境,等该进程获得时间片后,再恢复该进程的上下文到系统中。

      从整个操作系统层面来说,多个进程是可以并发的,那么并发的价值何在?下面我们先看以下几种场景。

    • 一方面我们需要灵敏响应的图形用户界面,一方面程序还需要执行大量的运算或者 IO 密集操作,而我们需要让界面响应与运算同时执行。
    • 当我们的 Web 服务器面对大量用户请求时,需要有更多的Web 服务器工作单元来分别响应用户。
    • 我们的事务处于分布式环境上,相同的工作单元在不同的计算机上处理着被分片的数据。
    • 计算机的 CPU 从单内核(core)向多内核发展,而我们的程序都是串行的,计算机硬件的能力没有得到发挥。
    • 我们的程序因为 IO 操作被阻塞,整个程序处于停滞状态,其他 IO 无关的任务无法执行。

      从以上几个例子可以看到,串行程序在很多场景下无法满足我们的要求。下面归纳了并发程序的几条优点,可以发现并发的势在必行:

    • 并发能更客观地表现问题模型;
    • 并发可以充分利用 CPU 核心的优势,提高程序的执行效率;
    • 并发能充分利用 CPU 与其他硬件设备固有的异步性。

      现在我们已经意识到并发的好处了,那么到底有哪些方式可以实现并发执行呢?就目前而言,并发包含以下几种主流的实现模型。

    • 多进程。多进程是在操作系统层面进行并发的基本模式。同时也是开销最大的模式。在 Linux 平台上,很多工具链正是采用这种模式在工作。比如某个 Web 服务器,它会有专门的进程负责网络端口的监听和链接管理,还会有专门的进程负责事务和运算。这种方法的好处在于简单、进程间互不影响,坏处在于系统开销大,因为所有的进程都是由内核管理的。
    • 多线程。多线程在大部分操作系统上都属于系统层面的并发模式,也是我们使用最多的最有效的一种模式。目前,我们所见的几乎所有工具链都会使用这种模式。它比多进程的开销小很多,但是其开销依旧比较大,且在高并发模式下,效率会有影响。
    • 基于回调的非阻塞/异步 IO。这种架构的诞生实际上来源于多线程模式的危机。在很多高并发服务器开发实践中,使用多线程模式会很快耗尽服务器的内存和 CPU 资源。而这种模式通过事件驱动的方式使用异步 IO,使服务器持续运转,且尽可能地少用线程,降低开销,它目前在 Node.js 中得到了很好的实践。但是使用这种模式,编程比多线程要复杂,因为它把流程做了分割,对于问题本身的反应不够自然。
    • 协程。协程(Coroutine)本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中,因此,系统开销极小,可以有效提高线程的任务并发性,而避免多线程的缺点。使用协程的优点是编程简单,结构清晰;缺点是需要语言的支持,如果不支持,则需要用户在程序中自行实现调度器。目前,原生支持协程的语言还很少。

      接下来先诠释一下传统并发模型的缺陷,之后再所说 goroutine 并发模型是如何逐一解决这些缺陷的。

      人的思维模式可以认为是串行的,而且串行的事务具有确定性。线程类并发模式在原先的确定性中引入了不确定性,这种不确定性给程序的行为带来了意外和危害,也让程序变得不可控。线程之间通信只能采用共享内存的方式。为了保证共享内存的有效性,我们采取了很多措施,比如加锁等,来避免死锁或资源竞争。实践证明,我们很难面面俱到,往往会在工程中遇到各种奇怪的故障和问题。

      我们可以将之前的线程加共享内存的方式归纳为共享内存系统,虽然共享内存系统是一种有效的并发模式,但它也暴露了众多使用上的问题。计算机科学家们在近40年的研究中又产生了一种新的系统模型,称为消息传递系统

      对线程间共享状态的各种操作都被封装在线程之间传递的消息中,这通常要求:发送消息时对状态进行复制,并且在消息传递的边界上交出这个状态的所有权。从逻辑上来看,这个操作与共享内存系统中执行的原子更新操作相同,但从物理上来看则非常不同。由于需要执行复制操作,所以大多数消息传递的实现在性能上并不优越,但线程中的状态管理工作通常会变得更为简单。

      最早被广泛应用的消息传递系统是由 C. A. R. Hoare 在他的 Communicating Sequential Processes 中提出的。在 CSP 系统中,所有的并发操作都是通过独立线程以异步运行的方式来实现的。这些线程必须通过在彼此之间发送消息,从而向另一个线程请求信息或者将信息提供给另一个线程。使用类似 CSP 的系统将提高编程的抽象级别。

      随着时间的推移,一些语言开始完善消息传递系统,并以此为核心支持并发,比如 Erlang。

    协程

      执行体是个抽象的概念,在操作系统层面有多个概念与之对应,比如操作系统自己掌管的进程(process)、进程内的线程(thread)以及进程内的协程(coroutine,也叫轻量级线程)。与传统的系统级线程和进程相比,协程的最大优势在于其轻量级,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。这也是协程也叫轻量级线程的原因。

      多数语言在语法层面并不直接支持协程,而是通过库的方式支持,但用库的方式支持的功能也并不完整,比如仅仅提供轻量级线程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行轻量级线程,从而无法真正达到轻量级线程本身期望达到的目标。

      Go 语言在语言级别支持轻量级线程,叫 goroutine。Go 语言标准库提供的所有系统调用操作(当然也包括所有同步 IO 操作),都会出让 CPU 给其他 goroutine。这让事情变得非常简单,让轻量级线程的切换管理不依赖于系统的线程和进程,也不依赖于 CPU 的核心数量。

    goroutine

      goroutine是 Go 语言中的轻量级线程实现,由 Go 运行时(runtime)管理。你将会发现,它的使用出人意料得简单。

      假设我们需要实现一个函数 Add(),它把两个参数相加,并将结果打印到屏幕上,具体代码如下:

    func Add(x, y int) {
        z := x + y
        fmt.Println(z)
    }

      那么,如何让这个函数并发执行呢?具体代码如下:

    go Add(1, 1)

      是不是很简单?

      你应该已经猜到,go 这个单词是关键。与普通的函数调用相比,这也是唯一的区别。的确,go 是 Go 语言中最重要的关键字,这一点从 Go 语言本身的命名即可看出。

      在一个函数调用前加上 go 关键字,这次调用就会在一个新的 goroutine 中并发执行。当被调用的函数返回时,这个 goroutine 也自动结束了。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃

    package main
    
    import "fmt"
    
    func Add(x, y int) {
        z := x + y
        fmt.Println(z)
    }
    
    func main() {
        for i := 0; i < 10; i++ {
            go Add(i, i)
        }
    }

      在上面的代码里,在一个 for 循环中调用了10次 Add() 函数,它们是并发执行的。可是当你编译执行了上面的代码,就会发现一些奇怪的现象:

      “什么?!屏幕上什么都没有,程序没有正常工作!”

      是什么原因呢?明明调用了10次 Add(),应该有10次屏幕输出才对。要解释这个现象,就涉及 Go 语言的程序执行机制了。

      Go 程序从初始化 main package 并执行 main() 函数开始,当 main() 函数返回时,程序退出,且程序并不等待其他 goroutine(非主 goroutine)结束。

      对于上面的例子,主函数启动了10个 goroutine,然后返回,这时程序就退出了,而被启动的执行 Add(i, i) 的 goroutine 没有来得及执行,所以程序没有任何输出。

      OK,问题找到了,怎么解决呢?提到这一点,估计写过多线程程序的读者就已经恍然大悟,并且摩拳擦掌地准备使用类似 WaitForSingleObject、sleep 之类的调用,或者写个自己很拿手的忙等待或者稍微先进一些的 sleep 循环等待来等待所有线程执行完毕。

      在 Go 语言中有自己推荐的方式,它要比这些方法都优雅得多。

      要让主函数等待所有 goroutine 退出后再返回,如何知道 goroutine 都退出了呢?这就引出了多个 goroutine 之间通信的问题。

    并发通信

      从上面的例子中可以看到,关键字 go 的引入使得在 Go 语言中并发编程变得简单而优雅,但我们同时也应该意识到并发编程的原生复杂性,并时刻对并发中容易出现的问题保持警惕。别忘了,我们的例子还不能正常工作呢。

      事实上,不管是什么平台,什么编程语言,不管在哪,并发都是一个大话题。话题大小通常也直接对应于问题的大小。并发编程的难度在于协调,而协调就要通过交流。从这个角度看来,并发单元间的通信是最大问题。

      在工程上,有两种最常见的并发通信模型:共享数据消息

      共享数据是指多个并发单元分别保持对同一个数据的引用,实现对该数据的共享。被共享的数据可能有多种形式,比如内存数据块、磁盘文件、网络数据等。在实际工程应用中最常见的无疑是内存了,也就是常说的共享内存

      先看看在 C 语言中通常是怎么处理线程间数据共享的,如下代码清单所示。

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    void *count();
    
    pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    
    int counter = 0;
    
    main()
    {
        int rc1, rc2;
        pthread_t thread1, thread2;
    
        /* 创建线程,每个线程独立执行函数functionC */
        if((rc1 = pthread_create(&thread1, NULL, &add, NULL)))
        {
            printf("Thread creation failed: %d\n", rc1);
        }
        if((rc2 = pthread_create(&thread2, NULL, &add, NULL)))
        {
            printf("Thread creation failed: %d\n", rc2);
        }
    
        /* 等待所有线程执行完毕 */
        pthread_join( thread1, NULL);
        pthread_join( thread2, NULL);
        exit(0);
    }
    
    void *count()
    {
        pthread_mutex_lock( &mutex1 );
        counter++;
        printf("Counter value: %d\n",counter);
        pthread_mutex_unlock( &mutex1 );
    }

      现在尝试将这段 C 语言代码直接翻译为 Go 语言代码,如下代码清单所示。

    package main
    
    import "fmt"
    import "sync"
    import "runtime"
    
    var counter int = 0
    
    func Count(lock *sync.Mutex) {
        lock.Lock()
        counter++
        fmt.Println(z)
        lock.Unlock()
    }
    
    func main() {
        lock := &sync.Mutex{}
    
        for i := 0; i < 10; i++ {
            go Count(lock)
        }
    
        for {
            lock.Lock()
            c := counter
            lock.Unlock()
            runtime.Gosched()
            if c >= 10 {
                break
            }
        }
    }

      此时这个例子终于可以正常工作了。在上面的例子中,我们在10个 goroutine 中共享了变量 counter。每个 goroutine 执行完成后,将 counter 的值加1。因为10个 goroutine 是并发执行的,所以我们还引入了锁,也就是代码中的 lock 变量。每次对 n 的操作,都要先将锁锁住,操作完成后,再将锁打开。在主函数中,使用 for 循环来不断检查 counter 的值(同样需要加锁)。当其值达到10时,说明所有 goroutine 都执行完毕了,这时主函数返回,程序退出。

      事情好像开始变得糟糕了。实现一个如此简单的功能,却写出如此臃肿而且难以理解的代码。想象一下,在一个大的系统中具有无数的锁、无数的共享变量、无数的业务逻辑与错误处理分支,那将是一场噩梦。这噩梦就是众多 C/C++ 开发者正在经历的,其实 Java 和 C# 开发者也好不到哪里去。

      Go 语言既然以并发编程作为语言的最核心优势,当然不至于将这样的问题用这么无奈的方式来解决。Go 语言提供的是另一种通信模型,即以消息机制而非共享内存作为通信方式。

      消息机制认为每个并发单元是自包含的、独立的个体,并且都有自己的变量,但在不同并发单元间这些变量不共享。每个并发单元的输入和输出只有一种,那就是消息。这有点类似于进程的概念,每个进程不会被其他进程打扰,它只做好自己的工作就可以了。不同进程间靠消息来通信,它们不会共享内存。

      Go 语言提供的消息通信机制被称为 channel接下来我们将详细记录 channel。现在,用 Go 语言社区的那句著名的口号来结束这一小节:

      不要通过共享内存来通信,而应该通过通信来共享内存。

    展开全文
  • Goroutine

    千次阅读 2011-04-14 13:04:00
    Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Goroutine能够让你的程序以异步的方式运行,而不需要担心一个函数导致程序中断,因此Go语言也非常地适合网络服务。 我们通过go让其中一...

    Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Goroutine能够让你的程序以异步的方式运行,而不需要担心一个函数导致程序中断,因此Go语言也非常地适合网络服务。

    我们通过go让其中一个函数同步运行,如此就不需要等待该函数运行完后才能运行下一个函数。

    func main() {

    // 通过 `go`,我们可以把这个函数异步执行,这样就不会阻塞往下执行。 go loop()

    // 执行 Other

    }

    Goroutine是类似线程的概念(但Goroutine并不是线程)。线程属于系统层面,通常来说创建一个新的线程会消耗较多的资源且管理不易。而 Goroutine就像轻量级的线程,但我们称其为并发,一个Go程序可以运行超过数万个 Goroutine,并且这些性能都是原生级的,随时都能够关闭、结束。一个核心里面可以有多个Goroutine,通过GOMAXPROCS参数你能够限制Gorotuine可以占用几个系统线程来避免失控。

    在内置的官方包中也不时能够看见Goroutine的应用,像是net/http中用来监听网络服务的函数实际上是创建一个不断运行循环的Goroutine。

     

    设置同时执行的cpu数(GOMAXPROCS)

    GOMAXPROCS 在调度程序优化后会去掉,默认用系统所有资源。

    func main() {

    num := runtime.NumCPU() //本地机器的逻辑CPU个数

    runtime.GOMAXPROCS(num) //设置可同时执行的最大CPU数,并返回先前的设置 fmt.Println(num)

    }

     

    Goroutine中使用recover

    应用场景,如果某个goroutine panic了,而且这个goroutine里面没有捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产生一个goroutine,就需要写下recover。

    var (

    domainSyncChan = make(chan int, 10)

    )

     

    func domainPut(num int) {

    defer func() {

    err := recover()

    if err != nil {

    fmt.Println("error to chan put.")

    }

    }()

    domainSyncChan <- num

    panic("error....")

    }

     

    func main() {

    for i := 0; i < 10; i++ {

    domainName := i

    go domainPut(domainName)

    }

    time.Sleep(time.Second * 2)

    }

     

    Goroutine 栗子

    package main

     

    import (

    "fmt"

    "sync"

    "time"

    )

     

    var (

    m = make(map[int]uint64)

    lock sync.Mutex //申明一个互斥锁)

     

    type task struct {

    n int

    }

     

    func calc(t *task) {

    defer func() {

    err := recover()

    if err != nil {

    fmt.Println("error...")

    return

    }

    }()

     

    var sum uint64

    sum = 1

    for i := 1; i < t.n; i++ {

    sum *= uint64(i)

    }

     

    lock.Lock() //写全局数据加互斥锁

    m[t.n] = sum

    lock.Unlock() //解锁}

     

    func main() {

    for i := 0; i < 10; i++ {

    t := &task{n: i}

    go calc(t) // Goroutine来执行任务 }

     

    time.Sleep(time.Second) // Goroutine异步,所以等一秒到任务完成

     

    lock.Lock() //读全局数据加锁

    for k, v := range m {

    fmt.Printf("%d! = %v\n", k, v)

    }

    fmt.Println(len(m))

    lock.Unlock() //解锁

    }

     

    Goroutine 栗子(等待所有任务退出主程序再退出)

    package main

     

    import (

    "sync"

    "fmt"

    "time"

    )

     

    func calc(w *sync.WaitGroup, i int) {

    fmt.Println("calc: ", i)

    time.Sleep(time.Second)

    w.Done()

    }

     

    func main() {

    wg := sync.WaitGroup{}

    for i:=0; i<10; i++ {

    wg.Add(1)

    go calc(&wg, i)

    }

    wg.Wait()

    fmt.Println("all goroutine finish")

    }

     

    Channel

    channel,管道、队列,先进先出,用来异步传递数据。channel加上goroutine,就形成了一种既简单又强大的请求处理模型,使高并发和线程同步之间代码的编写变得异常简单。

    线程安全,多个goroutine同时访问,不需要加锁。

    channel是有类型的,一个整数的channel只能存放整数。

    channel使用

    //chan申明var userChan chan interface{} // chan里面放interface类型

    userChan = make(chan interface{}, 10) // make初始化,大小为10

     

    var readOnlyChan <-chan int // 只读chanvar writeOnlyChan chan<- int // 只写chan

    //chan放取数据

    userChan <- "nick"

    name := <- userChan

    name, ok := <- userChan

    //关闭chan

    intChan := make(chan int, 1)

    intChan <- 9

    close(intChan)

    // range chan

    intChan := make(chan int, 10)

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    close(intChan)

     

    for v := range intChan {

    fmt.Println(v)

    }

      

    放入chan数据个数超过初始化指定大小会怎样?

    userChan := make(chan interface{})

    userChan <- "nick"// 错误!fatal error: all goroutines are asleep - deadlock!

    // 开启race会一直阻塞

    开启一个goroutine来放入初始化未指定大小的chan不会报错。

    即放即走,在等放入时有来拿数据的,就直接拿走。

    userChan := make(chan interface{})

    go func() {

    userChan <- "nick"

    }()

    name := <- userChan

    userChan := make(chan interface{})

    go func() {

    for {

    userChan <- "nick"

    }

    }()

    for {

    name := <- userChan

    fmt.Println(name)

    time.Sleep(time.Millisecond)

    }

     

    chan关闭与不关闭

    关闭chan后再放入数据会 panic: send on closed channel。

    chan不关闭取超数据的情况会报 deadlock

    func main() {

    intChan := make(chan int, 10)

     

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    for {

    //十次后 fatal error: all goroutines are asleep - deadlock!

    i := <- intChan

    fmt.Println(i)

    time.Sleep(time.Second)

    }

    }

    chan关闭的情况取超出值为类型默认值,如int为0

    func main() {

    intChan := make(chan int, 10)

     

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    close(intChan)

     

    for {

    i := <- intChan

    //十次后i值都为0,不报错 time.Sleep(time.Second)

    fmt.Println(i)

    }

    }

    判断chan是否取完

    func main() {

    intChan := make(chan int, 10)

     

    for i := 0; i < 10; i++ {

    intChan <- i

    }

    close(intChan)

     

    for {

    i, ok := <- intChan

    if !ok {

    fmt.Println("channel is close.")

    return

    }

    fmt.Println(i)

    }

    }

     

    channel 栗子

    栗子一

    func sendData(ch chan<- string) {

    ch <- "go"

    ch <- "java"

    ch <- "c"

    ch <- "c++"

    ch <- "python"

    close(ch)

    }

     

    func getData(ch <-chan string, chColse chan bool) {

    for {

    str, ok := <-ch

    if !ok {

    fmt.Println("chan is close.")

    break

    }

    fmt.Println(str)

    }

    chColse <- true

    }

     

    func main() {

    ch := make(chan string, 10)

    chColse := make(chan bool, 1)

    go sendData(ch)

    go getData(ch, chColse)

    <-chColse

    close(chColse)

    }

     

    栗子二:interface类型chan,取出后转化为对应类型。

    type user struct {

    Name string

    }

     

    func main() {

    userChan := make(chan interface{}, 1)

     

    u := user{Name: "nick"}

    userChan <- &u

    close(userChan)

     

    var u1 interface{}

    u1 = <-userChan

     

    var u2 *user

    u2, ok := u1.(*user)

    if !ok {

    fmt.Println("cant not convert.")

    return

    }

    fmt.Println(u2)

    }

     

    channel 超时处理

    利用select来处理chan超时。

    for {

    select {

    case v := <-chan1:

    fmt.Println(v)

    case v := <-chan2:

    fmt.Println(v)

    default:

    time.Sleep(time.Second)

    fmt.Println("timeout...")

    }

    }

    time.After()定时器来做处理。

    在time.After()计时器触发之前,底层计时器不会被垃圾收集器回收。

    select {

    case m := <-c:

    handle(m)

    case <-time.After(5 * time.Minute):

    fmt.Println("timed out")

    }

     定时器栗子

     

    Goroutine+Channel 栗子

    栗子一

    多个goroutine处理任务;

    等待一组channel的返回结果。

    func calc(taskChan, resChan chan int, exitChan chan bool) {

    defer func() {

    err := recover()

    if err != nil {

    fmt.Println("error...")

    return

    }

    }()

    for v := range taskChan {

    // 任务处理逻辑

    flag := true

    for i := 2; i < v; i++ {

    if v%i == 0 {

    flag = false

    break

    }

    }

    if flag {

    //结果进chan

    resChan <- v

    }

    }

    //处理完进退出chan

    exitChan <- true

    }

     

    func main() {

    //任务chan

    intChan := make(chan int, 1000)

    //结果chan

    resChan := make(chan int, 1000)

    //退出chan

    exitChan := make(chan bool, 8)

     

    go func() {

    for i := 0; i < 1000; i++ {

    intChan <- i

    }

    close(intChan)

    }()

     

    //启动8个goroutine做任务

    for i := 0; i < 8; i++ {

    go calc(intChan, resChan, exitChan)

    }

     

    go func() {

    //等所有goroutine结束

    for i := 0; i < 8; i++ {

    <-exitChan

    }

    close(resChan)

    close(exitChan)

    }()

     

    for v := range resChan {

    fmt.Println(v)

    }

    }

     

    栗子二

    等待一组channel的返回结果 sync.WaitGroup 的解决方法。

    WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。

    func merge(cs <-chan int) <-chan int {

    var wg sync.WaitGroup

    out := make(chan int)

     

    output := func(c <-chan int) {

    for n := range c {

    out <- n

    }

    wg.Done()

    }

    wg.Add(len(cs))

     

    for _, c := range cs {

    go output(c)

    }

     

    go func() {

    wg.Wait()

    close(out)

    }()

    return out

    }

     

    展开全文
  • GoRoutine

    2019-08-26 11:27:33
    在这里不一定要去关闭channel,因为底层的垃圾回收机制会根据它是否可以访问来决定是否自动回收它。(这里不是根据channel是否关闭来决定的) 3.单向通道类型 当程序则够复杂的时候,为了代码可读性更高,拆分成一个一...

    在这里不一定要去关闭channel,因为底层的垃圾回收机制会根据它是否可以访问来决定是否自动回收它。(这里不是根据channel是否关闭来决定的)

    3.单向通道类型

    当程序则够复杂的时候,为了代码可读性更高,拆分成一个一个的小函数是需要的。

    此时go提供了单向通道的类型,来实现函数之间channel的传递。

    上代码:

    package main

    import (
    “fmt”
    “time”
    )

    // 定义goroutine 1
    func Echo(out chan<- string) { // 定义输出通道类型
    time.Sleep(1*time.Second)
    out <- “咖啡色的羊驼”
    close(out)
    }

    // 定义goroutine 2
    func Receive(out chan<- string, in <-chan string) { // 定义输出通道类型和输入类型
    temp := <-in // 阻塞等待echo的通道的返回
    out <- temp
    close(out)
    }

    func main() {
    echo := make(chan string)
    receive := make(chan string)

    go Echo(echo)
    go Receive(receive, echo)
    
    getStr := <-receive   // 接收goroutine 2的返回
    
    fmt.Println(getStr)
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    程序输出:

    咖啡色的羊驼
    1
    4.缓冲管道

    goroutine的通道默认是是阻塞的,那么有什么办法可以缓解阻塞?
    答案是:加一个缓冲区。

    对于go来说创建一个缓冲通道很简单:

    ch := make(chan string, 3) // 创建了缓冲区为3的通道

    //=========
    len(ch) // 长度计算
    cap(ch) // 容量计算
    1
    2
    3
    4
    5

    6.goroutine死锁与友好退出
    6.1goroutine死锁
    来一个死锁现场一:

    package main

    func main() {
    ch := make(chan int)
    <- ch // 阻塞main goroutine, 通道被锁
    }
    1
    2
    3
    4
    5
    6
    输出:

    fatal error: all goroutines are asleep - deadlock!

    goroutine 1 [chan receive]:
    main.main()
    1
    2
    3
    4
    死锁现场2:

    package main

    func main() {
    cha, chb := make(chan int), make(chan int)

    go func() {
        cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
        chb <- 0
    }()
    
    <- chb // chb 等待数据的写
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    为什么会有死锁的产生?

    非缓冲通道上如果发生了流入无流出,或者流出无流入,就会引起死锁。
    或者这么说:goroutine的非缓冲通道里头一定要一进一出,成对出现才行。
    上面例子属于:一:流出无流入;二:流入无流出

    当然,有一个例外:

    func main() {
    ch := make(chan int)
    go func() {
    ch <- 1
    }()
    }
    1
    2
    3
    4
    5
    6
    执行以上代码将会发现,竟然没有报错。
    what?
    不是说好的一进一出就死锁吗?
    仔细研究会发现,其实根本没等goroutine执行完,main函数自己先跑完了,所以就没有数据流入主的goroutine,就不会被阻塞和报错

    6.2goroutine的死锁处理
    有两种办法可以解决:

    1.把没取走的取走便是

    package main

    func main() {
    cha, chb := make(chan int), make(chan int)

    go func() {
        cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
        chb <- 0
    }()
    
    <- cha // 取走便是
    <- chb // chb 等待数据的写
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    2.创建缓冲通道

    package main

    func main() {
    cha, chb := make(chan int, 3), make(chan int)

    go func() {
        cha <- 1 // cha通道的数据没有被其他goroutine读取走,堵塞当前goroutine
        chb <- 0
    }()
    
    <- chb // chb 等待数据的写
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    这样的话,cha可以缓存一个数据,cha就不会挂起当前的goroutine了。除非再放两个进去,塞满缓冲通道就会了。

    7.select的简介
    定义:在golang里头select的功能与epoll(nginx)/poll/select的功能类似,都是坚挺IO操作,当IO操作发生的时候,触发相应的动作。

    select有几个重要的点要强调:

    1.如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
    上代码:

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)

    ch<-1
    select {
    case <-ch:
        fmt.Println("咖啡色的羊驼")
    case <-ch:
        fmt.Println("黄色的羊驼")
    }
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    输出:

    (随机)二者其一
    1
    2.case后面必须是channel操作,否则报错。

    上代码:

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)
    ch<-1
    select {
    case <-ch:
    fmt.Println(“咖啡色的羊驼”)
    case 2:
    fmt.Println(“黄色的羊驼”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    输出报错:

    2 evaluated but not used
    select case must be receive, send or assign recv
    1
    2
    3.select中的default子句总是可运行的。所以没有default的select才会阻塞等待事件
    上代码:

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)
    // ch<-1 <= 注意这里备注了。
    select {
    case <-ch:
    fmt.Println(“咖啡色的羊驼”)
    default:
    fmt.Println(“黄色的羊驼”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    输出:

    黄色的羊驼
    1
    4.没有运行的case,那么江湖阻塞事件发生报错(死锁)

    package main

    import “fmt”

    func main() {
    ch := make (chan int, 1)
    // ch<-1 <= 注意这里备注了。
    select {
    case <-ch:
    fmt.Println(“咖啡色的羊驼”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    输出报错:

    fatal error: all goroutines are asleep - deadlock!
    1
    8.select的应用场景
    1.timeout 机制(超时判断)

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    timeout := make (chan bool, 1)
    go func() {
    time.Sleep(1*time.Second) // 休眠1s,如果超过1s还没I操作则认为超时,通知select已经超时啦~
    timeout <- true
    }()
    ch := make (chan int)
    select {
    case <- ch:
    case <- timeout:
    fmt.Println(“超时啦!”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    以上是入门版,通常代码中是这么写的:

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    ch := make (chan int)
    select {
    case <-ch:
    case <-time.After(time.Second * 1): // 利用time来实现,After代表多少时间后执行输出东西
    fmt.Println(“超时啦!”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    2.判断channel是否阻塞(或者说channel是否已经满了)

    package main

    import (
    “fmt”
    )

    func main() {
    ch := make (chan int, 1) // 注意这里给的容量是1
    ch <- 1
    select {
    case ch <- 2:
    default:
    fmt.Println(“通道channel已经满啦,塞不下东西了!”)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    3.退出机制

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
    close(ch)
    }()

    go func() {
        DONE: 
        for {
            time.Sleep(1*time.Second)
            fmt.Println(time.Now().Unix())
            i++
    
            select {
            case m := <-ch:
                println(m)
                break DONE // 跳出 select 和 for 循环
            default:
            }
        }
    }()
    
    time.Sleep(time.Second * 4)
    ch<-"stop"
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    输出:

    1532390471
    1532390472
    1532390473
    stop
    1532390474
    1
    2
    3
    4
    5
    这边要强调一点:退出循环一定要用break + 具体的标记,或者goto也可以。否则其实不是真的退出。

    package main

    import (
    “fmt”
    “time”
    )

    func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
    close(ch)
    }()

    go func() {
    
        for {
            time.Sleep(1*time.Second)
            fmt.Println(time.Now().Unix())
            i++
    
            select {
            case m := <-ch:
                println(m)
                goto DONE // 跳出 select 和 for 循环
            default:
            }
        }
        DONE:
    }()
    
    time.Sleep(time.Second * 4)
    ch<-"stop"
    

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    输出:

    ————————————————
    版权声明:本文为CSDN博主「咖啡色的羊驼」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u011957758/article/details/81159481

    展开全文
  • fmt.Println("goroutine is doing stuff..") res <- idx idx++ } } }() for r := range res { if r == 6 { quit <- true } fmt.Println("I received: ", r) } </code></pre> <p>Output: <pre>...
  • Goroutine超时

    2018-07-07 12:07:47
    <pre><code>type Response struct { data interface{} ...<p>I am not sure if there is goroutine leak here. Is there a way to cancel those https requests if there is timeout?</p> </div>
  • But there is a question I'm asking that Can we implement a goroutine in the other goroutine because in the function I already set a go routine but in that go routine there are to queries more and I ...
  • <p>That got me thinking if by calling a goroutine inside of a goroutine was best practice or if I as causing the function to never exit, and then a defer would only be called once the goroutine was ...
  • ... ... ... ... rw.WriteHeader(200) ...<p>I have my listeners ... This handler receives upto 10K requests per second, I am guessing it's not a good idea to open a <code>goroutine</code> for each request. </div>
  • <p>3) i can use the Option (2) by adding a counter to the struct and each time a goroutine founds that the url is already requested, it just add +1 to the counter and await the response from the ...
  • Goroutine无法运行

    2017-11-13 21:19:31
    <p>I'm trying to use a goroutine to write item to database. The goroutine however doesn't seem to do anything for some reason. <p>I've got following functions: <pre><code>func addEvent(w ...
  • goroutine不再安排

    2018-02-12 16:12:11
    I have a goroutine to print variable <code>i</code> and after it I write a deadloop. But when var <code>i</code> up to <code>491519(or some other value), there is no output on the terminal. It looks ...
  • 测试Golang Goroutine

    2015-03-05 01:47:46
    <p>I would like to know if I can bring goroutine into unit testing such that it can precisely count the concurrent # of goroutines is running and can tell me if they are correctly spawned goroutine in...
  • Goroutine执行顺序

    2018-03-05 03:49:02
    <p>I'm a beginner of golang and I'm learning the goroutine and channel. Intuitively, I consider a gorountine in golang is essentially a thread running independently. Therefore, if there are more than ...
  • <p>once data has been fetched from one of the goroutines, the other goroutine must be canceled completely not to waste CPU. <p>simplified: <pre><code>func A(){ go funcB(){ }() go funcC(){ }()...

空空如也

1 2 3 4 5 ... 20
收藏数 8,345
精华内容 3,338
关键字:

goroutine