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,70 +205,82 @@ int main(int, char**) { - // check basic functionality - { - std::thread t0 = support::make_test_thread(f0); - std::thread t1 = support::make_test_thread(f0); - t0.join(); - t1.join(); - assert(init0_called == 1); - } +#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); + std::thread t1 = support::make_test_thread(f0); + t0.join(); + t1.join(); + assert(init0_called == 1); + } #ifndef TEST_HAS_NO_EXCEPTIONS - // check basic exception safety - { - std::thread t0 = support::make_test_thread(f3); - std::thread t1 = support::make_test_thread(f3); - t0.join(); - t1.join(); - assert(init3_called == 2); - assert(init3_completed == 1); - } + // check basic exception safety + { + std::thread t0 = support::make_test_thread(f3); + std::thread t1 = support::make_test_thread(f3); + t0.join(); + t1.join(); + assert(init3_called == 2); + assert(init3_completed == 1); + } #endif - // check deadlock avoidance - { - std::thread t0 = support::make_test_thread(f41); - std::thread t1 = support::make_test_thread(f42); - t0.join(); - t1.join(); - assert(init41_called == 1); - assert(init42_called == 1); - } + // check deadlock avoidance + { + std::thread t0 = support::make_test_thread(f41); + std::thread t1 = support::make_test_thread(f42); + t0.join(); + t1.join(); + assert(init41_called == 1); + assert(init42_called == 1); + } #if TEST_STD_VER >= 11 - // check functors with 1 arg - { - std::thread t0 = support::make_test_thread(f1); - std::thread t1 = support::make_test_thread(f1); - t0.join(); - t1.join(); - assert(init1::called == 1); - } - // check functors with 2 args - { - std::thread t0 = support::make_test_thread(f2); - std::thread t1 = support::make_test_thread(f2); - t0.join(); - t1.join(); - assert(init2::called == 5); - } - { - std::once_flag f; - std::call_once(f, MoveOnly(), MoveOnly()); - } - // check LWG2442: call_once() shouldn't DECAY_COPY() - { - std::once_flag f; - int i = 0; - std::call_once(f, NonCopyable(), i); - } -// reference qualifiers on functions are a C++11 extension - { - std::once_flag f1, f2; - RefQual rq; - std::call_once(f1, rq); - assert(rq.lv_called == 1); - std::call_once(f2, std::move(rq)); - assert(rq.rv_called == 1); - } + // check functors with 1 arg + { + std::thread t0 = support::make_test_thread(f1); + std::thread t1 = support::make_test_thread(f1); + t0.join(); + t1.join(); + assert(init1::called == 1); + } + // check functors with 2 args + { + std::thread t0 = support::make_test_thread(f2); + std::thread t1 = support::make_test_thread(f2); + t0.join(); + t1.join(); + assert(init2::called == 5); + } + { + std::once_flag f; + std::call_once(f, MoveOnly(), MoveOnly()); + } + // check LWG2442: call_once() shouldn't DECAY_COPY() + { + std::once_flag f; + int i = 0; + std::call_once(f, NonCopyable(), i); + } + // reference qualifiers on functions are a C++11 extension + { + std::once_flag f1, f2; + RefQual rq; + std::call_once(f1, rq); + assert(rq.lv_called == 1); + std::call_once(f2, std::move(rq)); + assert(rq.rv_called == 1); + } #endif // TEST_STD_VER >= 11 return 0;