【问题标题】:Synchronized conditional statement in GoGo中的同步条件语句
【发布时间】:2021-03-29 17:56:21
【问题描述】:

我有一个方法可以在多个 goroutine 中同时运行。

在这个方法中,我有一个条件语句。如果条件语句为真,我希望所有其他调用此方法的 goroutine 等待 一个且只有一个 goroutine 执行此条件语句,然后再继续下一节。

例如:

type SomeClass struct {
    mu     sync.Mutex
}

func (c *SomeClass) SomeFunc() {
    //Do some calculation
    
    if condition {
        //This part should be executed by only one goroutine if the condition is true. 
        //All others must wait for this to finish
    }
    
    //Additional calculations
}

我想这样使用它:

func main(){
//initilize 


go someClass.SomeFunc()

//If the condition is true, the following will wait at the conditional statement until the first one finishes the code inside the conditional block
//Once it's done, they can run concurrently 
go someClass.SomeFunc()
go someClass.SomeFunc()
}

编辑 这可能不是正确的设计,所以我正在寻找有关如何实现它的任何建议。

编辑2: 请注意,每个例程都有自己的条件。 condition 的值不在线程之间共享。但是,只有当 2 个或多个例程中的条件恰好同时为真时,条件内的工作才应该只运行一次。

【问题讨论】:

  • 问题到底是什么?您有一个互斥锁,并且只有一个调用者可以持有您描述的行为的互斥锁。您是否尝试过使用互斥锁?
  • @JimB 问题是如果将锁放在 if 语句中,那么所有例程都会进入该块中,这不是我想要的。如果锁在 if 语句之外,那么所有例程都将在那里等待锁并一次执行一个,即使语句为假。这也不是我想要的。我想确保 if 语句仅在条件为真时由第一个例程执行,一旦完成(或者如果条件为假),那么所有例程都可以并行运行。
  • 你描述的可能sync.Once 会是理想的。它确保函数只执行一次,所有的 goroutine 都会等待它完成。
  • 当条件发生变化时,可以创建一个新的sync.Once值。

标签: go synchronization


【解决方案1】:

您将需要一个互斥锁来保护条件免受并发读/写,然后在您希望再次执行同步代码时重置条件的方法。

type SomeClass struct {
    conditionMu sync.Mutex
    condition   bool
}

func (c *SomeClass) SomeFunc() {
    // Lock the mutex, so that concurrent calls to SomeFunc will wait here.
    c.conditionMu.Lock()
    if c.condition {
        // Synchronous code goes here.

        // Reset the condition to false so that any waiting goroutines won't run the code inside this block again.
        c.condition = false
    }
    
    // Unlock the mutex, and any waiting goroutines.
    c.conditionMu.Unlock()
}

// ResetCondition sets the stored condition to true in a thread-safe manner.
func (c *SomeClass) ResetCondition() {
    c.conditionMu.Lock()
    c.condition = true
    c.conditionMu.Unlock()
}

【讨论】:

  • 这将强制所有例程同步上述条件并一次执行一个,即使条件为假。请在下面查看我的答案,这似乎可以解决此问题。
  • @Aiden:不,这将阻塞所有直到满足条件,然后它们可以在条件阻塞之后同时进行,这正是您所要求的:“我希望所有其他 goroutines 调用此方法等待一个且只有一个 goroutine 执行此条件语句,然后再继续下一部分”。如果您确实需要答案中的第二个条件(尽管您所拥有的可能会导致 condition 上的数据竞争),那很好,但根据所述规范,此代码是正确的。
  • 只是为了向可能遇到此问题的其他人澄清这一点:条件值在例程之间不共享,因此在检查条件之前无需获取锁。我们只关心检查条件内的代码是否至少运行一次,如果两个或多个线程恰好在同一时间有一个真实的条件。因此,接受的答案有效。
【解决方案2】:

此问题的其他答案不正确,因为它们不满足问题的要求。

如果在条件语句之外添加锁,那么它将充当屏障并强制所有例程在该位置同步。这不是这个问题的重点。假设解析条件值需要很长时间,我们不想一次检查一个值。我们想让每个进程同时检查条件,如果条件为假,我们可以继续前进而不会停止。

如果条件不成立,我们希望确保 goroutines 并行运行。在方法内部和条件语句外部添加锁将不允许这种情况发生。

以下解决方案是正确的,并且通过了所有测试并且表现良好。

解决方案 1:

使用 2 个嵌套条件语句,例如:

注意,在这种情况下,如果条件为假,则不会调用锁,也不需要同步。一切都可以并行运行。

type SomeClass struct {
    conditionMu sync.Mutex
    rwMu        sync.RWMutex
    additionalWorkRequired bool
}

func (c *SomeClass) SomeFunc() {
    //Do some work ...
    //Note: The condition is not shared, some routines can have false and some true at the same time, which is fine.
    condition := true;

    // All routines can check this condition and go inside the block if the condition is true
    if condition {
        c.rwMutex.Lock()
        c.additionalWorkRequired = true
        c.rwMutex.Unlock()

        //Lock so other routines can wait here for the first one
        c.conditionMu.Lock()
    
        if c.additionalWorkRequired {
             // Synchronous code goes here.
             c.additionalWorkRequired = false
        }

        //Unlock so all other processors can move forward in parallel 
        c.conditionMu.unlock()
       
    }

   //Finish up the remaining work

}

解决方案 2:

使用sync/singleflight 中的do 函数可以自动处理这种情况。

来自文档:

Do 执行并返回给定函数的结果,确保一次只针对给定键执行一次。如果出现重复,则重复调用者会等待原始调用者完成并接收相同的结果。返回值 shared 表示 v 是否被分配给多个调用者。

编辑:

由于许多人似乎对这个问题和答案感到困惑,我添加了一个用例,可能会使事情更清楚:

1. Send a HTTP Request 
2. If the server returns an error saying credentials are incorrect (This is condition):
       2.1. Save current credentials in a local variable 
       2.2. Acquire the mutex lock
             2.2.1. Compare the shared credentials with the ones in the local variable(This is the second condition)
             If they are the same, then replace them with new ones
            
       2.3. Unlock
       2.4. Retry request
      
       

【讨论】:

  • 如果condition 从未保护我的互斥锁,那么当多个读取器和写入器积极使用它时,它如何安全地更改状态?我没看懂你的代码。
  • 我觉得使用RW锁其实是对的,但是你用错了。在您的if(condition) 之前,您需要获得RLock(),当您在该条件内获得“写入”锁时,这将阻止所有其他代码进入。你根本不需要你的布尔值。
  • 其实condition并不是例程之间共享的,它是一个独立的值。额外的工作只是确保下一次函数被调用的所有例程的条件都为假。假设我们想将标头中的值发送到服务器,服务器告诉我们数据已过期,我们需要标头中的新数据。我们更新数据一次,所以当函数被调用时,所有例程都会发送正确的标题。
  • @mh-cbon 请在答案下查看添加的用例,这可能会更清楚。我注意到这个问题不是很清楚。
猜你喜欢
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-02
  • 1970-01-01
  • 1970-01-01
  • 2015-02-15
  • 1970-01-01
相关资源
最近更新 更多