diff --git a/libcxx/src/mutex.cpp b/libcxx/src/mutex.cpp --- a/libcxx/src/mutex.cpp +++ b/libcxx/src/mutex.cpp @@ -208,6 +208,25 @@ flag = once_flag::_Pending; return false; #else // !_LIBCPP_HAS_NO_THREADS + // Instead of checking if threads are enabled using + // __libcpp_is_threading_api_enabled, we can check whether other threads have + // been spawned, getting a little extra performance in single-threaded applications. + // + // This is safe because a) switching from multi- to single-threaded without + // us noticing only means we might perform superfluous synchronization. b) we + // can't go from single- to multi-threaded unless THIS thread spawns a thread + // (which might happen in the user provided function, between call_once_prep + // and finish/abort) c) if that does happen, nobody is inside call_once_prep/ + // finish/abort, so everybody will check __libcpp_might_have_multiple_threads + // again, and aquire the mutex before reading/writing to flag. + if (!__libcpp_might_have_multiple_threads()) { + // Revert to single-threaded implementation + if (flag != once_flag::_Unset) + return true; + flag = once_flag::_Pending; + return false; + } + __libcpp_mutex_lock(&mut); while (flag == once_flag::_Pending) __libcpp_condvar_wait(&cv, &mut); @@ -227,6 +246,14 @@ #ifdef _LIBCPP_HAS_NO_THREADS flag = once_flag::_Complete; #else // !_LIBCPP_HAS_NO_THREADS + // As in call_once_prep, instead of checking if threads are enabled, check + // whether other threads have been spawned. See that comment for more details + if (!__libcpp_might_have_multiple_threads()) { + // Revert to single-threaded implementation + flag = once_flag::_Complete; + return; + } + __libcpp_mutex_lock(&mut); __libcpp_atomic_store(&flag, once_flag::_Complete, _AO_Release); __libcpp_mutex_unlock(&mut); @@ -238,6 +265,14 @@ #ifdef _LIBCPP_HAS_NO_THREADS flag = once_flag::_Unset; #else // !_LIBCPP_HAS_NO_THREADS + // As in call_once_prep, instead of checking if threads are enabled, check + // whether other threads have been spawned. See that comment for more details + if (!__libcpp_might_have_multiple_threads()) { + // Revert to single-threaded implementation + flag = once_flag::_Unset; + return; + } + __libcpp_mutex_lock(&mut); __libcpp_relaxed_store(&flag, once_flag::_Unset); __libcpp_mutex_unlock(&mut); diff --git a/libcxx/test/std/thread/thread.mutex/thread.once/thread.once.callonce/call_once.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.once/thread.once.callonce/call_once.pass.cpp --- a/libcxx/test/std/thread/thread.mutex/thread.once/thread.once.callonce/call_once.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.once/thread.once.callonce/call_once.pass.cpp @@ -101,6 +101,22 @@ std::call_once(flg2, init2(), 4, 5); } +std::once_flag flg5; + +int init5_called = 0; + +void f5(); + +void init5(std::thread* t0) { + if (t0 != nullptr) { + *t0 = support::make_test_thread(f5); + std::this_thread::sleep_for(ms(250)); + } + ++init5_called; +} + +void f5() { std::call_once(flg5, init5, nullptr); } + #endif // TEST_STD_VER >= 11 std::once_flag flg41; @@ -189,6 +205,18 @@ int main(int, char**) { +#if TEST_STD_VER >= 11 + // check 2nd thread spawned from inside init + // + // This test is most useful if it is done before the other checks, that way + // when we first enter init5, no other threads have ever been created + { + std::thread t0; // don't start a second thread yet + std::call_once(flg5, init5, &t0); + t0.join(); + assert(init5_called == 1); + } +#endif // TEST_STD_VER >= 11 // check basic functionality { std::thread t0 = support::make_test_thread(f0);