Go语言入门(七)goroutine和channel
时间:2022-07-25
本文章向大家介绍Go语言入门(七)goroutine和channel,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
goroutine和channel
goroutine
多线程
func hello() {
//fmt.Printf("Hello Goroutine!!n")
for i:=0;i<100;i++ {
fmt.Printf("hello:%dn",i)
time.Sleep(time.Millisecond)
}
}
func main() {
go hello() //启动了一个独立的线程,使其与下面的代码交替执行,使之成为一个多线程
//fmt.Printf("main functionn")
for i:=0;i<100;i++ {
fmt.Printf("main:%dn",i)
time.Sleep(time.Millisecond)
}
time.Sleep(time.Second) //修复代码,使得主线程退出的时候子线程能执行
}
多个goroutine
func nunmers() {
for i :=0;i<=5;i++ {
time.Sleep(time.Millisecond*250)
fmt.Printf("%dn",i)
}
}
func chars() {
for i:='a';i<='e';i++ {
time.Sleep(time.Millisecond*400)
fmt.Printf("%cn",i)
}
}
func main() {
go nunmers()
go chars()
time.Sleep(time.Second*3)
}
进程和线程
- 进程:
- 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
- 线程:
- 线程是进程的一个执行实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位
- 一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行
并发与并行
- 多线程程序在一个核的CPU上运行,这是并发
- 多线程程序在多个核的CPU上运行,这是并行
协程和线程
- 协程: 独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的
- 线程: 一个县城上可以跑多个协程,协程是轻量级的线程
goroutine的调度模型
- M(线程)P(上下文)G(goroutine)
设置golang运行的CPU核心数
func main() {
cpu := runtime.NumCPU()
//限制核心数(新版本不用考虑),比如监控程序,可以控制其运行的资源消耗
//runtime.GOMAXPROCS(1)
for i:=0;i <=8;i++ {
go func() {
}()
}
fmt.Printf("%dn",cpu)
time.Sleep(time.Second*12)
}
channel
- 不同的goroutine之间如何进行通讯:
- 全局变量和锁同步: 不推荐
- Channel: 先进先出的队列
- channel的概念
- 类似于unix中的管道
- 先进先出
- 线程安全,多个goroutine同事访问不需要加锁
- channel是有类型的,一个证书的channel只能整数
channel的声明
- 引用类型,需要使用make来初始化
var 变量名 chan 类型
var test chan int
var test chan string
var test chan map[string]string
var test chan stu
var test chan *stu
- channel的初始化与读写
func main() {
//var intChan chan int = make(chan int,1) 有缓冲区的channel
var intChan chan int = make(chan int) // 无缓冲区channel
fmt.Printf("intChanin:%pn",intChan)
go func() {
//写入数据
intChan <- 100
fmt.Printf("insert item endn")
}()
go func() {
//读取数据
fmt.Printf("startn")
time.Sleep(time.Second*3)
var a int
a = <- intChan
fmt.Printf("intChan:%dn",a)
}()
time.Sleep(time.Second*5)
}
goroutine和channel相结合
- 生产者消费者模型
func senData(ch chan string) {
ch <- "Washinton"
ch <- "Tripoli"
ch <- "LongDong"
ch <- "Beijing"
ch <- "Tokyo"
}
func getData(ch chan string) {
var input string
for {
input = <- ch
fmt.Printf("input=%sn",input)
}
}
func main() {
ch := make(chan string,10)
go senData(ch)
go getData(ch)
time.Sleep(time.Second*1)
}
阻塞channel的情况
- 无写入,空读取会阻塞(空channel)
- 写入的数量超过缓冲区也会阻塞 (channel满了)
func sendChans(ch chan string) {
var i int
for {
var str string
str = fmt.Sprintf("stu %dn",i)
fmt.Printf("write:%sn",str)
//无人消费数据,则阻塞,如果无缓冲区的话,写入也是阻塞的
ch <- str
i++
}
}
func main() {
ch := make(chan string,10) //带缓冲区
//ch := make(chan string) //无缓冲区
go sendChans(ch)
time.Sleep(time.Second*200)
}
channel之间的同步
- 新增退出检测的方式保证同步
func sendChannels(ch chan string,exitCh chan bool) {
ch <- "AA"
ch <- "BB"
ch <- "CC"
ch <- "DD"
ch <- "EE"
close(ch)
exitCh <- true
}
func getChannels(ch chan string,exitCh chan bool) {
for {
// 检查channel关闭
input,ok := <- ch
if !ok {
break
}
fmt.Printf("getchannels中的input:%sn",input)
}
exitCh <- true
}
func getChannels2(ch chan string,exitCh chan bool) {
for {
// 检查channel关闭
input,ok := <- ch
if !ok {
break
}
fmt.Printf("getchannels中的input:%sn",input)
}
exitCh <- true
}
func main() {
ch := make(chan string)
exitChan := make(chan bool,3)
go sendChannels(ch,exitChan)
go getChannels(ch,exitChan)
go getChannels2(ch,exitChan)
//三次检查状态,然后退出,不需要time.sleep,等待其他goroutine退出
<- exitChan //取出元素,然后扔掉
<- exitChan
<- exitChan
}
- 使用waitGroup的方式确保goroutine的同步
func SendChannels1(ch chan string,waitGroup *sync.WaitGroup) {
ch <- "AA"
ch <- "BB"
ch <- "CC"
ch <- "DD"
ch <- "EE"
close(ch)
fmt.Printf("send data exitedn")
waitGroup.Done() //结束goroutine的标志,然后对标志位的数字-1
}
func GetChannels1(ch chan string,waitGroup *sync.WaitGroup) {
for {
input,ok := <- ch
if !ok{
break
}
fmt.Printf("getchannels中的input:%sn",input)
}
fmt.Printf("get data exitedn")
waitGroup.Done()
}
func GetChannels2(ch chan string,waitGroup *sync.WaitGroup) {
for {
input,ok := <- ch
if !ok{
break
}
fmt.Printf("getchannels中的input:%sn",input)
}
fmt.Printf("get data2 exitedn")
waitGroup.Done()
}
func main() {
var wg sync.WaitGroup
ch := make(chan string)
wg.Add(3) //每执行完一次goroutine就会-1
go SendChannels1(ch,&wg)
go GetChannels1(ch,&wg)
go GetChannels2(ch,&wg)
wg.Wait() //等到运行完成之后返回
fmt.Printf("main goroutine exitedn")
}
- 关闭channel的时候,生产者channel的数据是保存的,不丢失
func main() {
intChan := make(chan int,10)
for i:=0;i<10;i++ {
intChan<- i
}
//关闭channel
close(intChan)
time.Sleep(time.Second)
for j:=0;j<10;j++ {
a := <- intChan
fmt.Printf("a=%dn",a)
}
}
- channel的遍历
func GetChannels2(ch chan string,waitGroup *sync.WaitGroup) {
//for range会自动判断channel是否关闭
for v :=range ch {
fmt.Printf("get data2 %sn",v)
}
fmt.Printf("get data2 exitedn")
waitGroup.Done()
}
- chan的关闭
- 使用内置函数close进行关闭,chan关闭之后,for range 遍历chan中已经存在的元素后结束
- 使用内置函数close进行关闭,chan关闭之后,没有使用for range的写法,需要使用
v,ok :=<-chan
判断chan是否关闭
chan的只读和只写
需要注意<-
是在chan
关键字的位置,<-
在chan
左侧表示只读,在右侧表示只写
func chanPerms() {
var readOnly <- chan int = make(chan int,100)
// readOnly <- 100 只读不可写
var writeOnly chan <- int = make(chan int,10)
// <- writeOnly 只写不可读
}
- 使用场景: 三方调用只读只写权限控制,防止误操作
对chan进行select操作
func main() {
var intChan chan int = make(chan int,10)
var strChan chan string = make(chan string,10)
var wg sync.WaitGroup
wg.Add(2)
// 插入数据
go func() {
var count int
for count < 1000 {
count++
select {
case intChan <- 10:
fmt.Printf("write to int chan succn")
case strChan <- "hello":
fmt.Printf("write to str chan succn")
default:
fmt.Printf("all chan is fulln")
time.Sleep(time.Second)
}
}
wg.Done()
}()
wg.Wait()
//读取数据
go func() {
var count int
for count < 10000 {
count ++
select {
case a := <- intChan:
fmt.Printf("read from int chain a:%dn",a)
case <- strChan:
fmt.Printf("read from str chann")
default:
fmt.Printf("all chan is emptyn")
time.Sleep(time.Second)
}
}
wg.Done()
}()
wg.Wait()
}
- hbuilder 开发5+ APP采坑记录
- Spring Cloud如何提供API给客户端
- 5分钟学会Spring Boot自定义属性和自动配置
- 创建一个Spring Security OAuth认证服务
- Zipkin和微服务链路跟踪
- Java8真不用再搞循环了?
- 针对事件驱动架构的Spring Cloud Stream
- Spring的三种Circuit Breaker
- Spring5以来注册Bean的各种姿势,特别最后的纯编码注册值得尝试
- ONOS一键安装脚本
- Spring 5 新增全新的reactive web框架:webflux
- 认证鉴权与API权限控制在微服务架构中的设计与实现(一)
- 在服务器上利用docker快速部署博客—jpress
- NSQ深入与实践
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- SAP UI5框架Component.js里extend函数的实现原理
- 冒泡排序的终极改进优化
- alpine安装sshd/ssh server
- 使用jstack检测Java应用的死锁(deadlock)状态
- 使用xdebug对php做性能分析调优
- 使用SAP Analytics Path Framework通过图表和表格方式展示CDS view数据
- ELK学习笔记之Docker Container exited with code 137
- 用这10个小技巧加速Python编程
- Dijkstra算法及其C++实现
- WPF 启动屏幕键盘
- dotnet 里的那些锁 AutoResetEvent 用法
- MySQL是如何实现事务的ACID
- 白话K8S核心组件概念
- k8s 架构、基本概念及命令
- Java API 连接 Hbase示例