【问题标题】:Mutual Exclusion of Concurrent Goroutines并发 Goroutines 的互斥
【发布时间】:2017-07-14 03:46:18
【问题描述】:

在我的代码中有三个并发例程。我尝试简要概述一下我的代码,

Routine 1 {
do something

*Send int to Routine 2
Send int to Routine 3
Print Something
Print Something*

do something
}

Routine 2 {
do something

*Send int to Routine 1
Send int to Routine 3
Print Something
Print Something*

do something
}

Routine 3 {
do something

*Send int to Routine 1
Send int to Routine 2
Print Something
Print Something*

do something
}

main {
routine1
routine2
routine3
}

我希望,当两个之间的代码做某事(两个星号之间的代码)正在执行时,控制流不能转到其他 goroutine。例如,当例程 1 正在执行两颗星之间的事件(发送和打印事件)时,例程 2 和 3 必须被阻塞(意味着执行流程不会从例程 1 传递到例程 2 或 3)。完成最后一个打印事件后,执行流程可能会转到例程 2 或 3。任何人都可以通过指定来帮助我,我该如何实现?是否可以通过WaitGroup 实现上述规范?任何人都可以通过给出一个简单的例子来告诉我如何使用 WaitGroup 来实现上述指定的例子。谢谢。

注意:我给出了两个发送和两个打印选项,实际上有很多发送和打印。

【问题讨论】:

  • 请提供一个清晰的场景,甚至是您自己的实现(即使它不起作用)。这里的“发送”命令是什么意思?如果它正在调用一个函数,上面的代码就是一个无限循环。
  • 这里发送的意思是,发送一个整数。我的代码可以在play.golang.org/p/wYFViAQbN2 中找到。在我想要的代码中,当例程 1 的第 27 到 35 行(可以说例程 1 的关键部分)正在执行时,其他例程必须停止执行。
  • 感谢您提供有用的更新。您不能停止其他 goroutine,但可以阻止执行不同 goroutine 的特定部分,以避免使用互斥锁或阻塞通道同时执行。在这种情况下,我可以帮助您在第 27 行等待其他 goroutine 完成,并在其他 goroutine 的开头等到第 35 行。如果这是您想要的,我可以用代码解释它。
  • 我想,例如在上面的例子中,当routine1正在执行发送和打印事件时,程序2和3被阻塞(意味着执行流程不会传递到程序2或3)。完成最后一个打印事件后,执行流程可能会转到例程 2 或 3。
  • Mostafa,当例程 1 从第 27 行到第 35 行执行时,我想等待所有其他 go 例程。

标签: go concurrency goroutine


【解决方案1】:

如果我理解正确,您想要的是防止同时执行每个功能的某些部分和其他功能。以下代码执行此操作:fmt.Println 行不会在其他例程运行时发生。以下是发生的情况:当执行到达打印部分时,它会等到其他例程结束,如果它们正在运行,并且当这个打印行正在执行时,其他例程不会启动并等待。我希望这就是你要找的。如果我错了,请纠正我。

package main

import (
    "fmt"
    "rand"
    "sync"
)

var (
    mutex1, mutex2, mutex3 sync.Mutex
    wg sync.WaitGroup
)

func Routine1() {
    mutex1.Lock()
    // do something
    for i := 0; i < 200; i++ {
        mutex2.Lock()
        mutex3.Lock()
        fmt.Println("value of z")
        mutex2.Unlock()
        mutex3.Unlock()
    }
    // do something
    mutex1.Unlock()
    wg.Done()
}

func Routine2() {
    mutex2.Lock()
    // do something
    for i := 0; i < 200; i++ {
        mutex1.Lock()
        mutex3.Lock()
        fmt.Println("value of z")
        mutex1.Unlock()
        mutex3.Unlock()
    }
    // do something
    mutex2.Unlock()
    wg.Done()
}

func Routine3() {
    mutex3.Lock()
    // do something
    for i := 0; i < 200; i++ {
        mutex1.Lock()
        mutex2.Lock()
        fmt.Println("value of z")
        mutex1.Unlock()
        mutex2.Unlock()
    }
    // do something
    mutex3.Unlock()
    wg.Done()
}

func main() {
    wg.Add(3)
    go Routine1()
    go Routine2()
    Routine3()
    wg.Wait()
}

更新:让我在这里解释一下这三个互斥锁:互斥锁,如documentation says:“互斥锁”。这意味着当您在互斥锁上调用Lock 时,如果其他人锁定了同一个互斥锁,您的代码就会在那里等待。在您致电Unlock 之后,将立即恢复被阻止的代码。

在这里,我通过在函数开头锁定互斥锁并在结尾解锁互斥锁,将每个函数放入其自己的互斥锁中。通过这种简单的机制,您可以避免在运行这些函数的同时运行您想要的任何代码部分。例如,当Routine1 正在运行时,您希望在任何地方都有不应运行的代码,只需在该代码的开头锁定mutex1 并在末尾解锁即可。这就是我在Routine2Routine3 的适当行中所做的。希望能澄清一些事情。

【讨论】:

  • 非常感谢穆斯塔法。在这里,假设在例程 1 中,如果我在 mutex3.Lock() mutex2.Unlock() 之间添加更多打印事件,直到这些事件不会执行,例程 2 和 3 将无法执行任何事件。正确吗?
  • 是的,你是对的。让我更新我的答案,清楚地解释这三个互斥锁在这里的作用。
  • 对不起,Mostafa 我分享了你的代码play.golang.org/p/VsQ_09FGVQ。但是,它只执行例程 3,而不是例程 2 或 1 的语句。您能帮我解决错误吗?
  • 我专注于使互斥锁工作,忘记了为 goroutine 添加WaitGroup。此更新版本运行良好。另请注意,在main 中,我不会将Routine3 作为goroutine 运行,因为在您的主要示例中它不是那样的。你也可以简单地将它作为一个 goroutine 运行,一切都会正常运行。
  • @Mostafa:如果有 N 个例程,其中 N 是有限正数,你的解决方案是什么?
【解决方案2】:

您可以使用sync.Mutex。例如,im.Lock()im.Unlock() 之间的所有内容都将排除另一个 goroutine。

package main

import (
"fmt"
"sync"
)

func f1(wg *sync.WaitGroup, im *sync.Mutex, i *int) {
  for {
    im.Lock()
    (*i)++
    fmt.Printf("Go1: %d\n", *i)
    done := *i >= 10
    im.Unlock()
    if done {
      break
    }
  }
  wg.Done()
}

func f2(wg *sync.WaitGroup, im *sync.Mutex, i *int) {
  for {
    im.Lock()
    (*i)++
    fmt.Printf("Go2: %d\n", *i)
    done := *i >= 10
    im.Unlock()
    if done {
      break
    }
  }
  wg.Done()
}

func main() {
    var wg sync.WaitGroup

    var im sync.Mutex
    var i int

    wg.Add(2)
    go f1(&wg, &im, &i)
    go f2(&wg, &im, &i)
    wg.Wait()   

}

【讨论】:

  • 非常感谢laslowh。但是,是否有可能针对我所说的情况实施它。因为,它会在第一个例程完成后第二个例程开始(对不起,可能是我错了,因为我是 Go 的新手)。您能否以我的示例说明这一点?
  • 嗯,简短的故事是你不知道 goroutine 什么时候执行,但是上面的代码并不是要等待第一个完成,然后再开始第二个。 Mutex 唯一能做的就是确保它们不会同时执行“关键部分”。临界区是介于 Lock 和 Unlock 调用之间的任何内容。
  • 对不起我的错误。实际上,我想说的是,在我的示例中,routine1,2 & 3 未在主函数中定义(我修改了我的示例)。对于我的示例,是否可以实现 Sync & Mutex。您能否为我给出的示例案例提供简单的示例来说明如何实现 Sync & Mutex(对不起,我对 Go 知之甚少)。谢谢。
  • 这段代码就像我想给你的建议。如果您对此的唯一问题是关于在main 之外实现它,那很容易:将wgim(甚至在本例中为i)定义为全局变量;将这两个闭包定义为单独的函数;并使用go &lt;funcname&gt; 运行它们。这有帮助吗?
  • 能不能给出一个简单的示例代码或者伪代码来说明?
【解决方案3】:

另一种方法是拥有一个控制通道,在任何时候只允许执行一个 goroutine,每个例程在完成原子操作时都会发送回“控制锁”:

package main
import "fmt"
import "time"

func routine(id int, control chan struct{}){
    for {
        // Get the control
        <-control
        fmt.Printf("routine %d got control\n", id)
        fmt.Printf("A lot of things happen here...")
        time.Sleep(1)
        fmt.Printf("... but only in routine %d !\n", id)
        fmt.Printf("routine %d gives back control\n", id)
        // Sending back the control to whichever other routine catches it
        control<-struct{}{}
    }
}

func main() {
    // Control channel is blocking
    control := make(chan struct{})

    // Start all routines
    go routine(0, control)
    go routine(1, control)
    go routine(2, control)

    // Sending control to whichever catches it first
    control<-struct{}{}
    // Let routines play for some time...
    time.Sleep(10)
    // Getting control back and terminating
    <-control
    close(control)
    fmt.Println("Finished !")
}

打印出来:

routine 0 got control
A lot of things happen here...... but only in routine 0 !
routine 0 gives back control
routine 1 got control
A lot of things happen here...... but only in routine 1 !
routine 1 gives back control
routine 2 got control
A lot of things happen here...... but only in routine 2 !
routine 2 gives back control
routine 0 got control
A lot of things happen here...... but only in routine 0 !
routine 0 gives back control
routine 1 got control
A lot of things happen here...... but only in routine 1 !
routine 1 gives back control
routine 2 got control
A lot of things happen here...... but only in routine 2 !
routine 2 gives back control
routine 0 got control
A lot of things happen here...... but only in routine 0 !
routine 0 gives back control
routine 1 got control
A lot of things happen here...... but only in routine 1 !
routine 1 gives back control
routine 2 got control
A lot of things happen here...... but only in routine 2 !
routine 2 gives back control
routine 0 got control
A lot of things happen here...... but only in routine 0 !
routine 0 gives back control
routine 1 got control
A lot of things happen here...... but only in routine 1 !
routine 1 gives back control
routine 2 got control
A lot of things happen here...... but only in routine 2 !
routine 2 gives back control
Finished !

【讨论】:

    【解决方案4】:

    您要求一个明确停止其他例程的库函数?明确一点,在 Go 和大多数其他语言中都是不可能的。并且您的情况无法同步,互斥情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-27
      • 1970-01-01
      • 1970-01-01
      • 2011-01-06
      • 2018-05-23
      • 1970-01-01
      相关资源
      最近更新 更多