goroutinue和channel

devtools/2024/11/14 3:08:44/

goroutinue和channel

  • 需求
  • 传统方式实现
  • goroutinue
    • 进程和线程说明
    • 并发和并行
    • go协程和go主线程
    • MPG
    • 设置Go运行的cpu数
  • channel(管道)-看个需求
    • 使用互斥锁、写锁
    • channel 实现
  • 使用select可以解决从管道取数据的阻塞问题(无需手动关闭channel了)
  • goroutinue中使用了recover,解决协程中出现panic,导致程序崩溃问题
  • 素数问题

需求

要求:统计 1-20000的数字中,哪些是素数?
分析思路:
(1)传统的方法中,就是使用一个循环,循环的判断各个数是不是素数
(2)使用并发或并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会使用到goroutinue

传统方式实现

go">func main() {now := time.Now()printPrime(1000000)seconds := time.Since(now).Seconds()fmt.Println("time : ", seconds)
}// 打印素数
// 素数定义:仅能被1和它本身整除的大于1的自然数func printPrime(n int) {for i := 2; i <= n; i++ {isPrime(i)}
}// 判断是否是素数func isPrime(n int) bool {if n <= 1 {return false}sqrt := int(math.Sqrt(float64(n))) // 更高效for i := 2; i <= sqrt; i++ {if n%i == 0 {return false}}fmt.Println(n)return true
}

单核 大约需要0.9s左右

goroutinue_45">goroutinue

进程和线程说明

  1. 进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单元
  2. 线程是进城的一个执行实例,是程序执行的最小单元,他是比进程更小的能独立运行的基本单位
  3. 一个进程可以创建核销多个线程,同一个进程中的多个线程可以并发执行
  4. 一个程序至少有一个进程,一个进程至少有一个线程

并发和并行

  1. 多个线程在单核上执行,就是并发
  2. 多个线程在多核上运行,就是并行

gogo_54">go协程和go主线程

  1. Go主线程:一个狗线程上可以起多个协程,协程是轻量级的线程
  2. Go协程的特点
    • 有独立的栈空间
    • 共享程序堆空间
    • 调度由用户控制
    • 协程是轻量级的线程

MPG

  1. 主线程是一个物理线程,直接作用在cpu上。是重量级的,非常耗费cpu资源
  2. 协程是从主线程开启的,是轻量级别的线程,是逻辑太,对资源消耗相对小
  3. Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其他编程语言的并发机制一般基于线程的,开启过多的线程,资源耗费大,这里就凸显Golang在并发的优势了

MPG模式基本介绍
M: 操作系统主线程 是物理线程
P: 协程执行需要的上下文
G: 协程
在这里插入图片描述

  1. 当前程序由三个M,如果三个M都在一个cpu运行,就是并发,如果在不同的cpu运行,就是并行
  2. M1、M2、M3 正在执行一个G,M1协程队列有三个,M2协程队列有三个,M3线程队列有两个
  3. go协程是 轻量级的线程,是逻辑态的,Go可以容易的启动上万个协程
  4. 其他程序c/Java的多线程,往往是内核态的,比较重量级,几千个线程可能耗光cpu

在这里插入图片描述1. 分成两个部分来看
2. 原来的情况是M0主线程正在执行Go协程,另外有三个协程在队列等待
3. 如果Go协程阻塞,比如读文件或数据库等
4. 这是就会创建M1主线程(也可能从已有线程中取出M1),并且将等待的3个协程挂到M1下开始执行,M0的主线程下的GO仍然执行文件IO的读写
5.这样MPG调度模拟,可以既让Go执行,同时也不会让队列的其他协程一直阻塞,仍然可以并发或并行执行
6. 等到Go不阻塞了,M0会被放到空闲的主线程继续执行(从已有线程中取),同时GO又会被唤醒

设置Go运行的cpu数

go1.8后,默认让程序运行在多个核上,可以不用设置了

go">fmt.Println("CPU", runtime.NumCPU())
runtime.GOMAXPROCS(19)

channel(管道)-看个需求

现在要计算1-200的各个数的阶乘,并且把各个数的阶乘写入到map中,最后显示出来。要求使用goroutinue实现
分析:

  1. map不是并发安全,应该会有安全问题
  2. 加锁或者使用channel通信实现
go">var wg sync.WaitGroup
func main() {wg.Add(200)calcJiCheng(200)
}func calcJiCheng(n int) {m := make(map[int]int64)for i := 1; i <= n; i++ {go jiCheng(i, m)}wg.Wait() // 使用同步等待组,阻塞主线程fmt.Println(m)
}func jiCheng(n int, m map[int]int64) {var sum int64 = 1for i := 1; i <= n; i++ {sum *= int64(i)}m[n] = sumwg.Done()
}

fatal error: concurrent map writes 对map存在并发写操作
java 解决方案,使用 ConcurrentHashMap 或者 手动加锁

使用互斥锁、写锁

go">var wg sync.WaitGroup// var mutex sync.Mutex // 互斥锁 实现
var mutex sync.RWMutex // 写锁实现func main() {wg.Add(200)calcJiCheng(200)
}func calcJiCheng(n int) {m := make(map[int]int64)for i := 1; i <= n; i++ {go jiCheng(i, m)}wg.Wait() // 使用同步等待组,阻塞主线程fmt.Println(m)
}func jiCheng(n int, m map[int]int64) {defer mutex.Unlock()mutex.Lock()var sum int64 = 1for i := 1; i <= n; i++ {sum *= int64(i)}m[n] = sumwg.Done()
}

这是低水平程序猿首选,哈哈,高级程序猿使用channel解决

channel 实现

  1. channel 本质上是一个数据结构 队列
  2. 数据是先进先出
  3. 线程安全,多goroutinue访问时,不需要加锁,就是说channel本身就是线程安全的
  4. channel时有类型的,一个string的channel只能存放string类型数据

无缓冲管道 var ch = make(chan [2]int64)
有写必有读,缺少一个必死锁,
使用range读必须在写入最后一个关闭通道

缓冲管道 var ch = make(chan [2]int64,3)
写入超过缓冲值,必死锁
没有值读取,必死锁
使用range读必须在写入最后一个关闭通道

管道的读写是一个阻塞操作,我更愿意把其当作消息队列来用

go">var wg sync.WaitGroup
var ch = make(chan [2]int64)func main() {wg.Add(200)calcJiCheng(200)
}func calcJiCheng(n int) {m := make(map[int]int64)for i := 1; i <= n; i++ {go jiCheng(i, n)}for a := range ch {var num int64 = a[0]var value int64 = a[1]m[int(num)] = value}fmt.Println(m)
}func jiCheng(n int, end int) {var sum int64 = 1for i := 1; i <= n; i++ {sum *= int64(i)}ch <- [2]int64{int64(n), sum}wg.Done()// 关闭channelif n == end {close(ch)}
}

使用select可以解决从管道取数据的阻塞问题(无需手动关闭channel了)

go">func main() {intChan := make(chan int, 10)for i := 0; i < 10; i++ {intChan <- i}strChan := make(chan string, 5)for i := 0; i < 5; i++ {strChan <- strconv.Itoa(i)}// 传统的方法在遍历管道的时候,如果不关闭会阻塞而导致 deadlock// 问题 在实际开发中,可能我们不好确定什么时候关闭该管道// 可以使用select 方式 解决for {select {case v := <-intChan:fmt.Printf("从intChan读取的数据 %d\n", v)case v := <-strChan:fmt.Printf("从strChan读取的数据 %s\n", v)default:fmt.Printf("取不到了,不玩了  ")return}}
}

goroutinuerecoverpanic_250">goroutinue中使用了recover,解决协程中出现panic,导致程序崩溃问题

如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成程序崩溃,这是我们在goroutinue中使用recover来捕获panic,进行处理,这样即时这个协程发生问题,但主线程不受影响,继续执行

go">func main() {go hi()go say()time.Sleep(time.Second * 2)
}
func say() {println("say 3333333")}
func hi() {defer func() {if err := recover(); err != nil {fmt.Println("程序出戳了", err)}}()fmt.Println("hi")var a map[string]inta["2"] = 3}

素数问题

go">
var wg sync.WaitGroup
var ch = make(chan int)
var done = make(chan struct{})func main() {now := time.Now()wg.Add(1000)go printPrime(1000)go func() {for i := range ch {fmt.Println("主线程", i)}close(done)}()wg.Wait()close(ch)<-doneseconds := time.Since(now).Seconds()fmt.Println("time : ", seconds)
}// 打印素数
// 素数定义:仅能被1和它本身整除的大于1的自然数func printPrime(n int) {for i := 1; i <= n; i++ {go isPrime(i, n)}
}// 判断是否是素数func isPrime(n int, end int) bool {defer func() {wg.Done()}()if n <= 1 {return false}sqrt := int(math.Sqrt(float64(n))) // 更高效for i := 2; i <= sqrt; i++ {if n%i == 0 {return false}}ch <- nfmt.Println(n)return true
}

http://www.ppmy.cn/devtools/19873.html

相关文章

Qt:实现TCP同步与异步读写消息

一、异步读写 在 Qt 中实现 TCP 客户端和服务器的同步和异步读写消息涉及使用 QTcpSocket 和 QTcpServer 类。这两个类提供了用于建立 TCP 连接、发送和接收数据的功能。下面是一个简单的示例&#xff0c;演示了如何在 Qt 中实现 TCP 客户端和服务器的同步和异步读写消息&…

【数字电路与系统】【北京航空航天大学】实验:时序逻辑设计——三色灯开关(二)、需求分析和系统设计

本次实验&#xff08;一&#xff09;见博客&#xff1a;【数字电路与系统】【北京航空航天大学】实验&#xff1a;时序逻辑设计——三色灯开关&#xff08;一&#xff09;、实验指导书 说明&#xff1a;本次实验的代码使用verilog编写&#xff0c;文章中为阅读方便&#xff0c…

软件测试报告的用途

软件测试报告的用途十分广泛&#xff0c;主要体现在以下几个方面&#xff1a; 评估软件质量&#xff1a;软件测试报告是对软件进行全面、系统测试后的总结&#xff0c;通过报告中的各项数据和结果&#xff0c;可以评估软件的质量水平&#xff0c;包括功能的完整性、性能的稳定…

Swift-27-类的初始化与销毁

Swift的初始化是一个有大量规则的固定过程。初始化是设置类型实例的操作&#xff0c;包括给每个存储属性初始值&#xff0c;以及一些其他准备工作。完成这个过程后&#xff0c;实例就可以使用了。 简单来讲就是类的构造函数&#xff0c;基本语法如下&#xff1a; 注意&#xff…

个人开发 App 最简单方法:使用现代开发工具和平台

在移动应用市场的蓬勃发展下&#xff0c;个人开发者也有机会将自己的创意转化为实际的应用程序&#xff0c;并通过应用商店实现盈利。然而&#xff0c;对于许多初学者来说&#xff0c;如何开始个人开发一个应用可能会感到困惑。本文将介绍个人开发 App 的最简单方法&#xff0c…

【MySQL】select查询

1. 基本的SELECT语句 1.1 SELECT ... FROM SELECT 标识选择哪些列FROM 标识从哪个表中选择例&#xff1a;SELECT * FROM student; #使用通配符&#xff0c;*表示返回所有的列例&#xff1a;SELECT id,name,guardian_phone FROM student; #具体行和列 1.2 列的别名 …

python3爬虫笔记2

1 urlpare模块 urlparse模块主要用于处理URL字符串&#xff0c;它的核心功能是将URL拆分为多个组成部分&#xff0c;并允许你通过名字属性或索引来访问这些部分。通过调用urlparse模块的相关函数&#xff0c;你可以轻松解析URL&#xff0c;获取其不同组件的信息&#xff0c;如…

安装docker后部署一个redis服务

安装 Docker 后&#xff0c;您可以使用 Docker Hub 上提供的 Redis 镜像轻松部署 Redis 服务。以下是在 Docker 中部署 Redis 服务的步骤&#xff1a; 1. 拉取 Redis 镜像&#xff1a; 使用以下命令从 Docker Hub 拉取 Redis 镜像&#xff1a; docker pull redis2. 运行 Red…