[Herramienta de concurrencia C++ 20 std::barrier] Programación concurrente maestra: comprensión profunda de std::barrier de C++


1. Introducción

1.1 La definición y función de std::barrier

En la programación concurrente (Programación concurrente), a menudo necesitamos coordinar el orden de ejecución de múltiples hilos (Threads). Este es el concepto de Sincronización. C++20 introduce una nueva función de sincronización llamada std::barrier(barrera). std::barrierEs una primitiva de sincronización que permite que varios subprocesos se esperen entre sí en un punto de sincronización específico (Punto de sincronización). En otras palabras, std::barrierse puede bloquear un grupo de subprocesos hasta que todos los subprocesos hayan alcanzado un punto de sincronización.

En la comunicación hablada, podemos describir std::barrierla función de esta manera: "Std::barrier es una primitiva de sincronización que permite que varios subprocesos se esperen entre sí en un punto de sincronización específico". los subprocesos pueden esperar el uno al otro en un punto de sincronización específico).

1.2 La importancia de std::barrier en la programación concurrente

En la programación concurrente, std::barrierla importancia de la misma es evidente. Nos proporciona una manera simple y eficiente de coordinar el orden de ejecución de múltiples hilos. Esto es especialmente importante cuando se trata de problemas de subprocesos múltiples, por ejemplo, puede ser útil cuando necesitamos esperar a que todos los subprocesos completen una determinada tarea antes de continuar con el siguiente paso std::barrier.

Por ejemplo, supongamos que tenemos un programa concurrente que consta de varios subprocesos, cada uno de los cuales necesita realizar algún cálculo. Queremos que todos los subprocesos completen sus cálculos antes de continuar con el siguiente paso. Este es std::barrierun escenario de aplicación típico.

En la comunicación hablada, podemos describir std::barrierla importancia de la programación concurrente de esta manera: "En la programación concurrente, la barrera std::barrier es crucial ya que proporciona una manera simple y efectiva de coordinar el orden de ejecución de múltiples subprocesos. Es especialmente importante cuando lidiando con problemas de subprocesos múltiples en los que necesitamos pasar a la siguiente operación solo después de que todos los subprocesos hayan completado una determinada tarea". (En la programación concurrente, la importancia de std::barrier es evidente. Nos proporciona un una forma eficaz de coordinar el orden de ejecución de múltiples subprocesos. Esto es especialmente importante cuando se trata de problemas de subprocesos múltiples. Por ejemplo, cuando necesitamos completar una tarea en todos los subprocesos antes de continuar con el siguiente paso, std:: barrier será útil. )

Capítulo 2: El principio de std::barrier

2.1 El mecanismo interno de std::barrier

std::barrier es una nueva primitiva de sincronización introducida por C++20, que permite que múltiples subprocesos esperen en un punto de sincronización específico hasta que todos los subprocesos hayan llegado a este punto, y luego todos los subprocesos pueden continuar ejecutándose. Esta característica es muy importante en la programación concurrente porque asegura que todos los subprocesos hayan completado el trabajo necesario antes de continuar con la ejecución.

Internamente, std::barrier usa un contador para realizar un seguimiento de la cantidad de subprocesos que han alcanzado un punto de sincronización. Cuando un subproceso alcanza un punto de sincronización, llama a la función miembro std::barrierarribe(), que disminuye el contador. Cuando el contador llega a cero, todos los subprocesos en espera se liberan y la ejecución puede continuar.

Aquí hay un ejemplo de uso simple 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;
}

En este ejemplo, creamos una barrera que requiere 3 subprocesos para alcanzar un punto de sincronización. Luego creamos 3 subprocesos, cada uno de los cuales, después de hacer algo de trabajo, llega a un punto de sincronización y espera. Cuando todos los subprocesos han alcanzado el punto de sincronización, pueden continuar ejecutándose.

2.2 Cómo usar std::barrier para sincronización

std::barrier proporciona varias funciones miembro para la sincronización:

  • llegar (): indica que un hilo ha llegado al punto de sincronización. Esto decrementa el contador interno. Si el contador llega a cero, se liberan todos los subprocesos en espera.
  • wait (): hace que un subproceso espere en el punto de sincronización hasta que todos los subprocesos alcancen el punto de sincronización.
  • llegar_y_esperar(): Esta es una combinación de llegar() y esperar(). Indica que un subproceso ha alcanzado el punto de sincronización y espera en el punto de sincronización hasta que todos los subprocesos hayan llegado al punto de sincronización.
  • arribe_and_drop(): Esto indica que un subproceso ha alcanzado el punto de sincronización y ya no necesita sincronizarse. Esto decrementa el contador interno y decrementa el número total de subprocesos de la barrera en uno.

Aquí hay un ejemplo de sincronización 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;
}

En este ejemplo, creamos una barrera que requiere 3 subprocesos para alcanzar un punto de sincronización. Luego creamos 3 subprocesos, cada uno de los cuales alcanza un punto de sincronización después de haber realizado algún trabajo. Después de que el subproceso 2 alcanza el punto de sincronización, indica que ya no necesita sincronizarse, por lo que llama a la función de llegada y caída(). Después de que otros subprocesos alcancen el punto de sincronización, llamarán a la función de llegada y espera () para esperar. Cuando todos los subprocesos han alcanzado el punto de sincronización, pueden continuar ejecutándose.

En este ejemplo, podemos ver la flexibilidad de std::barrier. Puede acomodar un número dinámico de subprocesos, lo cual es muy importante en la programación concurrente.

En la comunicación hablada, podemos describir el principio de funcionamiento de std::barrier de esta manera: "std::barrier funciona manteniendo un recuento del número de subprocesos que han llegado a un punto de sincronización. Cuando un subproceso llega al punto de sincronización, llama a la función de llegada (), que disminuye el conteo. Cuando el conteo llega a cero, todos los subprocesos en espera se liberan y pueden continuar la ejecución". Cuando un subproceso alcanza Cuando se alcanza el punto de sincronización, llama a la función de llegada (), que disminuye el contador. Cuando el contador llega a cero, todos los subprocesos en espera se liberan y la ejecución puede continuar).

En esta oración, "funciona por" es una expresión común que se usa para describir cómo o cómo funciona algo. "Llevar la cuenta de" es una expresión común para describir llevar la cuenta de. "Llega a" es una expresión común para describir la llegada a un lugar o estado. "llama a la función" es una expresión común para describir la llamada a una función. "disminuye la cuenta" es una expresión común para describir la disminución de una cantidad o cuenta. "llega a cero" es una expresión común para describir una cantidad o recuento que llega a cero. "son liberados" es una expresión común para describir ser liberado o puesto en libertad. "puede continuar la ejecución" es una expresión común para describir que la ejecución puede continuar o proceder.

3. Comparación de std::barrier con otras herramientas de sincronización

En la programación concurrente, tenemos una variedad de herramientas de sincronización que se pueden usar, incluidas std::mutex (mutex), std::condition_variable (variable de condición), std::latch (pestillo de puerta) y std::semaphore (semáforo) . En este capítulo, compararemos std::barrier con estas herramientas en profundidad.

3.1 Comparación de std::barrier y std::mutex

std::mutex (mutex) es una herramienta de sincronización que se utiliza para proteger los recursos compartidos, lo que evita que varios subprocesos accedan al mismo recurso al mismo tiempo. El std::barrier (barrera) es una herramienta de sincronización que puede sincronizar múltiples subprocesos en un punto determinado y no continuará hasta que todos los subprocesos lleguen a este punto.

En la comunicación hablada en inglés, generalmente decimos "Se usa una exclusión mutua para proteger los recursos compartidos del acceso simultáneo" (la exclusión mutua se usa para proteger los recursos compartidos del acceso simultáneo), y para std::barrier, diríamos "Se usa una barrera para sincronizar varios subprocesos en un punto determinado" (la barrera se utiliza para sincronizar varios subprocesos en un punto determinado).

En ambas oraciones, usamos la frase "se usa para" para describir el propósito de estas dos herramientas. En inglés americano, "is used to" es una expresión común que se usa para describir el propósito o la función de algo.

Aquí hay un ejemplo usando std::mutex y 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;
}

En este ejemplo, usamos std::mutex para proteger los recursos compartidos y std::barrier para sincronizar dos subprocesos.

3.2 Comparación de std::barrier y std::condition_variable

std::condition_variable (variable de condición) es una herramienta de sincronización que permite que un subproceso espere una condición específica hasta que otro subproceso lo notifique. El std::barrier (barrera) es una herramienta de sincronización que puede sincronizar múltiples subprocesos en un punto determinado y no continuará hasta que todos los subprocesos lleguen a este punto.

En la comunicación en inglés hablado, generalmente decimos "Se usa una variable de condición para hacer que un subproceso espere bajo ciertas condiciones" (las variables de condición se usan para hacer que los subprocesos esperen bajo ciertas condiciones), y para std::barrier, diríamos "A barrier se utiliza para sincronizar varios subprocesos en un punto determinado" (la barrera se utiliza para sincronizar varios subprocesos en un punto determinado).

En ambas oraciones, usamos la frase "se usa para" para describir el propósito de estas dos herramientas. En inglés americano, "is used to" es una expresión común que se usa para describir el propósito o la función de algo.

Aquí hay un ejemplo usando std::condition_variable y 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;
}

En este ejemplo, usamos std::condition_variable para hacer que un subproceso espere en una determinada condición y std::barrier para sincronizar dos subprocesos.

3.3 Comparación de std::barrier con std::latch y std::semaphore

Tanto std::latch (pestillo de la puerta) como std::semaphore (semáforo) son nuevas herramientas de sincronización introducidas por C++20. std::latch permite que el conteo disminuya una o más veces hasta que el conteo llegue a cero, después de lo cual todos los subprocesos en espera pueden pasar. std::semaphore es una herramienta de sincronización más general, que puede limitar la cantidad de subprocesos que acceden a un recurso o conjunto de recursos al mismo tiempo.

La principal diferencia entre std::barrier y estos dos es que std::barrier se restablece automáticamente cada vez que todos los subprocesos alcanzan la barrera, mientras que std::latch no. Además, std::semaphore puede permitir el paso de cualquier cantidad de subprocesos, mientras que std::barrier requiere que todos los subprocesos lleguen.

在英语口语交流中,我们通常会说 “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主页
在这里插入图片描述

Supongo que te gusta

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