浅谈go语言channel通道造成的死锁问题

news/2024/5/20 12:48:11 标签: golang, 死锁

go语言虽然号称协程之间必须使用channel通信,但是如果使用不当,非常容易形成deadlock死锁。下面的代码就是这样的一个例子

package main

import "fmt"

func doWork(id int, dataChan chan int, done chan bool) {
	for data := range dataChan { 
		fmt.Printf("Worker %d received %c\n", id, data)		
		done <- true
	}
}

func createChannels(id int) Worker {
	ch := Worker{
		data: make(chan int),
		done: make(chan bool),
	}
	go doWork(id, ch.data, ch.done)
	return ch
}

func chanRun() {
	var channels [10]Worker
	for i := 0; i < 10; i++ {
		channels[i] = createChannels(i)
	}

	for i, ch := range channels {
		ch.data <- 'a' + i
		//<-ch.done
	}

	for i, ch := range channels {
		ch.data <- 'A' + i
		//<-ch.done
	}

	for _, ch := range channels {
		<-ch.done
		<-ch.done
	}

}

type Worker struct {
	data chan int
	done chan bool
}

func main() {
	chanRun()
}

为什么会形成死锁? 这里需要对无缓冲channel有深刻的理解。

无缓冲channel需要发送方和接收方同时在线,只要有一方没有就位,就会引发死锁

在在doWork这段代码里,dataChan和done这两个通道是耦合在一起了,因为他们在一个同步代码块里,所以会出现互相等待的问题。  一旦其中一个通道被阻塞,另外一个通道也会因为等待陷入无法就绪状态,从而产生deadlock死锁问题。

这里的解决死锁关键就是解耦,也就是将两个channel的因果关系破坏掉,避免互相等待。

具体来说:可以给其中的一个channel操作再开一个协程,也就是并发执行,这样就脱离了同步代码块,在这个例子里面,如下修改即可。

done <- true ==》 go func() { done <- true }()
func doWork(id int, dataChan chan int, done chan bool) {
	for data := range dataChan { // 注意range是阻塞式读取channel,所以后面的done <- true如果不开协程也会被阻塞住
		fmt.Printf("Worker %d received %c\n", id, data)
		//给done这个channel单独再可一个协程,并发执行,脱离doWork主程序,
        // 从而避免dataChan和done这两个channel在同步代码块中的互相等待    
		go func() { done <- true }()
		//done <- true
	}
}

还有一个解决办法: 将两个通道中任意一个通道变成有缓冲通道

ch := Worker{
   data: make(chan int,1),
   done: make(chan bool),
}

或者

ch := Worker{
   data: make(chan int),
   done: make(chan bool,1),
}

此时done通道也就无需单独开协程。

因此我们使用无缓冲channel时一定要非常小心,为避免死锁, 总体有以下两大原则:

1. 尽量避免在同步代码中使用两个channel的情况,避免channel之间出现耦合、嵌套的情况,因为容易出现两个channel互相等待的问题; 如果确实没法避免,则必须给其中一个channel单独再开一个协程,类似于下面的代码:

go func() { channel <- data }()  // 通过匿名函数给channel再单独开一个协程

2. 往无缓冲channel发送数据时务必单独再开一个协程

如果没有把握处理好无缓冲channle,为了安全起见,建议使用有缓冲channel

顺附:有缓冲channel和无缓冲channel的区别说明

有缓冲channel和无缓冲channel本质上就是一个同步和异步的区别。

无缓冲:就是完全同步,发送和接收方紧紧耦合在一起。

有缓冲: 异步工作。 通道未满之前,发送方和接收方各自独立工作:也就是都不会阻塞。这个有点类似于消息队列MQ.此时通道起到了解耦的作用。通道满了以后,就是同步通信,此时发送方阻塞。


http://www.niftyadmin.cn/n/1434612.html

相关文章

推荐系统笔记

推荐系统 常用的推荐系统算法可以分为以下三种&#xff1a; 1、基于人口统计学的推荐2、基于内容的推荐3、基于协同过滤的推荐 其中&#xff0c;本文简单介绍一下基于UGC&#xff08;用户自定义标签&#xff09;推荐和基于协同过滤的推荐。 基于UGC&#xff08;用户自定义标…

kafka创建topic_c#操作kafka(上)搭建kafka环境

小伙伴们大家好&#xff0c;今天没有概念&#xff0c;也没有理论&#xff0c;仅仅和大家一起快速的在centos上搭建一下kafka的测试环境&#xff0c;测试环境嘛&#xff0c;不涉及集群什么的&#xff0c;仅仅是单节点的kafka&#xff0c;日后可以在这个基础上&#xff0c;进行集…

SeNet || 注意力机制——源代码+注释

文章目录1 SeNet介绍2 SeNet优点3 Se模块的具体介绍4 完整代码1 SeNet介绍 SENet是Squeeze-and-Excitation Networks的简称&#xff0c;由Momenta公司所作并发于2017CVPR&#xff0c;论文中的SENet赢得了ImageNet最后一届&#xff08;ImageNet 2017&#xff09;的图像识别冠军…

基于MLP进行文本分类

最近学习了基于Pytorch框架下的MLP、CNN、RNN网络模型&#xff0c;利用在GitHub上获取的商品评论数据进行文本分类实验。本文介绍了如何在Pytorch框架下建立MLP对数据进行二分类&#xff0c;数据集大致如下&#xff1a; 1、导入模块 import pandas as pd import numpy as np…

python高阶函数map_Python学习之高阶函数——map/reduce

map map()函数接收两个参数&#xff0c;一个是函数&#xff0c;一个是Iterable&#xff0c;map将传入的函数依次作用到序列的每个元素&#xff0c;并把结果作为新的Iterator返回。 即map(函数&#xff0c;Iteratable) map()传入的第一个参数是f&#xff0c;即函数对象本身。由于…

Python的数据切片操作

文章目录1 一维数组的切片操作1.1 A[i]1.2 A[-1]1.3 A[:n]1.4 A[:-1]1.5 A[n:]1.6 A[-1:]1.7 A[m,n]2 二维数组的切片操作2.1 B[1,:]2.2 B[:,1]2.3 B[0,2]2.4 B[1:, 1:]1 一维数组的切片操作 A [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1.1 A[i] 作用&#xff1a;取数组中下标为 i 的…

python的easygui模块用法_Python 模块EasyGui详细介绍

# coding:utf-8 # __author__ Mark sinoberg # __date__ 2016/5/25 # __Desc__ 能让你最初选择的简单的界面&#xff0c;第二个参数为一个列表 import easygui # choice easygui.buttonbox("这里是提示的语句信息&#xff1a;\n", id"codetool"> ch…

python给图片加动态特效_Python tkinter实现的图片移动碰撞动画效果【附源码下载】...

本文实例讲述了Python tkinter实现的图片移动碰撞动画效果。分享给大家供大家参考&#xff0c;具体如下&#xff1a; 先来看看运行效果&#xff1a;具体代码如下&#xff1a; #!/usr/bin/python # -*- coding: utf-8 -*- import time try: from tkinter import * except Import…