libc++的 call once 为什么用 mutex/cv 而不是 atomic test_and_set

2023-04-11 16:14:47 +08:00
 dangyuluo

看了下源代码,call_once 的实现是

template<class _Callable, class... _Args>
inline _LIBCPP_INLINE_VISIBILITY
void
call_once(once_flag& __flag, _Callable&& __func, _Args&&... __args)
{
    if (__libcpp_acquire_load(&__flag.__state_) != ~once_flag::_State_type(0))
    {
        typedef tuple<_Callable&&, _Args&&...> _Gp;
        _Gp __f(_VSTD::forward<_Callable>(__func), _VSTD::forward<_Args>(__args)...);
        __call_once_param<_Gp> __p(__f);
        __call_once(__flag.__state_, &__p, &__call_once_proxy<_Gp>);
    }
}

其中__call_once简化后:

void __call_once(volatile once_flag::_State_type& flag, void* arg,
                 void (*func)(void*)) {

  __libcpp_mutex_lock(&mut);
  while (flag == 1)
    __libcpp_condvar_wait(&cv, &mut);
  if (flag == 0) {
    try {
      __libcpp_relaxed_store(&flag, once_flag::_State_type(1));
      __libcpp_mutex_unlock(&mut);
      func(arg);
      __libcpp_mutex_lock(&mut);
      __libcpp_atomic_store(&flag, ~once_flag::_State_type(0), _AO_Release);
      __libcpp_mutex_unlock(&mut);
      __libcpp_condvar_broadcast(&cv);
    } catch (...) {
      __libcpp_mutex_lock(&mut);
      __libcpp_relaxed_store(&flag, once_flag::_State_type(0));
      __libcpp_mutex_unlock(&mut);
      __libcpp_condvar_broadcast(&cv);
      throw;
    }
  } else
    __libcpp_mutex_unlock(&mut);
}

请问这里是出于什么考量不使用 atomic test_and_set ?

1690 次点击
所在节点    C++
12 条回复
dangyuluo
2023-04-11 16:15:20 +08:00
chatgpt 给的样例:
```cpp
template<typename Callable, typename ...Args>
void call_once(std::once_flag& flag, Callable&& func, Args&&... args)
{
// Atomically check if the flag is set
if (!flag.test_and_set())
{
// The flag is not set, so call the function
std::forward<Callable>(func)(std::forward<Args>(args)...);
// Reset the flag to indicate that the function has been called
flag.clear();
}
}
```
dangyuluo
2023-04-11 16:17:35 +08:00
Abseil 的 call_once 就是采用了 compare_exchange_strong. 感觉更合理

https://github.com/abseil/abseil-cpp/blob/master/absl/base/call_once.h#L174
liberize
2023-04-11 19:09:13 +08:00
假设 2 个线程同时执行 call_once ,必须保证 2 个线程都是函数执行完之后 call_once 才返回,你的这个例子显然不能保证,甚至可以执行多次。
dangyuluo
2023-04-12 00:58:20 +08:00
@liberize 可是第二个线程的 call_once 并不会是 blocking 的吧,cppreference 上是这么解释的:

> If, by the time call_once is called, flag indicates that f was already called, call_once returns right away (such a call to call_once is known as passive).
cnbatch
2023-04-12 02:24:02 +08:00
我猜,可能是因为有潜在的“ABA 问题”,所以就索性用 mutex 简单粗暴免除隐患吧?
dangyuluo
2023-04-12 04:38:20 +08:00
仔细读了一下文档,可能指的是这里:
> The end of each active call synchronizes-with the next active call in that order.
nlzy
2023-04-12 08:04:16 +08:00
ChatGPT 的实现已经完全错了。合理的 call_once 应当会等待其他线程并阻塞的,只要没看到阻塞的代码就肯定是错的。

Abseil 没有保证异常下的语义,所以不能用来代替 C++ 标准里的 std::call_once 。

只有 libc++ 实现了全部的 std::call_once 的语义。

在我看来 libc++ 的代码是最合理的,call_once 里的第一行 acquire_load 已经是一个 fast path 优化了,如果这个 fast path 进不去,没有理由再去利用其他的机制(包括 test_and_set 或者 compare_and_swap )增加一个 fast path 优化。而且 call_once 是绝对不可能用无锁算法实现的,因为 call_once 会等待其他线程,那在用户态等待其他线程不用 mtx/cv 那还能用啥?在我看来 Abseil 自己包装一个 spinlock 是真的丑陋。
liberize
2023-04-12 08:26:48 +08:00
@dangyuluo 这个说的是 was already called ,我说的是 was being called
dangyuluo
2023-04-12 13:20:29 +08:00
@nlzy 请教了下一个在 C++委员会的同事,解释说是 call_once 需要保证第一个线程 throw 之后第二个线程可以继续执行。所以一个额外的同步是需要的。
dangyuluo
2023-04-12 14:38:58 +08:00
@nlzy 忘了问一点了。。为什么所有的 call_once 要用同一个 mutex ,难道`call_once(func1)`和`call_once(func2)`要互相竞争么

```
_LIBCPP_SAFE_STATIC static __libcpp_mutex_t mut = _LIBCPP_MUTEX_INITIALIZER;
```
nlzy
2023-04-12 15:34:14 +08:00
@dangyuluo libc++ 的这个 static 令我瞬间觉得 Abseil 用 futex 实现的 spinlock 其实挺顺眼的。我收回“在我看来 libc++ 的代码是最合理的”那句话。
j16ZgMV9cs6ZB23n
2023-07-29 22:34:29 +08:00
看了眼 想起最近自己魔改的 libc++吓了一跳,原来只有非 win32 call_once 才会遇到这个问题。

在 win32 下,libc++会判断是否是 microsoft abi 然后使用系统函数 InitOnceExecuteOnce 交给系统实现。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/931632

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX