简体   繁体   English

使用“首先到达”标志(而不是锁定/互斥体)来同步对数据的访问

[英]Synchronizing access to data using a “got there first” flag, instead of a lock/mutex

Suppose we have a function which accesses some globally shared data - and suppose this function will be called by multiple concurrent threads. 假设我们有一个访问某些全局共享数据的函数,并且假设该函数将被多个并发线程调用。

Naturally, we need to somehow synchronize access to this data. 自然,我们需要以某种方式同步对这些数据的访问。 Is it possible to do this via a mechanism where we have an atomic flag, and whichever thread manages to set the flag can then proceed to access the shared data? 是否可以通过我们具有原子标记的机制来执行此操作,然后任何设法设置该标记的线程都可以继续访问共享数据? Whereas the losing threads will not block on a lock, but simply return from the function. 而丢失的线程将不会在锁上阻塞,而只是从函数中返回。

Something like the following: 类似于以下内容:

Given some globally shared data along with a synchronization flag: 给定一些全局共享的数据以及同步标志:

namespace global {

  int x;
  int y;

  std::atomic_flag sync_flag = ATOMIC_FLAG_INIT;

}

And our function which will be accessed by concurrent threads: 并发线程可以访问的函数:

void race()
{
   // See if we won the race
   //
   if (!global::sync_flag.test_and_set())
   {
      // We won
      //
      global::x = 10;
      global::y = 11;
   }
   else
   {
     // We lost... 
     return;
   }
}

Does the above code guarantee that global::x and global::y will be safely accessed by only a single thread, and that no race conditions will occur? 上面的代码是否保证只能通过单个线程安全地访问global::xglobal::y ,并且不会发生竞争条件? Or is this not guaranteed due to memory ordering problems? 还是由于内存排序问题而不能保证?

Note we never actually locked a mutex or anything, so no thread ends up blocking. 请注意,我们实际上从未锁定过互斥锁或任何东西,因此没有线程最终会阻塞。 The idea here is simply to ensure that only one thread is allowed to access (non-atomic) global variables here. 这里的想法只是确保此处仅允许一个线程访问(非原子的)全局变量。

Of course, after the winning thread is done, we would need to somehow safely clear the atomic flag if we ever intend on calling race() again. 当然,在完成获胜线程后,如果我们打算再次调用race() ,则需要以某种方式安全地清除原子标志。 That is a problem I haven't thought much about yet, but it is really beyond the scope of this question. 我还没有考虑过这个问题,但这确实超出了这个问题的范围。

So is the above code race-condition free (for a single call to race() )? 那么上面的代码是否具有竞争条件(一次调用race() )?

Yes. 是。 Only one thread can get the not-yet-set test on the flag. 只有一个线程可以对该标志进行尚未设置的测试。 This has an advantage over a mutex in that on some architectures the atomic flag can be done with lock-free operations. 与互斥锁相比,这具有优势,因为在某些体系结构上,原子标志可以通过无锁操作来完成。

The interface of std::atomic_flag is pretty straightforward. std::atomic_flag的接口非常简单。 According to the reference and the example it provides, your code should work correctly (until you call race() the second time, of course). 根据参考资料及其提供的示例,您的代码应该可以正常工作(当然,直到您第二次调用race()为止)。

What you have is almost correct. 你所拥有的几乎是正确的。 What you are trying to do is feasible with std::atomic_flag. 您尝试使用std :: atomic_flag可行。

void race()
{
   // See if we won the race
   // NEW CODE - pass std::memory_order_acquire
   if (!global::sync_flag.test_and_set(std::memory_order_acquire))
   {
      // We won
      //
      global::x = 10;
      global::y = 11;

      // NEW CODE - release the flag
      global::sync_flag.clear(std::memory_order_release);         
   }
   else
   {
     // We lost... 
     return;
   }
}

This code is race condition free. 此代码是无竞争条件的。

Reference: http://en.cppreference.com/w/cpp/atomic/atomic_flag 参考: http//en.cppreference.com/w/cpp/atomic/atomic_flag

I see nothing here that cannot be accomplished with bog-standard std::mutex . 我在这里看不到用标准标准std::mutex无法完成的任何事情。

std::mutex does not require you to pause and wait until the mutex is acquired. std::mutex不需要您暂停并等待直到获取互斥量。

See the documentation for std::unique_lock 's constructor that takes an optional second argument of std::try_to_lock_t . 请参阅std::unique_lock的构造函数的文档,该构造函数采用可选的第二个参数std::try_to_lock_t If the mutex can be acquired, it gets acquired. 如果可以获取互斥体,则它将获取。 Otherwise, you keep going. 否则,您会继续前进。

It's always better to stick to the standard, and tested, library functions, especially when they seem to already meet the desired requirements, instead of trying to roll one's own. 始终坚持使用经过测试的标准库函数总是更好的选择,尤其是当它们似乎已经满足期望的要求时,而不是尝试自己动手做。

Yes. 是。 In fact, this is precisely how a call to try_lock on a mutex would work. 实际上,这正是在互斥try_lock调用try_lock

Your assumption that the use of mutexes inherently involves blocking threads is incorrect. 您关于互斥量的使用固有涉及阻塞线程的假设是错误的。 We usually call lock on them which, by convention, blocks while waiting for the mutex to become available. 我们通常对它们调用lock ,按照惯例,锁在等待互斥体可用时会阻塞。 But there's no reason you actually have to. 但是实际上并没有理由。 Mutexes themselves don't block anything. 互斥体本身不会阻止任何东西。 As far as I can tell, they're just wrappers around atomic flags. 据我所知,它们只是原子标记的包装。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM