go多线程

server/2024/10/25 18:06:04/

1.仅加go

在一个golang编写的程序,主函数运行完毕后,程序就结束了

package mainimport ("fmt""time"
)func main() {// 如果这样写go 要加在上面的函数,因为如果只单独加在下面的函数或者都加上,程序就会直接仅执行上面的函数或者不执行go count(5, "羊")count(5, "牛")
}func count(n int, animal string) {for i := 0; i < n; i++ {fmt.Println(i+1, animal)time.Sleep(time.Millisecond * 500)}
}

2.使得两个go都执行

1.直接time.Sleep(3 * time.Second)

package mainimport ("fmt""time"
)func main()// 如果这样写go 要加在上面的函数,因为如果只单独加在下面的函数或者都加上,程序就会直接仅执行上面的函数或者不执行go count(5, "羊")go count(5, "牛")time.Sleep(3 * time.Second)
}func count(n int, animal string) {for i := 0; i < n; i++ {fmt.Println(i+1, animal)time.Sleep(time.Millisecond * 500)}
}

2.WaitGroup 计数器 执行完一个任务就减1

追踪还有多少任务没有执行

package mainimport ("fmt""sync""time"
)func main() {var wg sync.WaitGroupwg.Add(2)go func() {count(5, "羊")wg.Done()}()go func() {count(5, "牛")wg.Done()}()wg.Wait() // 函数结束前原地听命,计数器变为0才继续
}func count(n int, animal string) {for i := 0; i < n; i++ {fmt.Println(i+1, animal)time.Sleep(time.Millisecond * 500)}
}

3.交流goroutine

其他具有多线程的编程语言中,线程之间的交流通过共享内存完成

1.通过计数交流,counte 缺点是可能分配在不同的核心 可能同时执行

package mainimport ("fmt""sync""time"
)func main() {var wg sync.WaitGroupwg.Add(2)go func() {count(5, "羊")wg.Done()}()go func() {count(5, "牛")wg.Done()}()wg.Wait() // 函数结束前原地听命,计数器变为0才继续fmt.Println(counte)
}var counte intfunc count(n int, animal string) {for i := 0; i < n; i++ {fmt.Println(i+1, animal)counte++time.Sleep(time.Millisecond * 500)}
}

2.Channel

不通过共享内存交流,通过交流去共享内存

往channel发送和接收一条消息,都会阻塞代码的执行

当我要发送一条消息,我会一直在这里等着,直到一条消息在channel另一方被接收了

反过来,如果我想从channel接受一条消息 我会等着

如果channel另一方有一个人拿着消息等着,我会立刻收到消息,他会立刻发送消息,然后都会运行

可以通过阻塞的特性同步我们的代码

package mainimport ("fmt""time"
)func main() {c := make(chan string)go count(5, "羊", c)for { // 消息有很多 使用for循环message := <-c // 从channel收到消息fmt.Println(message)}}func count(n int, animal string, c chan string) {for i := 0; i < n; i++ {// 直接往channel里喊话c <- animaltime.Sleep(time.Millisecond * 500)}
}

主函数一直等待channel传来消息,但是count函数已经结束了

对程序来说,新的数据不会传进来

go检测到这一点,报错

在这里插入图片描述
在这里插入图片描述

package mainimport ("fmt""time"
)func main() {c := make(chan string)go count(5, "羊", c)// 方式1for { // 消息有很多 使用for循环message, open := <-c // 从channel收到消息 会额外获得一个bool值,使用open接收if !open {           // 通过bool判断是不是要继续从channel接收消息break}fmt.Println(message)}// 方式2for message2 := range c {fmt.Println(message2)}}func count(n int, animal string, c chan string) {for i := 0; i < n; i++ {// 直接往channel里喊话c <- animaltime.Sleep(time.Millisecond * 500)}close(c) // 关闭channel
}

3.从多个channel接收数据

当你把一个任务分为多个子任务时

可能和每一个子任务都有一个单独的channel专门和那一个子任务通讯

这些channel随时可能传来数据,怎么去管理

往channel发送和接收一条数据都会阻塞程序

有多个channel发送消息时

使用channel保证第一时间从有新消息的channel接收数据

不受不同channel消息频率不同的影响

package mainimport ("fmt""time"
)func main() {c1 := make(chan string)c2 := make(chan string)go func() {for {c1 <- "羊"time.Sleep(time.Millisecond * 500)}}()go func() {for {c1 <- "牛"time.Sleep(time.Millisecond * 2000)}}()//for {// fmt.Println(<-c1) // 往channel发送和接收一条数据都会阻塞程序 所以c1执行完之后就会到c2,然后就会阻塞,c1的消息此时无人接收,所以使用select方法// fmt.Println(<-c2)//}for {select { // 有很多case 哪个不阻塞就先执行case msg := <-c1:fmt.Println(msg)case msg := <-c2:fmt.Println(msg)}}
}

4.树形结构便利 搜索文件

搜索所有叫test的文件或者文件夹

深度优先的搜索实现

1.直接遍历

package mainimport ("fmt""io/ioutil""time"
)var query = "test"
var matches int // 计量数字var workerCount = 0
var maxWorkerCount = 32
var searchRequest = make(chan string) // 让包工头指派工作
var workerDone = make(chan bool)      // 让工人们互相告诉包工头说工作做完了
var foundMatch = make(chan bool)      // 传输关于我找到搜索的结果的消息func main() {start := time.Now()workerCount = 1go search("/Users/lfzxmw/", true)waitForWorkers() //指派完第一个search等待一下fmt.Print(matches, "matches")fmt.Println(time.Since(start))
}func waitForWorkers() {for {select {case path := <-searchRequest: // 有新的工作 search request有新的消息 把此时工人数量加一workerCount++go search(path, true) // 指派一个新的工人工作case <-workerDone: // 有工人工作结束 数量就减一workerCount--if workerCount == 0 { // 工作都做完了return // 退出等待的函数}case <-foundMatch: // 有新的结果找到matches++}}
}func search(path string, master bool) {files, err := ioutil.ReadDir(path)if err == nil {for _, file := range files {name := file.Name()if name == query {foundMatch <- true}if file.IsDir() {if workerCount < maxWorkerCount {searchRequest <- path + name + "/"} else {search(path+name+"/", false)}}}}if master { // 看一下当前的搜索函数是不是一个在goroutine运行的搜索函数workerDone <- true // 是的话告诉总部工作结束}
}

5.扩展

1.带有buffer(缓存)的channel

2.go语言的context包裹

1.带有buffer(缓存)的channel

在 Go 语言中,带有缓冲的通道(buffered channel)允许你在通道中存储一定数量的数据,而不需要立即接收。这使得发送者在发送数据时可以继续工作,而不必等到接收者准备好接收。带缓冲的通道在需要在 goroutine 之间异步通信时非常有用。

package mainimport ("fmt""time"
)func main() {// 创建一个缓冲区大小为 3 的通道bufferedChannel := make(chan int, 3)// 启动一个 goroutine 发送数据go func() {for i := 1; i <= 5; i++ {fmt.Printf("Sending %d to the channel\n", i)bufferedChannel <- i // 发送数据到通道fmt.Printf("Sent %d to the channel\n", i)time.Sleep(1 * time.Second) // 模拟工作延迟}close(bufferedChannel) // 发送完成后关闭通道}()// 接收数据for value := range bufferedChannel {fmt.Printf("Received %d from the channel\n", value)time.Sleep(2 * time.Second) // 模拟处理数据的延迟}fmt.Println("All values received from the channel.")
}
代码说明
  1. 创建缓冲通道
    • 使用 make(chan int, 3) 创建一个整型的缓冲通道,缓冲大小为 3。
  2. 发送数据的 goroutine
    • 在一个 goroutine 中,循环发送 5 个整数到通道中。由于通道是缓冲的,所以最多可以容纳 3 个整数而不会阻塞发送。
  3. 接收数据
    • 使用 for range 循环接收通道中的数据。接收者会阻塞,直到通道中有数据可用。
  4. 关闭通道
    • 发送完成后,使用 close(bufferedChannel) 关闭通道。这是一个好习惯,可以避免接收者在通道中等待更多数据。
注意事项
  • 如果缓冲通道已满,发送者将被阻塞,直到接收者读取数据,使得通道中有空位。
  • 如果通道已空,接收者将被阻塞,直到有数据可接收。
  • 关闭通道是必要的,特别是在需要通知接收者发送完成时。
总结

带缓冲的通道可以有效地在 goroutine 之间传递消息,允许更灵活的并发设计。它们非常适合需要一定程度的异步处理的场景。希望这个示例能够帮助您理解带缓冲通道的使用!如果您有任何进一步的问题,欢迎随时提问。

6.go语言的context包裹

在 Go 语言中,context 包是一个用于处理上下文(context)信息的标准库,广泛用于处理并发操作中的请求传递、取消信号以及超时控制。它尤其在编写网络服务和分布式系统时非常有用。

主要功能

  1. 请求的传递:可以在多个 goroutine 之间传递请求范围的值。
  2. 取消信号:允许上下文的持有者通知其他 goroutine 取消操作。
  3. 超时控制:可以设置超时,从而避免操作长时间阻塞。

常用类型

  • Context:接口类型,表示上下文。
  • Background:返回一个非空的 Context,通常用于顶层的 context。
  • TODO:返回一个非空的 Context,用于某些还未决定的上下文。

创建 Context

1. 背景上下文
ctx := context.Background()
2. 带取消的上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在不再需要时调用取消
3. 带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // 确保在不再需要时调用取消
4. 带截止时间的上下文
deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel() // 确保在不再需要时调用取消
5. 带值的上下文
ctx = context.WithValue(context.Background(), "key", "value")
value := ctx.Value("key")

示例代码

下面是一个完整的示例,展示如何使用 context 包处理并发操作和取消信号:

package mainimport ("context""fmt""time"
)func worker(ctx context.Context, id int) {for {select {case <-ctx.Done():fmt.Printf("Worker %d: stopped\n", id)return // 处理取消信号default:fmt.Printf("Worker %d: working...\n", id)time.Sleep(1 * time.Second) // 模拟工作}}
}func main() {// 创建一个带取消的上下文ctx, cancel := context.WithCancel(context.Background())// 启动多个工作 goroutinefor i := 1; i <= 3; i++ {go worker(ctx, i)}// 等待 5 秒后取消上下文time.Sleep(5 * time.Second)cancel() // 通知所有 goroutine 停止工作// 等待一段时间以确保所有 goroutine 都已退出time.Sleep(2 * time.Second)fmt.Println("All workers stopped.")
}

代码说明

  1. 上下文的创建

    • 使用 context.WithCancel 创建一个可取消的上下文。
  2. 工作 goroutine

    • 每个 worker 在一个无限循环中运行,检查上下文是否已被取消。
  3. 取消上下文

    • 在主函数中,等待 5 秒后调用 cancel(),通知所有 worker 停止工作。
  4. 输出

    • 每个 worker 每秒输出一次工作信息,直到被取消。

注意事项

  • 避免使用全局上下文:尽量避免在函数中使用 context.Background()context.TODO(),而应将上下文信息传递给需要它的函数。
  • 使用 defer:确保调用 cancel(),以避免资源泄露。
  • 优雅地停止 goroutine:通过 ctx.Done() 来监听取消信号,确保 goroutine 能够优雅地停止。

总结

context 包是 Go 语言中用于处理并发操作的重要工具,能够帮助你更好地管理请求的生命周期、取消操作和超时控制。希望这个介绍能帮助您理解 context 包的基本使用。如果您还有其他问题,欢迎提问!


http://www.ppmy.cn/server/134779.html

相关文章

封装echarts组件,即插即用(附源码)

前言&#xff1a;最近一个项目刚收工&#xff0c;分享一个常用的封装echarts的组件。 一、直接上组件代码 <template><el-card class"echart-card" shadow"hover"><template v-slot:header><div class"card-header">&…

构建SpringBoot宠物用品电商:从零到一

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理在线宠物用品交易网站的相关信息成为必然。…

7、Vue2(二) vueRouter3+axios+Vuex3

14.vue-router 3.x 路由安装的时候不是必须的&#xff0c;可以等到使用的时候再装&#xff0c;如果之前没有安装的话&#xff0c;可以再单独安装。之前的终端命令行不要关闭&#xff0c;再重新开一个&#xff0c;还需要再package.json文件的依赖中添加。 如果忘记之前是否有安…

国产大模型基础能力大比拼 - 计数:通义千文 vs 文心一言 vs 智谱 vs 讯飞-正经应用场景的 LLM 逻辑测试

在大语言模型&#xff08;LLM&#xff09;不断涌现的时代&#xff0c;如何评估这些国产大模型的逻辑推理能力&#xff0c;尤其是在处理基础计数问题上的表现&#xff0c;成为了一个备受关注的话题。随着越来越多的国产大模型进入市场&#xff0c;比较它们在不同任务中的表现尤为…

实验23:DA呼吸灯实验

电路硬件: 实现功能: 代码: public.h #ifndef _public_H #define _public_H#include "reg52.h" //#include "key.h"typedef unsigned int u16; typedef unsigned char u8;void delay_10us(u16 n); void delay_ms(u16 ms);#endif public.c #include …

C++ [项目] 愤怒的小鸟

现在才发现C游戏的支持率这么高&#xff0c;那就发几篇吧 零、前情提要 此篇为 制作,由于他没有CSDN,于是由我代发 一、基本介绍 支持Dev-C5.11版本(务必调为英文输入法),基本操作看游戏里的介绍,怎么做的……懒得说,能看懂就看注释,没有的自己猜,如果你很固执……私我吧 …

孤岛架构在安全性方面

孤岛架构在安全性方面的考虑主要涉及如何确保每个孤岛的安全性&#xff0c;同时维护整个系统的安全。 关键的安全性考虑&#xff1a; 1. 数据隔离和访问控制 数据隔离&#xff1a;每个孤岛应该有独立的数据存储&#xff0c;以确保数据隔离。这有助于防止数据泄露和未经授权的…

2024.10月21日- Vue Router路由管理器

Vue Router路由管理器 一、路由的理解 1&#xff09;什么是路由? 一个路由就是一组映射关系&#xff08;key - value&#xff09; key 为路径&#xff0c;value 可能是 function 或 component 2&#xff09;路由分类 后端路由&#xff1a; 理解&#xff1a;value 是 fu…