# 门闩和栅栏

门闩和栅栏是比较简单的线程同步机制，其能使一些线程阻塞，直到计数器变为零时解除阻塞。首先，不要把栅栏和内存栅栏混为一谈。C++ 20/23中，我们假设有三种门闩和栅栏：`std::latch`、`std::barrier`和`std::flex_barrier`。

首先，要回答两个问题:

1. 这三种同步线程的机制有什么不同?`std::latch`只能使用一次，但是`std::barrier`和`std::flex_barrier`可以使用多次。此外，`std::flex_barrier`允许计数器变为0时执行一个函数。
2. 哪些支持的门闩和栅栏的用例，在C++11和C++14中无法通过future、线程或条件变量与锁结合来实现呢?门闩和栅栏并不涉及新的用例，但它们使用起来要容易得多。通常是在内部使用无锁机制，所以它们还具有更高的性能。

## std::latch

`std::latch`门闩是一个倒计时器，该值可以在构造函数中设置。门闩可以通过使用`latch.count_down_and_wait`来减小计数，并阻塞线程，直到计数器变为0。另外，`latch.count_down(n)`可以将计数器减少n，而不进行阻塞。如果没有给出参数，n默认为1。门闩也有`latch.is_ready`可以用来检查计数器是否为零，以及`latch.wait`会阻塞线程，直到计数器变为零。`std::latch`的计数器不能增加或重置，因此不能复用。

下面是来自[N4204提案](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4204.html)的一个简短代码段。

```cpp
void DoWork(threadpool *pool){
  latch completion_latch(NTASKS);
  for (int i = 0; i < NTASKS; ++i){
    pool->add_task([&]{
      // perform work
        ...
        completion_latch.count_down();
    }); 
  }
  // Block until work is done
  completion_latch.wait();
}
```

`std::latch completion_latch`在其构造函数中将计数器设置为NTASKS (第2行)，线程池执行NTASKS(第4 - 8行)个任务。每个任务结束时(第7行)，计数器递减。第11行是运行DoWork函数的线程，以及工作流的栅栏。这样，线程就会阻塞，直到所有任务都完成。

`std::barrier`与`std::latch`非常相似。

## std::barrier

`std::latch`和`std::barrier`之间的区别是，`std::barrier`计数器可以重置，所以可以多次地使用。计数器变为零之后，立即进入完成阶段。与`std::flex_barrier`有关，`std::barrier`有一个空的完成阶段。`std::barrier`有两个有趣的成员函数：`std::arrive_and_wait`和`std::arrive_and_drop`。当`std::arrive_and_wait`在同步点阻塞时，`std::arrive_and_drop`会从相关线程集中，删除自己的线程。未指定此函数是否阻塞，直到完成阶段结束。这里没有对函数块进行指定，是否到完成阶段才算结束。

> **N4204提案**
>
> 该建议使用`vector<thread*>`，并将动态分配的线程推给vector：`workers.push_back(new thread([&]{ ... }))`。这会产生内存泄漏。应该将线程放到`std::unique_ptr`中，或者直接在vector中进行创建: `workers.emplace_back([&]{ ... })`，这个适用于`std::barrier`和`std::flex_barrier`。本例中使用`std::flex_barrier`的名称有点迷，例如：`std::flex_barrier`被称为`notifying_barrier`。所以我把名字改成`flex_barrier`，会更容易理解一些。此外，代表线程数量的`n_threads`没有初始化，我把它初始化为NTASKS。

深入研究`std::flex_barrier`和完成阶段之前，这里给出一个简短的示例，演示`std::barrier`的用法。

**std::barrier**

```cpp
void DoWork(){
  Tasks& tasks;
  int n_threads{NTASKS};
  vector<thread*> workes;

  barrier task_barrier(n_threads);

  for (int i = 0; i < n_threads; ++i){
    workers.push_back(new thread([&]{
      bool active = ture;
      while(active){
        Task task = tasks.get();
        // perform task
        ...
        task_barrier.arrive_and_wait();
      }
    });
  }
  // Read each stage of the task until all stages are complete.
  while(!finished()){
    GetNextStage(tasks);
  }
}
```

第6行中的`barrier`用于协调多个执行线程，线程的数量是`n_threads`(第3行)，每个线程通过`tasks.get()`获取(第12行中)任务，执行该任务并阻塞(第15行)，直到所有线程完成其任务为止。之后，在第12行接受一个新任务，`active`在第11行返回true。

与`std::barrier`不同，`std::flex_barrier`多一个构造函数。

## std::flex\_barrier

此构造函数接受在完成阶段调用可调用单元。可调用单元必须返回一个数字，使用这个数字设置计数器的值，返回-1意味着计数器在下一次迭代中保持相同的计数器值，而小于-1的数字是不允许的。

完成阶段会执行以下步骤:

1. 阻塞全部线程
2. 任意个线程解除阻塞，并执行可调用单元。
3. 如果完成阶段已经完成，那么所有线程都将解除阻塞。

下面的段代码展示了`std::flex_barrier`的用法

```cpp
void DoWork(){
  Tasks& tasks;
  int initial_threads;
  int n_threads{NTASKS};
  atomic<int> current_threads(initial_threads);
  vector<thread*> workers;

  // Create a flex_barrier, and set a lambda that will be
  // invoked every time the barrier counts down. If one or more
  // active threads have completed, reduce the number of threads.
  std::function rf = [&]{return current_threads;};
  flex_barrier task_barrier(n_threads, rf);

  for (int i = 0; i < n_threads; ++i){
   workers.push_back(new thread([&]{
     bool active = true;
     while(active) {
       Task task = tasks.get();
       // perform task
          ...
       if (finished(task)){
         current_threads--;
         active = false;
       }
       task_barrier.arrive_and_wait();
     }     
    }))； 
  }

  // Read each stage of the task until all stages are cpmplete.
  while(!finished()){
    GetNextStage(tasks);
  }
}
```

这个例子采用了与`std::barrier`类似的策略，不同的是这次`std::flex_barrier`计数器是在运行时进行调整，所以`std::flex_barrier task_barrier`在第11行获得一个Lambda函数。这个Lambda函数通过引用获取变量current\_thread：`[&] { return current_threads; }`。变量在第21行进行递减，如果线程完成了任务，则将`active`设置为false。因此，计数器在完成阶段是递减的。

与`std::barrier`或`std::latch`相比，`std::flex_barrier`可以增加计数器。

可以在cppreference.com上阅读关于[std::latch](http://en.cppreference.com/w/cpp/experimental/latch)、[std::barrier](http://en.cppreference.com/w/cpp/experimental/barrier)、[std::flex\_barrier](http://en.cppreference.com/w/cpp/experimental/flex_barrier)的更多细节。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/concurrency-with-modern-c/xiang-xi-jie-shao/5.0-chinese/5.5-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
