关于Sync.Mutex的竞争问题

Chaoxin · 2020-09-15 08:49:07 · 1346 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2020-09-15 08:49:07 的主题,其中的信息可能已经有所发展或是发生改变。

我是go的初学者,我今天写了一个问题代码。本意是让资源生成和消费的时候同时打印出该资源信息。 但是该程序会导致死锁,有没有什么解决方法能保证输出信息和管道同步

var mu sync.Mutex

func produce(ch chan<- int){
    for i:=0;i<10;i++{
        mu.Lock()
        ch<-i
        fmt.Println("produce:"+strconv.Itoa(i))
        mu.Unlock()
    }
}

func consumer(ch <- chan int){
    for i:=0;i<10;i++{
        mu.Lock()
        v:=<-ch
        fmt.Println("consumer:"+strconv.Itoa(v))
        mu.Unlock()
    }
}
func main(){
    ch:=make(chan int,5)
     go produce(ch)
    go consumer(ch)
    time.Sleep(10*time.Second)
}

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1346 次点击  ∙  1 赞  
加入收藏 微博
14 回复  |  直到 2020-11-02 14:42:00
Felixw
Felixw · #1 · 5年之前

个人理解不一定正确: produce在执行第五次的时候,ch已经执行mu.Lock(),此时ch已满,通道阻塞,但是mu.Unlocl没有释放锁,所以造成了持续阻塞。

Chaoxin
Chaoxin · #2 · 5年之前
FelixwFelixw #1 回复

个人理解不一定正确: produce在执行第五次的时候,ch已经执行mu.Lock(),此时ch已满,通道阻塞,但是mu.Unlocl没有释放锁,所以造成了持续阻塞。

谢谢解答。不过我的疑惑是produce在通道未满的时候,也有释放锁。但是consumer永远竞争不到,produce一直占有

byssh1989
byssh1989 · #3 · 5年之前

小兄弟注意哈,使用go关键字 以goroutine方式执行后面的代码,就不能根据父协程的代码顺序来看谁先执行了。所以 go produce(ch) go consumer(ch) 这两行代码不能说明produce先被执行。更离谱的是,CPU使用单核执行的前提下,大多数机子上可能先执行consumer。

zzustu
zzustu · #4 · 5年之前

刚运行了一下,原因是正如3楼所说的,consumer(ch)早于produce(ch)先执行, consumer(ch)上来先拿到mu.Lock然后立马阻塞在了v:=<-ch, 而produce(ch)执行的时候一直拿不到mu.Lock,所以导致produce(ch)无法生产, consumer(ch)干等着没什么可消费的。你把go consumer(ch) go produce(ch)顺序换一下就不一样了

COMS
COMS · #5 · 5年之前

由于consumer里面chan没有东西而进行堵塞,导致锁无法释放!进行了饥饿状态了。如果要解决建议使用 chan 对其进行拦截,就可以实现同步了

COMS
COMS · #6 · 5年之前

package main

import ( "fmt" "strconv" "sync" "time" )

var mu sync.Mutex

func produce(ch chan<- int,index chan <-int){ for i:=0;i<10;i++{ index <- 1 ch<-i fmt.Println("produce:"+strconv.Itoa(i)) } }

func consumer(ch <- chan int,index <- chan int){ for i:=0;i<10;i++{ v:=<-ch fmt.Println("consumer:"+strconv.Itoa(v)) <-index } } func main(){ ch := make(chan int,5) index := make(chan int,1) go produce(ch,index) go consumer(ch,index) time.Sleep(10*time.Second) }

jiuker
jiuker · #7 · 5年之前

chan不需要容量呗

jiuker
jiuker · #8 · 5年之前

或者chan只有一个容量也可以,保证放之前,肯定被拿了

aixiangbing
aixiangbing · #9 · 5年之前

回答下我的浅薄建议,为什么要把chan和sync.Mutex一起使用尼,这种场景见过嘛。这样写,不是一样的嘛

func produce(ch chan<- int) { for i := 0; i < 10; i++ { ch <- i fmt.Println("produce:" + strconv.Itoa(i)) } }

func consumer(ch <-chan int) { for i := 0; i < 10; i++ { v := <-ch fmt.Println("consumer:" + strconv.Itoa(v)) } }

aixiangbing
aixiangbing · #10 · 5年之前

过度设计了吧,个人觉得

Chaoxin
Chaoxin · #11 · 5年之前
aixiangbingaixiangbing #9 回复

回答下我的浅薄建议,为什么要把chan和sync.Mutex一起使用尼,这种场景见过嘛。这样写,不是一样的嘛 func produce(ch chan<- int) { for i := 0; i < 10; i++ { ch <- i fmt.Println("produce:" + strconv.Itoa(i)) } } func consumer(ch <-chan int) { for i := 0; i < 10; i++ { v := <-ch fmt.Println("consumer:" + strconv.Itoa(v)) } }

老哥我说了我的本意是让资源生成和消费的时候同时打印出该资源信息。

__Golang__
__Golang__ · #12 · 4年之前
var s1, s2 = make(chan int, 1), make(chan int, 1)
var wg sync.WaitGroup = sync.WaitGroup{}

func produce(ch chan<- int) {
    for i := 0; i < 10; i++ {

        <-s1
        ch <- i
        fmt.Println("produce:" + strconv.Itoa(i))
        s2 <- 1
    }
    close(s2)
    wg.Done()
}

func consumer(ch <-chan int) {

    for i := 0; i < 10; i++ {

        <-s2
        v := <-ch
        fmt.Println("consumer:" + strconv.Itoa(v))
        s1 <- 1
    }
    close(s1)
    wg.Done()
}
func main() {
    ch := make(chan int, 1)

    wg.Add(2)
    s1 <- 1
    go consumer(ch)
    go produce(ch)

    wg.Wait()
}
ruige
ruige · #13 · 4年之前

你可以换一种方式,返回通道。

package main

import (
    "fmt"
    "strconv"
)

func produce(size uint) <-chan int {
    ch := make(chan int, size)
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
            fmt.Println("produce:" + strconv.Itoa(i))
        }
        close(ch)
    }()
    return ch
}

func consumer(ch <-chan int) {
    for i := 0; i < 10; i++ {
        v := <-ch
        fmt.Println("consumer:" + strconv.Itoa(v))
    }
}
func main() {
    p := produce(5)
    consumer(p)
}
Atticus
Atticus · #14 · 4年之前

个人理解,你在对通道元素的发送、接收外部封装了锁,确实保证了同一时间要么在向通道写元素,要么在从通道里面读元素,但是由于发送/接收操作都在go routine中实现,因此两种情况会出现死锁:

  • 接收操作先获取到锁:此时由于通道中没有元素,接收的routine会一直阻塞,且由于锁的原因,发送routine始终无法竞争到锁,更别提向通道发送元素
  • 发送routine连续5次竞争到锁或者接收routine连续5次竞争到锁的情况:由于通道容量只有5,连续接收5个或者连续发送5个元素后均无法再继续操作,此时也会阻塞
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传