[Ferramenta de simultaneidade do C++ 20 std::barrier] Programação simultânea mestre: compreensão aprofundada do std::barrier do C++


1. Introdução

1.1. A definição e função de std::barrier

Na programação simultânea (Programação Concorrente), muitas vezes precisamos coordenar a ordem de execução de vários threads (Threads). Este é o conceito de Sincronização. C++20 apresenta um novo recurso de sincronização chamado std::barrier(barreira). std::barrierÉ uma primitiva de sincronização que permite que vários threads esperem uns pelos outros em um ponto de sincronização específico (Ponto de Sincronização). Em outras palavras, std::barrierum grupo de threads pode ser bloqueado até que todos os threads tenham atingido um ponto de sincronização.

Na comunicação falada, podemos descrever std::barriera função assim: "O std::barrier é um primitivo de sincronização que permite que vários threads esperem um pelo outro em um ponto de sincronização específico." (std::barrier é um primitivo de sincronização, é Multiple os threads podem esperar um pelo outro em um ponto de sincronização específico.)

1.2. A importância de std::barrier na programação concorrente

Na programação concorrente, std::barriera importância disso é evidente. Ele nos fornece uma maneira simples e eficiente de coordenar a ordem de execução de vários threads. Isso é especialmente importante ao lidar com problemas de multi-threading, por exemplo, pode ser útil quando precisamos esperar que todos os threads concluam uma determinada tarefa antes de prosseguir para a próxima etapa std::barrier.

Por exemplo, suponha que temos um programa simultâneo que consiste em vários threads, cada um dos quais precisa fazer algum cálculo. Queremos que todos os threads concluam seus cálculos antes de prosseguir para a próxima etapa. Este é std::barrierum cenário de aplicativo típico.

Na comunicação falada, podemos descrever std::barriera importância da programação simultânea desta forma: "Na programação simultânea, o std::barrier é crucial, pois fornece uma maneira simples e eficaz de coordenar a ordem de execução de vários threads. É especialmente importante quando lidando com problemas de multi-threading onde precisamos prosseguir para a próxima operação somente depois que todos os threads concluírem uma determinada tarefa." (Na programação simultânea, a importância de std::barrier é evidente. maneira eficaz de coordenar a ordem de execução de vários threads. Isso é especialmente importante ao lidar com problemas de multithreading. Por exemplo, quando precisamos concluir uma tarefa em todos os threads antes de prosseguir para a próxima etapa, a barreira std:: será útil. )

Capítulo 2: O princípio de std::barrier

2.1 O mecanismo interno do std::barrier

std::barrier é uma nova primitiva de sincronização introduzida pelo C++20, que permite que vários threads esperem em um ponto de sincronização específico até que todos os threads tenham alcançado esse ponto e, então, todos os threads podem continuar a executar. Esse recurso é muito importante na programação simultânea porque garante que todos os threads tenham concluído o trabalho necessário antes de continuar a execução.

Internamente, std::barrier usa um contador para controlar o número de threads que atingiram um ponto de sincronização. Quando um thread atinge um ponto de sincronização, ele chama a função de membro std::barrier reach(), que decrementa o contador. Quando o contador chega a zero, todos os threads em espera são liberados e a execução pode continuar.

Aqui está um exemplo simples de uso de std::barrier:

#include <barrier>
#include <thread>
#include <vector>

std::barrier b(3); // 创建一个barrier,需要3个线程到达同步点

void worker() {
    
    
    // ... do some work ...

    b.arrive_and_wait(); // 到达同步点并等待

    // ... continue with other work ...
}

int main() {
    
    
    std::vector<std::thread> threads;
    for (int i = 0; i < 3; ++i) {
    
    
        threads.emplace_back(worker);
    }

    for (auto& t : threads) {
    
    
        t.join();
    }

    return 0;
}

Neste exemplo, criamos uma barreira que requer 3 threads para atingir um ponto de sincronização. Em seguida, criamos 3 threads, cada uma das quais, após realizar algum trabalho, atinge um ponto de sincronização e espera. Quando todos os threads atingirem o ponto de sincronização, eles poderão continuar a execução.

2.2 Como usar std::barrier para sincronização

std::barrier fornece várias funções de membro para sincronização:

  • Arrival(): Indica que uma thread atingiu o ponto de sincronização. Isso diminui o contador interno. Se o contador chegar a zero, todos os threads em espera serão liberados.
  • wait (): Faz um thread esperar no ponto de sincronização até que todos os threads alcancem o ponto de sincronização.
  • reach_and_wait(): Esta é uma combinação de reach() e wait(). Ele indica que um thread alcançou o ponto de sincronização e espera no ponto de sincronização até que todos os threads tenham alcançado o ponto de sincronização.
  • reach_and_drop(): Isso indica que um thread atingiu o ponto de sincronização e não precisa mais ser sincronizado. Isso diminui o contador interno e diminui a contagem total de threads da barreira em um.

Aqui está um exemplo de sincronização usando std::barrier:

#include <barrier>
#include <thread>
#include <vector>

std::barrier b(3); // 创建一个barrier,需要3个线程到达同步点

void worker(int id) {
    
    
    // ... do some work ...

    if (id == 2) {
    
    
        b.arrive_and_drop(); // 线程2到达同步点,并且不再需要同步
    } else {
    
    
        b.arrive_and_wait(); // 其他线程到达同步点并等待
    }

    // ... continue with other work ...
}

int main() {
    
    
    std::vector<std::thread> threads;
    for (int i = 0; i < 3; ++i) {
    
    
        threads.emplace_back(worker, i);
    }

    for (auto& t : threads) {
    
    
        t.join();
    }

    return 0;
}

Neste exemplo, criamos uma barreira que requer 3 threads para atingir um ponto de sincronização. Em seguida, criamos 3 threads, cada uma das quais atinge um ponto de sincronização após ter feito algum trabalho. Depois que o thread 2 atinge o ponto de sincronização, ele indica que não precisa mais sincronizar e, portanto, chama reach_and_drop(). Depois que outras threads atingirem o ponto de sincronização, elas chamarão reach_and_wait() para esperar. Quando todos os threads atingirem o ponto de sincronização, eles poderão continuar a execução.

Neste exemplo, podemos ver a flexibilidade de std::barrier. Ele pode acomodar um número dinâmico de threads, o que é muito importante na programação concorrente.

Na comunicação falada, podemos descrever o princípio de funcionamento do std::barrier assim: "std::barrier funciona mantendo uma contagem do número de threads que chegaram a um ponto de sincronização. Quando um thread chega ao ponto de sincronização, ele chama a função reach(), que diminui a contagem. Quando a contagem chega a zero, todos os threads em espera são liberados e podem continuar a execução." (std::barrier funciona mantendo o controle do número de threads que atingiram um ponto de sincronização . Quando um thread atinge o ponto de sincronização, ele chama a função reach(), que decrementa o contador. Quando o contador chega a zero, todos os threads em espera são liberados e a execução pode continuar.)

Nesta frase, "funciona por" é uma expressão comum usada para descrever como ou como algo funciona. "Manter uma contagem de" é uma expressão comum para descrever manter uma contagem de. "Chega em" é uma expressão comum para descrever a chegada a um lugar ou estado. "chama a função" é uma expressão comum para descrever a chamada de uma função. "diminui a contagem" é uma expressão comum para descrever a diminuição de uma quantidade ou contagem. "chega a zero" é uma expressão comum para descrever uma quantidade ou contagem que chega a zero. "são liberados" é uma expressão comum para descrever ser liberado ou liberado. "pode ​​continuar a execução" é uma expressão comum para descrever que a execução pode continuar ou prosseguir.

3. Comparação de std::barrier com outras ferramentas de sincronização

Na programação simultânea, temos uma variedade de ferramentas de sincronização que podem ser usadas, incluindo std::mutex (mutex), std::condition_variable (variável de condição), std::latch (trava de porta) e std::semaphore (semáforo) . Neste capítulo, compararemos std::barrier com essas ferramentas em profundidade.

3.1. Comparação de std::barrier e std::mutex

std::mutex (mutex) é uma ferramenta de sincronização usada para proteger recursos compartilhados, o que impede que vários threads acessem o mesmo recurso ao mesmo tempo. O std::barrier (barreira) é uma ferramenta de sincronização que pode sincronizar vários threads em um determinado ponto e não continuará até que todos os threads atinjam esse ponto.

Na comunicação falada em inglês, costumamos dizer "Um mutex é usado para proteger recursos compartilhados de acesso simultâneo" (mutex é usado para proteger recursos compartilhados de acesso simultâneo) e para std::barrier, diríamos "Uma barreira é usada para sincronizar vários threads em um determinado ponto" (a barreira é usada para sincronizar vários threads em um determinado ponto).

Em ambas as frases, usamos a frase "está acostumado a" para descrever a finalidade dessas duas ferramentas. No inglês americano, "is used to" é uma expressão comum usada para descrever o propósito ou a função de algo.

Aqui está um exemplo usando std::mutex e std::barrier:

std::mutex mtx;
std::barrier bar(2);

void thread_func() {
    
    
    std::lock_guard<std::mutex> lock(mtx);
    // 对共享资源进行操作
    // ...
    bar.arrive_and_wait();  // 等待其他线程
}

int main() {
    
    
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    t1.join();
    t2.join();
    return 0;
}

Neste exemplo, usamos std::mutex para proteger recursos compartilhados e std::barrier para sincronizar dois threads.

3.2. Comparação de std::barrier e std::condition_variable

std::condition_variable (variável de condição) é uma ferramenta de sincronização que permite que um thread aguarde uma condição específica até que outro thread o notifique. O std::barrier (barreira) é uma ferramenta de sincronização que pode sincronizar vários threads em um determinado ponto e não continuará até que todos os threads atinjam esse ponto.

Na comunicação falada em inglês, costumamos dizer "Uma variável de condição é usada para fazer uma thread esperar sob certas condições" (variáveis ​​de condição são usadas para fazer as threads esperarem sob certas condições) e para std::barrier, diríamos "Uma barreira é usado para sincronizar vários threads em um determinado ponto" (a barreira é usada para sincronizar vários threads em um determinado ponto).

Em ambas as frases, usamos a frase "está acostumado a" para descrever a finalidade dessas duas ferramentas. No inglês americano, "is used to" é uma expressão comum usada para descrever o propósito ou a função de algo.

Aqui está um exemplo usando std::condition_variable e std::barrier:

std::mutex mtx;
std::condition_variable cv;
bool ready = false;
std::barrier bar(2);

void thread_func() {
    
    
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{
    
     return ready; });  // 等待条件满足
    // ...
    bar.arrive_and_wait();  // 等待其他线程
}

void set_ready() {
    
    
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
    cv.notify_all();  // 通知所有等待的线程
}

int main() {
    
    
    std::thread t1(thread_func);
    std::thread t2(set_ready);
    t1.join();
    t2.join();
    return 0;
}

Neste exemplo, usamos std::condition_variable para fazer um thread aguardar uma determinada condição e std::barrier para sincronizar dois threads.

3.3. Comparação de std::barrier com std::latch e std::semaphore

Ambos std::latch (trava da porta) e std::semaphore (semaphore) são novas ferramentas de sincronização introduzidas pelo C++20. std::latch permite que a contagem seja diminuída uma ou mais vezes até que a contagem chegue a zero, após o que todos os threads em espera podem passar. std::semaphore é uma ferramenta de sincronização mais geral, que pode limitar o número de threads que acessam um recurso ou conjunto de recursos ao mesmo tempo.

A principal diferença entre std::barrier e esses dois é que std::barrier é redefinido automaticamente toda vez que todos os threads atingem a barreira, enquanto std::latch não. Além disso, std::semaphore pode permitir a passagem de qualquer número de threads, enquanto std::barrier requer que todos os threads sejam alcançados.

在英语口语交流中,我们通常会说 “A latch allows a count to be decremented once or more times until it reaches zero, then all waiting threads can pass”(门闩允许计数一次或多次递减,直到达到零,然后所有等待的线程都可以通过),“A semaphore can limit the number of threads that can access a resource or set of resources at the same time”(信号量可以限制同时访问某一资源或资源集的线程数量),而对于std::barrier,我们会说 “A barrier is used to synchronize multiple threads at a certain point, and it resets automatically after all threads have arrived”(屏障用于在某一点同步多个线程,并在

所有线程都到达后自动重置)。

在这三个句子中,我们使用了 “allows”(允许)、“can limit”(可以限制)和 “is used to”(用于)这些短语来描述这三个工具的用途。在美式英语中,这些都是常见的表达方式,用于描述某物的用途或功能。

下面是一个使用std::latch、std::semaphore和std::barrier的示例:

std::latch lat(2);
std::counting_semaphore sem(1);
std::barrier bar(2);

void thread_func() {
    
    
    sem.acquire();  // 获取信号量
    // 对资源进行操作
    // ...
    sem.release();  // 释放信号量
    lat.count_down();  // 递减门闩计数
    bar.arrive_and_wait();  // 等待其他线程
}

int main() {
    
    
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    t1.join();
    t2.join();
    return 0;
}

在这个示例中,我们使用std::latch来同步两个线程,使用std::semaphore来限制对资源的并发访问,使用std::barrier来同步两个线程。

下表总结了std::barrier与其他同步工具的主要区别:

工具 用途 特性
std::mutex 保护共享资源免受并发访问 互斥
std::condition_variable 使线程在特定条件下等待 条件等待
std::latch 使线程在计数达到零时通过 一次性
std::semaphore 限制同时访问资源的线程数量 可重用
std::barrier 在某一点同步多个线程 自动重置

在理解这些同步工具的区别时,我们可以参考Bjarne Stroustrup的《C++ Programming Language》一书中的相关章节,其中详细介绍了这些工具的用途和使用方法。

4. std::barrier的实际应用

4.1. 使用std::barrier解决并发问题的实例

在并发编程中,我们经常会遇到需要多个线程(Threads)同时开始执行某项任务的情况。这时,我们可以使用std::barrier来实现这个需求。std::barrier(屏障)是C++20引入的一个新特性,它可以阻塞一组线程,直到所有线程都到达某个点,然后这些线程才会同时开始执行。

让我们来看一个例子。假设我们有一个需要多个线程同时开始的任务,我们可以使用std::barrier来实现这个需求。

#include <iostream>
#include <thread>
#include <barrier>

std::barrier b(3); // 创建一个屏障,需要3个线程到达

void task(const char* threadName) {
    
    
    std::cout << threadName << " is waiting\n";
    b.arrive_and_wait(); // 线程到达屏障并等待
    std::cout << threadName << " is processing\n";
}

int main() {
    
    
    std::thread t1(task, "Thread 1");
    std::thread t2(task, "Thread 2");
    std::thread t3(task, "Thread 3");

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

在这个例子中,我们创建了一个需要3个线程到达的屏障。每个线程在开始处理任务之前,都会先到达屏障并等待。只有当所有线程都到达屏障时,这些线程才会同时开始处理任务。

在口语交流中,我们可以这样描述这个过程:“We create a barrier that requires three threads to arrive. Each thread arrives at the barrier and waits before it starts processing the task. The threads only start processing the task when all of them have arrived at the barrier.”(我们创建了一个需要三个线程到达的屏障。每个线程在开始处理任务之前,都会先到达屏障并等待。只有当所有线程都到达屏障时,这些线程才会同时开始处理任务。)

4.2. std::barrier在Qt编程中的应用

在Qt编程中,我们也可以使用std::barrier来同步多个线程。例如,我们可以使用std::barrier来确保所有的GUI线程在开始更新界面之前,都已经完成了数据的加载。

#include <QThread>
#include <barrier>

std::barrier b(3); // 创建一个屏障,需要3个线程到达

class DataLoader : public QThread {
    
    
public:
    void run() override {
    
    
        // 加载数据...
        b.arrive_and_wait(); // 线程到达屏障并等待
    }
};

class GUIUpdater : public QThread {
    
    
public:
    void run() override {
    
    
        b.arrive_and_wait(); // 线程到达屏障并等待
        // 更新界面...
    }
};

int main() {
    
    
    DataLoader loader1, loader2;
    GUIUpdater updater;

    loader1.start();
    loader2.start();
    updater.start();

    loader1.wait();
    loader2.wait();
    updater.wait();

    return 0;
}

在这个例子中,我们有两个数据加载线程和一个GUI更新线程。我们使用std::barrier来确保所有的线程在开始更新界面之前,都已经完成了数据的加载。

在口语交流中,我们可以这样描述这个过程:“We have two data loading threads and one GUI updating thread. We use a barrier to ensure that all threads have finished loading data before they start updating the GUI.”(我们有两个数据加载线程和一个GUI更新线程。我们使用屏障来确保所有的线程在开始更新界面之前,都已经完成了数据的加载。)

在这两个例子中,我们可以看到std::barrier在实际应用中的强大功能。它可以帮助我们更容易地同步多个线程,使我们的代码更简洁,更易于理解和维护。

在接下来的章节中,我们将深入探讨std::barrier的内部工作原理,以及如何在实际编程中有效地使用std::barrier。

5. std::barrier的最佳实践和注意事项

5.1. 如何有效地使用std::barrier

在并发编程中,std::barrier(屏障)是一种非常有效的同步工具。它允许多个线程在一个预定的同步点上相互等待,直到所有线程都到达这个点,然后再继续执行。这种机制可以确保所有线程在进入下一阶段之前,都已经完成了当前阶段的工作。

在使用std::barrier时,我们需要注意以下几点:

  1. 初始化屏障的数量:在创建std::barrier时,我们需要指定一个参数,这个参数表示需要等待的线程数量。这个数量应该等于将要在屏障上等待的线程数量。如果这个数量设置得过大或过小,都可能导致程序的行为不符合预期。

  2. 避免在同一线程上多次等待std::barrier的设计是为了让多个线程在同一点上相互等待,而不是让同一线程在同一点上多次等待。如果在同一线程上多次调用std::barrier::wait(),可能会导致程序的行为不符合预期。

  3. 理解屏障的周期性std::barrier是周期性的,也就是说,当所有线程都到达屏障后,屏障会自动重置,可以被再次使用。这一点与std::latch(门闩)不同,std::latch在所有线程到达后就不能再被使用。

下面是一个使用std::barrier的示例:

#include <iostream>
#include <thread>
#include <barrier>

std::barrier b(2); // 创建一个需要等待两个线程的屏障

void print(const char* str) {
    
    
    std::cout << str << std::endl;
    b.arrive_and_wait(); // 到达屏障并等待
}

int main() {
    
    
    std::thread t1(print, "Hello from thread 1");
    std::thread t2(print, "Hello from thread 2");

    t1.join();
    t2.join();

    return 0;
}

在这个示例中,我们创建了两个线程,每个线程都会打印一条消息,然后到达屏障并等待。由于我们创建的屏障需要等待两个线程,所以当两个线程都到达屏障后,它们都会继续执行。

5.2. 避免常见的std::barrier使用错误

虽然std::barrier是一个强大的同步工具,但如果使用不当,也可能导致一些问题。下面我们来看一些常见的std::barrier使用错误,以及如何避免它们。

  1. 错误的屏障数量:如前所述,创建std::barrier时需要指定一个参数,表示需要等待的线程数量。如果这个数量设置得过大,那么可能会有一些线程永远无法到达屏障,导致其他线程在屏障上无限等待。如果这个数量设置得过小,那么可能会有一些线程在其他线程还没有到达屏障时就已经继续执行,这可能会导致数据竞争或其他并发问题。

  2. 在同一线程上多次等待:如前所述,std::barrier的设计是为了让多个线程在同一点上相互等待,而不是让同一线程在同一点上多次等待。如果在同一线程上多次调用std::barrier::wait(),可能会导致程序的行为不符合预期。

  3. 忽视屏障的周期性std::barrier是周期性的,也就是说,当所有线程都到达屏障后,屏障会自动重置,可以被再次使用。如果忽视了这一点,可能会导致程序的行为不符合预期。

为了避免这些错误,我们需要确保理解std::barrier的工作原理,并在使用时遵循上述的最佳实践。

在下一章节中,我们将探讨std::barrier在未来C++版本中的可能改进,以及它在并发编程中的未来角色。

6. std::barrier的未来展望

6.1. std::barrier在未来C++版本中的可能改进

在C++20中,std::barrier(屏障)已经成为了并发编程的重要工具。然而,C++的发展从未停止过,我们可以预期在未来的C++版本中,std::barrier可能会有一些改进。

首先,我们可能会看到对std::barrier的性能优化。这可能包括减少内存占用,提高同步效率等。例如,我们可能会看到一种新的std::barrier实现,它使用更少的内存,但仍然能够提供相同的同步功能。

其次,std::barrier可能会增加更多的功能。例如,它可能会支持超时,这样如果一组线程在指定的时间内没有达到屏障,那么屏障就会自动解除。这将使得std::barrier在处理一些需要时间限制的并发问题时更加方便。

最后,std::barrier可能会有更好的错误处理机制。目前,如果一个线程在达到屏障之前异常终止,那么其他等待在屏障处的线程可能会永远等待下去。未来的std::barrier可能会提供一种机制,使得在这种情况下,其他线程可以得到通知并继续执行。

6.2. std::barrier在并发编程中的未来角色

std::barrier在并发编程中的角色可能会随着并发编程模型的发展而发展。随着多核处理器的普及和并发编程的重要性日益增加,我们可能会看到std::barrier在更多的场景中被使用。

例如,随着异步编程模型的发展,我们可能会看到std::barrier在异步编程中扮演更重要的角色。在异步编程模型中,多个任务可能会在不同的时间点开始和结束,这使得同步变得更加复杂。std::barrier可以帮助解决这种复杂性,使得异步任务可以在特定的时间点进行同步。

此外,std::barrier可能会在分布式系统中扮演更重要的角色。在分布式系统中,多个节点需要进行协调以完成一项任务。std::barrier可以帮助实现这种协调,使得所有节点可以在同一时间点开始执行下一步操作。

总的来说,std::barrier在并发编程中的未来角色将取决于并发编程模型的发展和并发问题的复杂性。我们可以期待std::barrier在未来的并发编程中扮演更重要的角色。

注意:以上内容是基于对C++和并发编程的深入理解和预测,实际的发展可能会有所不同。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。


阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页
在这里插入图片描述

Acho que você gosta

Origin blog.csdn.net/qq_21438461/article/details/131851158
Recomendado
Clasificación