# 进行延迟初始化——std::call\_once

有时我们有一些特定的代码段，可以在多个线程间并行执行，不过其中有一些功能需要在进行执行前，完成一次初始化的过程。一个很简单的方式，就是在程序进入并行前，执行已存在的准备函数。

这种方法有如下几个缺点：

* 当并行线程来自于一个库，使用者肯定会忘记调用准备函数。这样会让库函数不是那么容易的让人使用。
* 当准备函数特别复杂，并且在某些条件下我们要通过条件来判断，是否要执行这个准备函数。

本节中，我们将来了解一下`std::call_once`，其能帮助使用简单且优雅的方式解决上面提到的问题。

## How to do it...

我们将完成一个程序，我们使用多线程对同一段代码进行执行。虽然这里执行的是相同的代码，但是我们的准备函数只需要运行一次：

1.  包含必要的头文件，并声明所使用的命名空间：

    ```cpp
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <vector>

    using namespace std;
    ```
2.  我们将使用`std::call_once`。为了对其进行使用，需要对`once_flag`进行实例化。在对指定函数使用`call_once`时，需要对所有线程进行同步：

    ```cpp
    once_flag callflag;
    ```
3.  现在来定义一个只需要执行一次的函数，就让这个函数打印一个感叹号吧：

    ```cpp
    static void once_print()
    {
        cout << '!';
    }
    ```
4.  再来定义所有线程都会运行的函数。首先，要通过`std::call_once`调用`once_print`。`call_once`需要我们之前定义的变量`callflag`。其会被用来对线程进行安排：

    ```cpp
    static void print(size_t x)
    {
        std::call_once(callflag, once_print);
        cout << x;
    }
    ```
5.  OK，让我们启动10个线程，并且让他们使用`print`函数进行执行：

    ```cpp
    int main()
    {
        vector<thread> v;

        for (size_t i {0}; i < 10; ++i) {
            v.emplace_back(print, i);
        }

        for (auto &t : v) { t.join(); }
        cout << '\n';
    }
    ```
6.  编译并运行程序，我们就会得到如下的输出。首先，我们可以看到由`once_print`函数打印出的感叹号。然后，我么可以看到线程对应的ID号。另外，其会对所有线程进行同步，所以不会有ID在`once_print`函数执行前被打印：

    ```cpp
    $ ./call_once
    !1239406758
    ```

## How it works...

`std::call_once`工作原理和栅栏类似。其能对一个函数(或是一个可调用的对象)进行访问。第一个线程达到`call_once`的线程会执行对应的函数。直到函数执行结束，其他线程才能不被`call_once`所阻塞。当第一个线程从准备函数中返回后，其他线程也就都结束了阻塞。

我们可以对这个过程进行安排，当有一个变量决定其他线程的运行时，线程则必须对这个变量进行等待，直到这个变量准备好了，所有变量才能运行。这个变量就是`once_flag callflag;`。每一个`call_once`都需要一个`once_flag`实例作为参数，来表明预处理函数是否运行了一次。

另一个细节是：如果`call_once`执行失败了(因为准备函数抛出了异常)，那么下一个线程则会再去尝试执行(这种情况发生在下一次执行不抛出异常的时候)。
