一、goroutine 基础

定义

  • 使用者分配足够多的任务,系统能自动帮助使用者把任务分配到 CPU 上,让这些任务尽量并发运作,此机制在Go中称作 goroutine
  • goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。
  • Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine。

语法

// 普通函数创建 goroutine 语法
go 函数名称(参数列表)     // 匿名函数创建 goroutine 语法
go func(形参列表) {函数体...}(实参列表) // 使用 go 创建 goroutine 后,函数的返回值会被忽略

普通函数创建 goroutine

package main

import (
"fmt"
"math/rand"
"time"
) func listElem(n int) {
for i:=; i<n; i++ {
fmt.Println(i)
}
} func main() {
go listElem() // 若 main 函数中仅有这一句代码,控制台将没有任何输出,因为 main 创建协程后立即推出了,还未来得及执行协程 //添加以下代码
var input string
fmt.Scanln(&input) // 需要用户输入任意字符 main 才会结束
}

匿名函数创建 goroutine

func main() {
go func() {
var times int
for {
times++
fmt.Println("tick", times)
time.Sleep(time.Second)
}
} ()
}

二、channel

channel 基础

  • 一个 channel 是一个通信机制,可让一个 goroutine 通过 channel 给另一个 goroutine 发送消息。
  • 每个 channel 都有一个特殊的类型,也就是 goroutine 之间进行通信时能够传递的数据类型;eg:若要通过 channel 发送 int 类型的数据,那么  chan int  即可。
  • 通道之间能够共享的数据类型有:内置类型、命名类型、结构类型、引用类型的值、指针。
  • 通道遵循 “先进先出” 原则,保证收发数据的顺序,并且在同一时间只能有一个 goroutine 访问通道进行发送消息或者读取消息。

语法

// 声明之后必须配合 make 才能使用
var 通道变量 chan 通道类型 // 声明通道;通道类型,通道内的数据类型 通道实例 = make(chan 数据类型) // 创建通道;数据类型,通道内传输的元素类型 // eg:
var ch1 chan string
ch1 = make(chan string) ch2 := make(chan int) // 创建整型类型的通道 ch3 := make(chan interface{}) // 创建空接口类型的通道,可存放任意格式 ch4 := make(chan *Equip) // 创建 Equip 指针类型的通道,可存放 *Equip // 关闭通道
close(ch1)

使用通道发送数据

// 通道发送数据的语法:  "<-"  操作符
通道变量 <- 值 // 值可以是:变量、常量、表达式、函数返回值、但值的类型必须与通道声明的元素类型一致 // eg
ch := make(chan interface{}) // 创建空接口通道
ch <- // 向通道内放入 0
ch <- "Hello World" // 向通道内放入 字符串 // 附:向通道内发送数据时,若没有对应的接收方读取数据,那么发送数据的操作将会一直堵塞。

使用通道接收数据

  • 通道数据的收发操作在两个不同的 goroutine 中进行
  • 接收方会一直阻塞到发送方发送数据为止,同样的,发送方也会一直阻塞到接收方读取数据为止。
  • 通道每次只能接收一个数据元素
// 接收数据同样使用  "<-" 操作符

data := <-ch        // 阻塞接收数据

data, ok := <-ch        // 非阻塞接收数据,方未接收到数据时,data为通道类型的零值,ok为false;此方式会造成CPU高占用,实现接收超时检测会配合 select 和 计时器channel进行,一般较少使用

<-ch                        // 接收任意数据,并忽略接收到的数据

// 循环接收
for data := range ch {
// do something
}

单向通道

var 通道变量 chan<- 通道类型        // 只写 channel
var 通道变量 <-chan 通道类型 // 只读 channel
var 通道变量 chan 通道类型 // 可读可写 channel // eg
var wc chan<- string
var rc <-chan string
var ch chan string // 关闭 channel
close(ch)

无缓冲通道

在接收数据前,没有能力,保存任何值,的通道。它要求 发送通道和接收通道 必须同时准备好,才能完成发送和接收操作;如果有一方通道a没有准备好,那么另一方通道b会阻塞等待着通道a。

package main

import (
"fmt"
"math/rand"
"sync"
"time"
) var wg sync.WaitGroup func init() {
rand.Seed(time.Now().UnixNano())
} func player(name string, court chan int) {
/**
击球比赛 大致流程:
创建一个数据类型是 int的channel
创建两个 goroutine(选手),互相进行击球,由于 player 中第一行代码是在阻塞等待接球(从channel中获取数据),所以在main当中需要发出第一个球
player:
启动两个 goroutine 后,总有一方会抢先从 channel 中取出数据,那么另一方处于阻塞等待的状态。
然后判断 通道channel 是否关闭(若已关闭,ok=false )
若已经关闭,代表是对方关闭的通道对方输球,则当前选手获胜,退出游戏。
若上一个判断不成立,则接下来选择一个随机数 并 判断是否可以被 22 整除,以此来决定是否丢球(向通道写入数据,让对方获取【接球】)
若无法被整除,当前选手输球,直接关闭通道并退出,此时执行权在另一方选手,从通道取出数据时,发现通道已关闭,那么打印胜利信息。
若可以整除,显现当前的击球数,并将其写入到通道,让对方接球。 以下代表表示主 goroutine 需要等待另两个 goroutine 结束之后才能结束 main
wg.Add(2)
defer wg.Done()
wg.Wait()
**/ defer wg.Done() // 函数退出时调用 Done 函数,以此通知 main 函数,当前函数的工作已完成 for {
ball, ok := <-court // 等待接球
if !ok { // 若通道关闭,则胜利
fmt.Printf("Player %s Won\n", name)
return
} // 选择随机数,使用此数判断是否丢球
n := rand.Intn()
if n % == {
fmt.Printf("Player %s Missed\n", name) // 关闭通道,表输球
close(court)
return
} // 显示击球数,且击球数 +1
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++ // 击球给对方
court <- ball
} } func main() { // 击球比赛
court := make(chan int)
wg.Add() // 表:需等待两个 goroutine
go player("Juan", court) // 启动两个选手
go player("Xiao", court)
court <- // 发球
wg.Wait() // 等待结束
}

击球比赛

跑步接力赛

带缓冲通道  &  关闭通道

在数据被接受前,可以存储一个或多个值的通道,此类型通道不强制要求 两方 goroutine 必须同时完成发送和接收 。阻塞发生的情况:当通道中没有需要接收的值时接受动作会被阻塞;当通道中没有可用的缓冲区来容纳要发送的值时发送动作会被阻塞。

func main() {
// 带缓冲通道: 创建语法, 通道实例 := make(chan 通道类型,缓存大小)
bufferCh := make(chan int, )
fmt.Printf("put data before, channel length: %d\n", len(bufferCh)) // 查看放入数据前通道大小
bufferCh <-
bufferCh <-
bufferCh <-
close(bufferCh) // 通道关闭仍然可以从通道中读取数据,但不可发送数据,会引发panic错误,当已关闭的通道中 也没有数据后,不会阻塞获取数据操作,将会返回零值
fmt.Printf("put data after, channel length: %d\n", len(bufferCh)) // 查看放入数据后通道大小 data1 := <-bufferCh
fmt.Printf("get first data: %d\n", data1)
data2 := <-bufferCh
fmt.Printf("get secondly data: %d\n", data2)
data3 := <-bufferCh
fmt.Printf("get thirth data: %d\n", data3)
data4 := <-bufferCh
fmt.Printf("get next data, bur no data anymore, it is %d\n", data4)
}

区别

  • 无缓冲通道保证双方的 goroutine 会在同一时间进行数据的交换(数据交换时,会把两个 goroutine 一起锁住,只有一个goroutine将数据交棒给另一个goroutine之后才会解除堵塞);
  • 带缓冲通道是在无缓冲通道的基础上,为通道增加了一个有限大小的存储空间,带缓冲通道在发送时 不需要等待接收方接收完 才完成发送,也不会发生堵塞,直到存储空间满了才会发生堵塞;同样的,如果通道中已有数据,接收时并不会发生堵塞,直到通道中没有数据了才会发生堵塞。
  • 举个栗子:上门取件服务(无缓冲通道)   和   快递柜取件(带缓冲通道)
    • 上门取件:发送方(我)  &  接收方(快递员),当我把快递准备好之后,打电话给快递员跟他说明地址(让快递员准备好)之后,才能把快递寄出去;如果快递员还没有来到我的地址,我就得阻塞等待;同样,如果我还没准备好快递,快递员就等待我把快递准备好之后,他才能把快递带走。
    • 快递柜:发送方(快递员)  & 接收方(我),快递到了之后快递员会把快递塞进快递柜里,然后再发一条短信给我让我取快递,但是我不需要立马去取快递,快递员也不需要等我取完快递之后他才能送下一个快递;只有当快递柜满了之后,快递员就不能塞快递了,就会阻塞等待有空位之后才能继续放入快递;而我也只有当快递柜里啥都没有的时候才会等着快递来。

channel 超时机制

// 首先,我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool, )
go func() {
time.Sleep(1e9) // 等待1秒钟
timeout <- true
}() // 然后我们把timeout这个channel利用起来
select {
case <-ch:
// 从ch中读取到数据
case <-timeout:
// 一直没有从ch中读取到数据,但从timeout中读取到了数据
}

 channel 多路复用

// 一次性接收多个通道的数据,当操作发生时,会执行对应case语句中的响应
select{
case 操作1:
响应操作1
case 操作2:
响应操作2

default:
没有操作情况
}
select 多路复用中可以接收的样式
操   作 语句示例
接收任意数据 case <- ch;
接收变量 case d :=  <- ch;
发送数据 case ch <- 100;

go 学习 (五):goroutine 协程的更多相关文章

  1. go语言之进阶篇创建goroutine协程

    1.goroutine是什么 goroutine是Go并行设计的核心.goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现 ...

  2. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  3. [转]向facebook学习,通过协程实现mysql查询的异步化

    FROM : 通过协程实现mysql查询的异步化 前言 最近学习了赵海平的演讲,了解到facebook的mysql查询可以进行异步化,从而提高性能.由于facebook实现的比较早,他们不得不对php ...

  4. Golang教程:goroutine协程

    在上一篇中,我们讨论了并发,以及并发和并行的区别.在这篇教程中我们将讨论在Go中如何通过Go协程实现并发. 什么是协程 Go协程(Goroutine)是与其他函数或方法同时运行的函数或方法.可以认为G ...

  5. Go goroutine (协程)

    在Go语言中goroutine是一个协程,但是跟Python里面的协程有很大的不同: 在任何函数前只需要加上go关键字就可以定义为协程; 不需要在定义时区分是否是异步函数  VS  async def ...

  6. uLua学习之使用协程(终)

    前言 今天是本系列的第六篇文章,也是最后一篇,我们来看看uLua中如何来实现协程吧.首先,让我们明确协程的概念.在百度百科上的是这样说的,协程更适合于用来实现彼此熟悉的程序组件,如合作式多任务,迭代器 ...

  7. Golang 入门 : goroutine(协程)

    在操作系统中,执行体是个抽象的概念.与之对应的实体有进程.线程以及协程(coroutine).协程也叫轻量级的线程,与传统的进程和线程相比,协程的最大特点是 "轻"!可以轻松创建上 ...

  8. Golang的goroutine协程和channel通道

    一:简介 因为并发程序要考虑很多的细节,以保证对共享变量的正确访问,使得并发编程在很多情况下变得很复杂.但是Go语言在开发并发时,是比较简洁的.它通过channel来传递数据.数据竞争这个问题在gol ...

  9. python学习道路(day11note)(协程,同步与异步的性能区别,url爬网页,select,RabbitMq)

    1.协程 #协程 又称微线程 是一种用户的轻量级线程 程序级别代码控制 就不用加机器 #不同函数 = 不同任务 A函数切到B函数没有进行cpu级别的切换,而是程序级别的切换就是协程 yelied #单 ...

随机推荐

  1. Django-12-auth认证组件

    1. 介绍 我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统.此时我们需要实现包括用户注册.用户登录.用户认证.注销.修改密码等功能. Django作为一个完美主义者的终极框架,当然也会 ...

  2. Go语言【数据结构】字符串

    字符串 简介 一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据.和数组不同的是,字符串的元素不可修改,是一个只读的字节数组.每个字符串的长度虽然也是固定的,但是字符串的长度并 ...

  3. 工信部要求应用商店上新 App 检查 IPv6,这里有一份 IPv6 快速部署指南

    7 月 25 日,工业和信息化部信息通信发展司组织召开部署推进 IPv6 网络就绪专项行动电视电话会议.会议指出,加快推进 IPv6 规模部署,构建高速率.广普及.全覆盖.智能化的下一代互联网,是互联 ...

  4. SpringBoot中Logback日志的配置

    说明 在SpringBoot中自带的日志工具是Logback,我们可以在Springboot的配置文件中直接对Logback进行一些简单的配置,如: logging.level.com.nowcode ...

  5. 解决Ajax前台中文传到后台出现中文乱码

    遇到的问题是: 前台利用Ajax, get方式向后台发送中文数据出现乱码. 解决办法是前台两次编码, 后台一次解码即可. 前台jsp文件 1 var text = "张三"; 3 ...

  6. Java之路---Day02

    2019-10-17-20:21:22 顺序结构: 概述:顺序执行,根据编写的顺序,从上到下执行语句 判断语句1-if: if语句第一种格式: if(关系表达式){ 语句体; } 执行流程: 1.首先 ...

  7. Java 之 MyBatis(一)入门

    一.Mybatis 框架概述 (1)mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动.创建连接.创 ...

  8. 19.centos7基础学习与积累-005-命令总结01

    从头开始积累centos7系统运用 大牛博客:https://blog.51cto.com/yangrong/p5 1.查看命令帮助的方法: --help 适用于一般命令,非内置命令 man  适用于 ...

  9. MySQL/MariaDB数据库的主从复制

     MySQL/MariaDB数据库的主从复制  作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL复制概述 1>.传统扩展方式 垂直扩展(也叫向上扩展,Sacle ...

  10. MySQL/MariaDB数据库的查询缓存优化

    MySQL/MariaDB数据库的查询缓存优化 作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL架构 Connectors(MySQL对外提供的交互接口,API): ...