C++中的多线程编程(附带实例)
在 C++11 之前,如果要进行多线程开发,则需要调用系统或者第三方提供的 API,例如在 Linux 系统下需要调用 pthread 的一组 API,在 Windows 系统下则需要调用 CreateThread() 的一组 API。这种操作导致代码需要在不同的操作系统上各个独立写一遍,造成代码量增加,从而使模块的通用性降低。
从 C++11 开始引入了对多线程编程的支持,包括头文件和相关的类和函数,使在 C++ 中使用多线程变得更加简便和安全。
C++11 中的多线程支持是由多个模块提供的,C++11 多线程的一些主要模块如下:
使用多线程可以更加有效地利用硬件的计算资源,在很多情况下多线程开发是不可或缺的,在一些情况下不使用多线程则无法满足实际的需要,但是多线程编程开发的难度和单线程开发相比会明显增加。例如需要注意线程的同步和互斥,以避免出现竞态条件和数据访问冲突等问题。
在 C++11 中提供了一组工具和机制,从而能够简化多线程编程,并使多线程程序更加易于理解和维护。尽管如此,在实际开发中仍然需要特别小心地处理好多线程数据的竞争关系,以避免数据竞争造成的计算错误和死锁。
下面是一个示例,展示了如何使用 std::thread 创建两个线程并执行并行任务,代码如下:
运行上述程序,输出的结果如下:
下面示例展示了如何使用 std::future 获取异步任务的返回值,代码如下:
提交任务后主线程可以执行其他操作,而不需要等待异步任务完成,但是在想要获取异步任务的返回值之前需要调用 std::future 的 wait() 方法等待异步任务完成。
最后,通过调用 std::future 的 get() 方法等待异步任务执行完成并获取它的返回值。运行上述程序,结果如下:
互斥量是一种同步原语,用于保护共享数据,只允许一个线程在任意时刻访问共享数据。互斥量通过 lock() 和 unlock() 方法实现加锁和解锁操作。
下面是一个示例,展示了如何在 std::thread 中使用互斥量进行数据同步,代码如下:
在主函数中创建了两个线程 t1 和 t2,并分别调用 increment() 函数。最后等待两个线程执行完成,并打印 counter 的值。运行上述程序,结果如下:
std::condition_variable 需要配合 std::mutex 一起使用以确保线程安全。当线程等待某个条件时,它会释放已经持有的锁,进入等待状态。当另一个线程满足了条件并进行通知时,等待的线程被唤醒,重新获取锁,并继续执行。
下面的示例演示了如何使用这个模块进行线程同步控制,代码如下:
消费者线程首先会加锁,并调用 cv.wait() 以等待条件满足。如果条件不满足,则消费者线程会释放锁,并进入等待状态。一旦生产者线程通知条件满足,消费者线程会重新获取锁,并继续执行。
在本例中运行结果如下:
从 C++11 开始引入了对多线程编程的支持,包括头文件和相关的类和函数,使在 C++ 中使用多线程变得更加简便和安全。
C++11 中的多线程支持是由多个模块提供的,C++11 多线程的一些主要模块如下:
模块 | 描 述 |
---|---|
线程类 | 引入了 std::thread 类,可以用于创建和管理线程。通过 std::thread 类可以创建新的线程,并指定线程要执行的函数或函数对象,还可以将参数传递给线程函数。std::thread 类提供了一些控制线程的方法,例如 join() 方法可以等待一个线程执行完成,detach() 方法可以将一个线程分离,使线程的执行与主线程独立。 |
异步类 | 引入了 std::future 和 std::async 两个异步处理类。提供了简单的接口用来处理异步任务和异步任务的返回值。 |
并发原语 | 提供了一些原子操作和锁机制,以便实现线程之间的同步和互斥,其中 std::atomic 类型用于原子操作,std::mutex、std::lock_guard 等类可以用于实现互斥锁。 |
条件变量 | 引入了 std::condition_variable 类,以便实现多线程之间的通信和同步。条件变量可以结合互斥锁一起使用,实现线程的等待和唤醒。 |
并行算法 | 引入了一些支持并行执行的算法,例如 std::for_each、std::transform 等。这些算法可以将一个任务并行化执行,从而提高程序的性能。 |
使用多线程可以更加有效地利用硬件的计算资源,在很多情况下多线程开发是不可或缺的,在一些情况下不使用多线程则无法满足实际的需要,但是多线程编程开发的难度和单线程开发相比会明显增加。例如需要注意线程的同步和互斥,以避免出现竞态条件和数据访问冲突等问题。
在 C++11 中提供了一组工具和机制,从而能够简化多线程编程,并使多线程程序更加易于理解和维护。尽管如此,在实际开发中仍然需要特别小心地处理好多线程数据的竞争关系,以避免数据竞争造成的计算错误和死锁。
C++ std::thread
std::thread 是 C++11 提供的多线程库中的一个类,用于创建和管理线程。下面是一个示例,展示了如何使用 std::thread 创建两个线程并执行并行任务,代码如下:
#include <iostream> #include <thread> // 线程函数 1 void threadFunc1() { std::cout << "Thread 1 running" << std::endl; // 执行一些任务... } // 线程函数 2 void threadFunc2(int numIterations) { std::cout << "Thread 2 running" << std::endl; for (int i = 0; i < numIterations; ++i) { // 执行一些任务... } } int main() { // 创建线程 1,并执行 threadFunc1 函数 std::thread t1(threadFunc1); // 创建线程 2,并执行 threadFunc2 函数,传递参数 10 作为 numIterations std::thread t2(threadFunc2, 10); // 等待线程 1 和线程 2 执行完成 t1.join(); t2.join(); std::cout << "Main thread exiting" << std::endl; return 0; }在上面的示例中首先定义了两个线程函数 threadFunc1() 和 threadFunc2(),它们分别用于线程 1 和线程 2 的执行逻辑,然后在 main() 函数中使用 std::thread 类创建了两个线程 t1 和 t2,分别指定了要执行的函数和需要传递的参数。最后,通过调用 join() 方法,主线程会等待线程 1 和线程 2 执行完成后再退出。
运行上述程序,输出的结果如下:
Thread 1 running
Thread 2 running
Main thread exiting
C++ std::future和std::async
std::future 是 C++11 提供的一个类模板,用于访问异步任务的结果或异常。它可以用于获取异步任务的返回值,或者通过 wait() 方法等待其完成。下面示例展示了如何使用 std::future 获取异步任务的返回值,代码如下:
#include <iostream> #include <future> // 异步任务函数 int asyncFunc() { // 执行一些耗时的任务... return 42; } int main() { // 创建一个异步任务并执行 asyncFunc 函数 std::future<int> fut = std::async(std::launch::async, asyncFunc); // 执行其他的操作... // 等待异步任务执行完成,并获取结果 fut.wait(); int result = fut.get(); std::cout << "Async task completed with result: " << result << std::endl; return 0; }在上面的示例中首先定义了一个异步任务函数 asyncFunc(),它会执行一些耗时的任务并返回一个整数值。在 main() 函数中使用 std::async() 函数创建了一个异步任务,并传递了要执行的函数 asyncFunc()。std::launch::async 参数表示这个异步任务应该在一个新线程中执行。
提交任务后主线程可以执行其他操作,而不需要等待异步任务完成,但是在想要获取异步任务的返回值之前需要调用 std::future 的 wait() 方法等待异步任务完成。
最后,通过调用 std::future 的 get() 方法等待异步任务执行完成并获取它的返回值。运行上述程序,结果如下:
Async task completed with result:42
C++ std::mutex
当多个线程同时访问和修改共享数据时,可能会导致数据竞争和不确定的行为。为了确保线程之间的数据同步和避免竞态条件,可以使用互斥量(Mutex)和条件变量(Condition Variable)。互斥量是一种同步原语,用于保护共享数据,只允许一个线程在任意时刻访问共享数据。互斥量通过 lock() 和 unlock() 方法实现加锁和解锁操作。
下面是一个示例,展示了如何在 std::thread 中使用互斥量进行数据同步,代码如下:
#include <iostream> #include <thread> #include <mutex> // 互斥量,用于保护共享数据 std::mutex mtx; // 共享数据 int counter = 0; void increment() { std::lock_guard<std::mutex> lock(mtx); // 加锁 // 修改共享数据 counter++; // 执行其他操作... } // 解锁时,std::lock_guard的析构函数会自动调用 unlock() int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Counter value: " << counter << std::endl; return 0; }在上面的示例中定义了一个全局变量 counter 作为共享数据。在 increment() 函数中使用 std::lock_guard 来管理互斥量的锁定和解锁。锁定互斥量后可以安全地修改共享数据,确保在任意时刻只有一个线程可以访问共享数据。
在主函数中创建了两个线程 t1 和 t2,并分别调用 increment() 函数。最后等待两个线程执行完成,并打印 counter 的值。运行上述程序,结果如下:
Counter value:2
这表明两个线程安全地增加了 counter 的值,最终得到了正确的结果。C++ std::condition_variable
std::condition_variable 用于线程之间的等待和通知。它可以让一个线程等待,直到满足某个条件然后由另一个线程通知它继续执行。std::condition_variable 需要配合 std::mutex 一起使用以确保线程安全。当线程等待某个条件时,它会释放已经持有的锁,进入等待状态。当另一个线程满足了条件并进行通知时,等待的线程被唤醒,重新获取锁,并继续执行。
下面的示例演示了如何使用这个模块进行线程同步控制,代码如下:
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; // 互斥量,用于保护共享数据 std::condition_variable cv; // 条件变量 bool is_consume_ready = false; // 消费者准备好的标志 int data = 0; // 共享数据 void producer() { std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟生产过程 std::lock_guard<std::mutex> lock(mtx); // 加锁 // 更新共享数据 data = 42; is_consume_ready = true; cv.notify_one(); // 通知等待的消费者线程 } void consumer() { std::unique_lock<std::mutex> lock(mtx); // 加锁 cv.wait(lock, []{ return is_consume_ready; }); // 等待条件满足 // 执行消费操作 std::cout << "Consumed data: " << data << std::endl; } int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); return 0; }在上面的示例中定义了一个生产者线程和一个消费者线程。生产者线程会在一段时间后生成数据,并将 is_consume_ready 设置为 true,表示消费者准备好了,然后生产者线程通过 cv.notify_one() 通知等待的消费者线程。
消费者线程首先会加锁,并调用 cv.wait() 以等待条件满足。如果条件不满足,则消费者线程会释放锁,并进入等待状态。一旦生产者线程通知条件满足,消费者线程会重新获取锁,并继续执行。
在本例中运行结果如下:
Consumed data:42
这表明消费者线程成功地获取了生产者线程生成的数据。通过使用 std::condition_variable 实现了线程之间的同步和等待/通知机制,从而确保数据正确和安全访问。