Analysis of once_flag,call_once implementation in C++11 __c++

Source: Internet
Author: User
Tags exception handling mutex

The analysis of this article is based on LLVM's libc++, not gun's libstdc++, because there are too many macros in the libstdc++ code, it looks like the egg hurts.

In multithreaded programming, there is a common scenario where a task needs to be executed only once. It provides convenient auxiliary class once_flag,call_once in c++11. Statement

First, take a look at Once_flag and Call_once's statement:

struct Once_flag
{
    constexpr once_flag () noexcept;
    Once_flag (const once_flag&) = delete;
    once_flag& operator= (const once_flag&) = delete;
Template<class callable, class ... args>
  void Call_once (once_flag& flag, callable&& func, Args&&.. Args);  Std

You can see that Once_flag is not allowed to be modified, and both the copy constructor and the operator= function are declared as delete, which prevents the programmer from messing around.

In addition, Call_once is also very simple, as long as the pass into a once_flag, callback function, and the argument list can be. Sample

Look at an example:

Http://en.cppreference.com/w/cpp/thread/call_once

#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag;
 
void Do_once ()
{
    std::call_once (flag, [] () {std::cout << "called Once" << Std::endl;});
 
int main ()
{
    std::thread T1 (do_once);
    Std::thread T2 (do_once);
    std::thread t3 (do_once);
    std::thread t4 (do_once);
 
    T1.join ();
    T2.join ();
    T3.join ();
    T4.join ();
}
Save As Main.cpp, if you are using g++ or clang++ to compile:

g++-std=c++11-pthread main.cpp

clang++-std=c++11-pthread main.cpp

./a.out

You can see that only one row is output

Called Once

It is worth noting that if an exception is thrown in the execution of a function, then another thread waiting on the Once_flag executes.

For example, the following examples:

#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag;
 
inline void may_throw_function (bool do_throw) {//Only one instance the ' this '
  function can be run Simultaneously
  if (do_throw) {
    std::cout << "throw\n";//The May is printed from 0 to 3 times
    //If function ex Its via exception, another function selected
    throw std::exception ();
 
  Std::cout << "once\n"; Printed exactly once, it ' s guaranteed that
      //There are no messages after it
}
 
inline void do_once (bool Do_throw)
{
  try {
    std::call_once (flag, May_throw_function, Do_throw);
  }
  catch (...) {
  }
}
 
int main ()
{
    std::thread T1 (do_once, true);
    Std::thread T2 (Do_once, true);
    std::thread T3 (do_once, false);
    std::thread t4 (do_once, true);
 
    T1.join ();
    T2.join ();
    T3.join ();
    T4.join ();
}
The output may be 0 to 3 rows throw, and one row of once.

In fact, Once_flag is the equivalent of a lock, and the thread that uses it waits on it, and only one thread allows it to be executed. If the thread throws an exception, select one from the waiting thread and repeat the process above.
Implementation Analysis

Once_flag actually has only one unsigned long __state_ member variable, the call_once is declared as a friend function, so call_once can modify the __state__ variable:

struct Once_flag
{
        once_flag () _noexcept: __state_ (0) {}
private:
    once_flag (const once_flag&); /= delete;
    once_flag& operator= (const once_flag&); = delete;

    unsigned long __state_;

    Template<class _callable>
    friend void Call_once (Once_flag&, _callable);
Call_once uses a __call_once_param class to wrap functions, a common template programming technique.

Template <class _fp>
class __call_once_param
{
    _fp __f_;
Public:
    Explicit __call_once_param (const _fp& __f): __f_ (__f) {}
    void operator () ()
    {
        __f_ ();
    }
};
Template<class _callable>
void Call_once (once_flag& __flag, _callable __func)
{
    if (__flag). __state_!= ~0ul)
    {
        __call_once_param<_callable> __p (__func);
        __call_once (__flag.__state_, &__p, &__call_once_proxy<_Callable>);
    }
}

The most important is the implementation of the __call_once function:

static pthread_mutex_t mut = Pthread_mutex_initializer;

Static pthread_cond_t CV = Pthread_cond_initializer;
    void __call_once (volatile unsigned long& flag, void* arg, Void (*func) (void*)) {Pthread_mutex_lock (&mut);
    while (flag = = 1) pthread_cond_wait (&AMP;CV, &mut);  if (flag = = 0) {#ifndef _libcpp_no_exceptions try {#endif//_libcpp_no_exceptions flag
            = 1;
            Pthread_mutex_unlock (&mut);
            Func (ARG);
            Pthread_mutex_lock (&mut);
            flag = ~0ul;
            Pthread_mutex_unlock (&mut);
Pthread_cond_broadcast (&AMP;CV);
        #ifndef _libcpp_no_exceptions} catch (...)
            {Pthread_mutex_lock (&mut);
            flag = 0ul;
            Pthread_mutex_unlock (&mut);
            Pthread_cond_broadcast (&AMP;CV);
        Throw
#endif//_libcpp_no_exceptions} else Pthread_mutex_unlock (&mut); }
It uses a global mutex and condition to do synchronization, as well as exception handling code.
In fact, when you see Mutext and condition, you know how to achieve it. There is a series of synchronization operations, you can refer to another blog:

http://blog.csdn.net/hengyunabc/article/details/27969613 Parallel Programming Conditional variables (POSIX condition variables)

Although the code looks simple, it's complicated to analyze its various timing carefully.

There is a place that is rather doubtful:

for synchronous __state__ variables, there is no memory order protection, there will be no problem.

Because in the JDK code Locksupport and logic are similar to the __call_once functions above, there is a memory order-related code:

Orderaccess::fence (); the other stuff:

There's something worth mentioning, in C + +, the initialization of static variables is not thread safe.

Like what

void Func () {
    static int value = m;
    ...
}

is actually equivalent to this code:

I

NT __flag = 0
void func () {
    static int value;
    if (!__flag) {
        value = m;
        __flag = 1;
    }
    ...
}

Summary:

There is one more thing to consider: whether all once_flag and call_once share global mutexes and condition will not have performance problems.

First, demand like call_once is not too much in a program. In addition, the Code for the critical section is relatively small, only the code that judges the respective flag.

If hundreds of thousands of threads are waiting for Once_flag, then Pthread_cond_broadcast may cause a "surprise group" effect, but if there are so many threads waiting, there is obviously a problem with programming.

Another place to note is the life cycle of the once_flag, which must be longer than the thread that uses it. So it's usually better to define it as a global variable.


Reference:

http://libcxx.llvm.org/

Http://en.cppreference.com/w/cpp/thread/once_flag

Http://en.cppreference.com/w/cpp/thread/call_once

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.