当前位置: 首页 > news >正文

【gogogo专栏】golang并发编程

golang并发编程

  • 并发编程的工具
    • goroutine介绍
      • 协程管理器sync.WaitGroup
    • channel介绍
      • readChannel和writeChannel
      • close的用法
      • select的用法
  • 通讯示例
  • 总结

并发编程的工具

在golang中,并发编程是比较简单的,不像java中那么麻烦,golang天然的支持协程(线程)的管理,通常用goroutine和channel来管理go的并发编程。

goroutine介绍

goroutine在go语言中叫做协程,相当于java中线程的概念,使用起来也非常简单,在方法前面加个go就行了。

go Run() 例如这样就是开启一个新的协程去跑Run方法

协程管理器sync.WaitGroup

在使用协程时,如果主线程结束的时候,我们并不能知道协程里面发生了什么,也无法控制它,因此引入了sync.WaitGroup,来控制协程。
我们来看以下这个例子,在协程启动的时候开启了一个协程WaitGroup,当Add里面有数字的时候,当数字减到0才能结束。

func main() {var wg sync.WaitGroupwg.Add(1)go Run(&wg)wg.Wait()
}func Run(wg *sync.WaitGroup) {fmt.Println("我跑起来了")wg.Done()
}

我们首先测试一下方法

wg.Add(1) ===> 输出结果:我跑起来了
wg.Add(0) ===> 输出结果:空
wg.Add(2) ===> 输出结果:我跑起来了 fatal error: all goroutines are asleep - deadlock!

我们可以看到这里有个报错,在 main 函数中使用了 wg.Wait() 来等待两个协程完成任务,但是在 Run 函数中,你只是调用了 wg.Done() 来通知 WaitGroup 任务完成,但是没有关闭等待。
因此推荐以下这种写法,比较安全,保证最终关闭等待。

func main() {var wg sync.WaitGroupwg.Add(2)go func() {defer wg.Done()Run()}()go func() {defer wg.Done()Run()}()wg.Wait()
}func Run() {fmt.Println("我跑起来了")
}

channel介绍

channel是用来负责协程之间的通信的,可以理解为一个管道,我们首先举一个最简单的例子,箭头表示读取,这样就表示往管道里面读入和获取一个数了。

func main() {c1 := make(chan int, 1)c1 <- 1fmt.Println(<-c1)
}

c1 := make(chan int, 1) 这里的1代表缓冲区的容量,当有缓冲区的时候,存入数据会预先存进缓冲区,当需要的时候再取出。当c1 := make(chan int) 这样写的时候,则代表没有容量,写入的时候会阻塞,除非有其他goroutine进行消费,否则无法执行下一步操作。

因此调用下面这个方法则会造成死锁。

func main() {c1 := make(chan int)c1 <- 1fmt.Println(<-c1)
}

但是如果是异步执行的话则可以正常执行获取,因为这个写入的操作跑到另一个cpu线程(时间分片)上去了,不会对主线程造成阻塞,当主线程去获取这个值的时候,则可以顺利拿到。

func main() {c1 := make(chan int)go func() {c1 <- 1}()fmt.Println(<-c1)
}

下面我们再来看看缓冲区这个概念,以下面这段代码为例,无缓冲区时,向channel中存入则阻塞,有缓冲区时更像一种生产者消费者模型,充满缓冲区才阻塞。

func main() {//c1 := make(chan int)//c1 := make(chan int, 5)c1 := make(chan int, 10)go func() {for i := 0; i < 10; i++ {c1 <- i}}()for i := 0; i < 10; i++ {time.Sleep(time.Second)fmt.Println(<-c1)}
}

我们在这里打上断点可以很明显的看到变化。
在这里插入图片描述

readChannel和writeChannel

在channel中可以定义可读和可写channel

func main() {c1 := make(chan int, 5)var read <-chan int = c1var write chan<- int = c1write <- 1fmt.Println(<-read)
}

close的用法

close可以关闭一个channel,使其无法往里面写,但是仍然可以往里面读。

func main() {c1 := make(chan int, 5)c1 <- 1c1 <- 2c1 <- 3c1 <- 4c1 <- 5close(c1)fmt.Println(<-c1)fmt.Println(<-c1)fmt.Println(<-c1)fmt.Println(<-c1)fmt.Println(<-c1)
}

此时不用close也是可以执行的,但是当我们使用循环取的时候,必须要用close。

func main() {c1 := make(chan int, 5)c1 <- 1c1 <- 2c1 <- 3c1 <- 4c1 <- 5close(c1)for v := range c1 {fmt.Println(v)}
}

select的用法

我们以一下代码为例

func main() {c1 := make(chan int, 1)c2 := make(chan int, 1)c3 := make(chan int, 1)c1 <- 1c2 <- 1c3 <- 1select {case <-c1:fmt.Println("c1")case <-c2:fmt.Println("c2")case <-c3:fmt.Println("c3")default:fmt.Println("都没执行")}
}

使用select可以将能执行的都执行,比如上面的代码执行顺序就是三个case随机一个或全部执行。

通讯示例

func main() {var wg sync.WaitGroupwg.Add(2) // 增加等待组计数器,表示有两个协程需要等待ch := make(chan int) // 创建整数类型的通道go producer(ch, &wg) // 启动生产者协程go consumer(ch, &wg) // 启动消费者协程wg.Wait() // 等待所有协程执行完毕
}func producer(ch chan int, wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 10; i++ {fmt.Println("Produced:", i)ch <- i}close(ch) // 关闭通道,表示生产者完成生产
}func consumer(ch chan int, wg *sync.WaitGroup) {defer wg.Done()for data := range ch {fmt.Println("Consumed:", data)}
}

我们可以通过这种方式实现一个简单的生产者消费者模型来实现线程交互。

总结

在go语言中,我们完全可以通过goroutine和channel实现线程之间的通讯,来实现协程之间协调。

http://www.lryc.cn/news/225154.html

相关文章:

  • 深入理解JVM虚拟机第二十二篇:详解JVM当中与操作数栈相关的字节码指令
  • Vue报错解决Error in v-on handler: “Error: 无效的节点选择器:#div1“
  • R | R包安装报错-github连接速度慢或无法访问 | metaboanalystR | Retip | rJava安装
  • 博阳精讯、凡得科技访问上海斯歌:共探BPM流程服务新高地
  • 响应式艺术作品展示前端html网站模板源码
  • 大语言模型(LLM)综述(六):大型语言模型的基准和评估
  • 【Python自学笔记】Flask调教方法Internel Server Error
  • 【AICFD案例教程】汽车外气动-AI加速
  • P1547 [USACO05MAR] Out of Hay S 题解
  • 2023.11.10联测总结
  • C++:list?自己模拟实现!
  • layui table合并相同的列
  • 【Spring】SpringBoot配置文件
  • python批量下载txt文件中链接的数据
  • stm32 Bootloader设计(YModem协议)
  • 竞赛 题目: 基于深度学习的疲劳驾驶检测 深度学习
  • ubuntu 16.04.5 安装 vivado 2019.1 完整编译AD9361的环境
  • Zotero详细功能补充!熟练使用!【进阶版,持续更新】
  • 【Windows】Windows系统常用命令大全
  • 大语言模型研究进展综述
  • linux gdb 调试 常见调试命令介绍+总结
  • 基于JavaWeb+SSM+Vue微信小程序校园兼职任务平台系统的设计和实现
  • 我的MQTT操作类(M2Mqtt.Net)
  • node插件MongoDB(四)—— 库mongoose 的个性话读取(字段筛选、数据排序、数据截取)(四)
  • AI:73-结合语法知识的神经机器翻译研究
  • [LeetCode]-225. 用队列实现栈
  • Kafka Rebanlace次数过高问题
  • 计算机是如何进行工作的+进程和线程
  • MySQL(11):数据处理之增删改
  • QT QDockWidget