Go 阻塞

server/2024/12/5 4:05:35/

阻塞 

在Go语言中,阻塞通常指的是一个goroutine(轻量级线程)在等待另一个goroutine完成操作(如I/O操作、channel通信等)时,暂时停止执行的现象。Go语言提供了多种同步和通信机制,可以用于实现阻塞的效果。

使用 Channel 实现阻塞

Channel 是Go语言中的一个核心特性,用于在goroutines之间进行通信。通过channel,你可以实现阻塞等待数据或命令。

package mainimport ("fmt""time"
)func main() {c := make(chan struct{})go func() {fmt.Println("业务处理~~~")time.Sleep(2 * time.Second)fmt.Println("业务处理完成~~~")close(c) // 关闭channel,通知工作完成}()<-c // 阻塞等待channel关闭fmt.Println("处理其他业务~~~")
}

使用 WaitGroup 实现阻塞

WaitGroup 是Go语言中用于同步一组并发操作的另一个工具。它通过计数器来跟踪完成的操作数量。

package mainimport ("fmt""strconv""sync""time"
)func main() {var wg sync.WaitGroup //控制并发组doWork := func(i int) {// wg.Done(): 表示一个事件已经完成。它等价于 wg.Add(-1),但更明确地表达了“完成一个任务”的意图,并且在使用上更安全,因为它不会导致计数变为负数(如果已经到达零,则会panic)。defer wg.Done() // 当函数返回时,通知WaitGroup一个操作已完成相当于wg.Add(-1)fmt.Println("处理业务~~~" + strconv.Itoa(i))time.Sleep(2 * time.Second)fmt.Println("业务处理完成~~~" + strconv.Itoa(i))}for i := 0; i < 5; i++ {wg.Add(1)    // 增加WaitGroup的计数器go doWork(i) // 启动一个goroutine做工作}//主goroutine调用wg.Wait(),直到所有启动的goroutines都通过调用wg.Done()通知它们已经完成工作wg.Wait() // 阻塞,直到WaitGroup的计数器为0fmt.Println("所有业务处理完成~~~")
}

使用 Mutex 和 Conditional Variables 实现阻塞

Mutex(互斥锁)和条件变量可以用来同步访问共享资源,并实现基于条件的阻塞。 

package mainimport ("fmt""sync""time"
)func main() {var mtx sync.Mutex         //创建互斥锁cond := sync.NewCond(&mtx) //使用mtx作为底层互斥锁ready := false// 启动一个 goroutine 来改变条件变量 ready 的值,并通知 cond。go func() {fmt.Println("循环跟goroutine是go内部决定先调度的--------------------goroutine--------------------")time.Sleep(3 * time.Second)mtx.Lock() //使用互斥锁ready = truecond.Signal() // 唤醒至少一个等待的 goroutinemtx.Unlock()  //解锁}()mtx.Lock() // 锁定互斥锁,准备进入条件等待for !ready {fmt.Println("循环跟goroutine是go内部决定先调度的--------------------阻塞--------------------")cond.Wait() // 阻塞,直到 cond.Signal() 被调用//mtx.Unlock()}mtx.Unlock() // 解锁互斥锁,继续执行(此处mtx.Unlock()在for循环里面阻塞等待完成后也可以,也可以没有,因为主线程会结束,但如果后续还需要获取互斥锁则必须要释放否则报错)fmt.Println("准备继续~~~")
}

这里是一些关键的修改和注意事项:

  1. sync.Cond 的使用需要一个 sync.Mutex 作为其底层的互斥锁。在使用 cond.Wait() 之前,必须先锁定这个互斥锁。

  2. cond.Wait() 调用中,当前的互斥锁会被自动释放,goroutine 会阻塞直到它被 cond.Signal()cond.Broadcast() 唤醒。

  3. 一旦 cond.Wait() 返回,goroutine 会重新获取互斥锁,然后继续执行循环或代码块。

  4. cond.Signal() 调用之后,您需要在某个地方调用 mtx.Unlock() 来释放互斥锁,否则主 goroutine 会在 cond.Wait() 之后无法获取到锁。

  5. 您的代码中,cond.Wait() 之后的 mtx.Unlock() 应该在 for 循环之外,以避免在循环的每次迭代中重复加锁和解锁。 

在Go语言中,sync.Mutex(互斥锁)用于保护共享资源不被多个goroutine同时修改,以避免竞态条件。sync.Cond(条件变量)与互斥锁结合使用,可以在多个goroutine之间同步共享条件。以下是关于何时使用 mtx.Lock()mtx.Unlock() 的指导:

mtx.Lock()

  • 在访问或修改由互斥锁保护的共享资源之前使用。
  • 在调用 cond.Wait() 之前使用,以确保在等待条件变量时,共享资源不会被其他goroutine并发访问。
  • 在调用 cond.Signal() 或 cond.Broadcast() 之前使用,因为这些操作需要在互斥锁保护的临界区内执行。

mtx.Unlock()

  • 在完成对共享资源的访问或修改后使用。
  • 在 cond.Wait() 返回后使用,因为我们已经完成了等待期间需要的共享资源访问,并且需要重新获取互斥锁以继续执行。
  • 在不再需要互斥锁保护当前goroutine的执行路径时使用,以允许其他等待互斥锁的goroutine继续执行。

注意事项

  • 互斥锁必须在获取后及时释放,否则会导致死锁。
  • 通常,获取互斥锁和释放互斥锁成对出现,以避免忘记释放锁。

永久阻塞

Go 的运行时的当前设计,假定程序员自己负责检测何时终止一个 goroutine 以及何时终止该程序。可以通过调用 os.Exit 或从 main() 函数的返回来以正常方式终止程序。而有时候我们需要的是使程序阻塞在这一行。

使用 sync.WaitGroup 

一直等待直到 WaitGroup 等于 0 

package mainimport "sync"func main() {var wg sync.WaitGroupwg.Add(1)wg.Wait()
}

空 select

 select{}是一个没有任何 case 的 select,它会一直阻塞

package mainfunc main() {select{}
}

 死循环

虽然能阻塞,但会 100%占用一个 cpu。不建议使用

package mainfunc main() {for {}
}

 用 sync.Mutex

一个已经锁了的锁,再锁一次会一直阻塞,这个不建议使用

package mainimport "sync"func main() {var m sync.Mutexm.Lock()
}

 os.Signal

系统信号量,在 go 里面也是个 channel,在收到特定的消息之前一直阻塞  

package mainimport ("os""os/signal""syscall"
)func main() {sig := make(chan os.Signal, 2)//syscall.SIGTERM 是默认的终止进程信号,通常由服务管理器(如systemd、supervisor等)发送来请求程序正常终止。//syscall.SIGINT 是中断信号,一般由用户按下Ctrl+C键触发,用于请求程序中断执行signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)<-sig
}
从终端发送信号
  • Ctrl+C: 在大多数Unix-like系统(包括Linux和macOS)以及Windows的命令行中,按 Ctrl+C 键会向当前前台进程发送一个 SIGINT(中断)信号。这通常是停止Go程序的快捷方式。

  • Kill命令: 如果你的程序在后台运行,并且你知道其进程ID(PID),可以通过终端发送一个信号。例如,发送一个 SIGTERM 信号,可以使用:kill PID或者指定型号类型kill -SIGTERM PID

 从Go代码内部发送信号
package mainimport ("os""os/signal""syscall""time"
)func main() {sig := make(chan os.Signal, 2)//syscall.SIGTERM 是默认的终止进程信号,通常由服务管理器(如systemd、supervisor等)发送来请求程序正常终止。//syscall.SIGINT 是中断信号,一般由用户按下Ctrl+C键触发,用于请求程序中断执行signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)go func() {time.Sleep(10 * time.Second)sig <- syscall.SIGTERM}()go func() {time.Sleep(5 * time.Second)sig <- syscall.SIGINT}()<-sig
}
使用外部工具或服务管理器

如果你的Go程序作为服务运行,可能由如systemd、supervisord等服务管理器控制,这些管理器通常提供了发送信号给托管服务的机制。具体操作需参考相应服务管理器的文档。

空 channel 或者 nil channel 

channel 会一直阻塞直到收到消息,nil channel 永远阻塞。 

package mainfunc main() {c := make(chan struct{})<-c
}
package mainfunc main() {var c chan struct{} //nil channel<-c
}
 总结

 注意上面写的的代码大部分不能直接运行,都会 panic,提示“all goroutines are asleep - deadlock!”,因为 go 的 runtime 会检查你所有的 goroutine 都卡住了, 没有一个要执行。

你可以在阻塞代码前面加上一个或多个你自己业务逻辑的 goroutine,这样就不会 deadlock 了。

 


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

相关文章

linux系统修改网卡名称

说明&#xff1a; 因操作过程需要停用网卡&#xff0c;导致ssh远程连接不上&#xff0c;需要控制台登录操作。 测试环境&#xff1a; CentOS7.9、8.2虚拟机 Suse15 SP4虚拟机 操作步骤&#xff1a; 方法一&#xff1a; 1、 查看网卡当前名称及状态 ip a2、 将网卡状态从启用…

docker镜像容器常用命令

常用基础命令1、docker info #查看docker版本等信息 2、docker search jenkins #搜索jenkins镜像 3、docker history nginx #查看镜像中各层内容及大小,每层对应的dockerfile中的一条指令。 4、docker network ls #显示当前主机上的所有网络 5、docker logs nginx …

c++ vector容器

在C中&#xff0c;vector 是一个动态数组&#xff0c;它可以根据需要自动增长和缩小。以下是对vector的基本概念和常用操作的详细解释&#xff1a; vector基本概念 vector 是一个模板类&#xff0c;它提供了对动态数组的封装。你可以用它来存储任何类型的对象&#xff0c;并自…

Mysql-几何类型-POINT

在MySQL中&#xff0c;地理空间数据类型和功能被称为GIS&#xff08;Geographic Information System&#xff0c;地理信息系统&#xff09;。MySQL支持几种不同的空间数据类型&#xff0c;包括点&#xff08;POINT&#xff09;、线&#xff08;LINESTRING&#xff09;、多边形&…

Spring的表达式语言(SpEL)使用

Spring表达式语言&#xff08;Spring Expression Language&#xff0c;简称SpEL&#xff09;是Spring框架提供的一种强大的表达式语言&#xff0c;它在Spring 2.0版本引入。SpEL的设计灵感来源于传统的EL&#xff08;Expression Language&#xff09;&#xff0c;即JSP中的表达…

基于ChatGPT 和 OpenAI 模型的现代生成式 AI

书籍&#xff1a;Modern Generative AI with ChatGPT and OpenAI Models: Leverage the capabilities of OpenAIs LLM for productivity and innovation with GPT3 and GPT4 作者&#xff1a;Valentina Alto 出版&#xff1a;Packt Publishing 书籍下载-《基于ChatGPT 和 Op…

vue3+ts(<script setup lang=“ts“>)刷新页面后保持下拉框选中效果

效果图&#xff1a; 代码&#xff1a; <template><div class"app-layout"><div class"app-box"><div class"header"><div class"header-left"></div><div class"title">室外智…

泽众财务RPA机器人常见五个应用场景

泽众RPA&#xff08;即机器人流程自动化&#xff0c;Robotic Process Automation, RPA&#xff09;解决方案是依托于各类先进信息技术手段的虚拟劳动力 &#xff08;数字劳动力&#xff09;&#xff0c;根据预先设定的程序操作指令对任务进行自动化处理&#xff0c;实现业务流程…