网站首页 > 基础教程 正文
1 C++并发基础
1.1 线程与进程基础
在C++中,线程和进程是实现并发执行的基本单位。理解它们之间的区别和如何使用它们是并发编程的基础。
1.1.1 进程
进程是程序的一次执行过程,每个进程都有独立的内存空间。这意味着进程间的通信需要通过操作系统提供的机制,如管道、套接字等。
1.1.2 线程
线程是进程内的一个执行单元,多个线程共享同一进程的内存空间。这使得线程间的通信比进程间通信更高效,但也增加了数据一致性的复杂性。
1.1.3 示例:创建线程
#include <iostream>
#include <thread>
// 线程函数
void threadFunction()
{
std::cout << "线程函数正在执行..." << std::endl;
}
int main()
{
// 创建线程
std::thread t(threadFunction);
// 等待线程结束
t.join();
std::cout << "主线程继续执行..." << std::endl;
return 0;
}
此示例展示了如何在C++中创建一个线程并执行一个函数。std::thread类用于创建线程,t.join()用于等待线程结束。
1.2 C++11线程库介绍
C++11标准引入了<thread>库,提供了线程支持,使得C++程序员可以更方便地进行并发编程。<thread>库包括std::thread类,用于创建和管理线程,以及线程同步的工具,如std::mutex和std::condition_variable。
1.2.1 std::thread类
std::thread类是C++11线程库的核心,它提供了创建、启动、加入和分离线程的方法。
1.2.2 std::mutex和std::condition_variable
std::mutex用于保护共享资源,防止多个线程同时访问。std::condition_variable用于线程间的同步,可以实现线程的等待和唤醒。
1.2.3 示例:使用std::mutex保护共享资源
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 创建互斥锁
int sharedData = 0;
void incrementSharedData()
{
std::lock_guard<std::mutex> lock(mtx); // 自动加锁和解锁
for(int i = 0; i < 100000; ++i)
{
++sharedData;
}
}
int main()
{
std::thread t1(incrementSharedData);
std::thread t2(incrementSharedData);
t1.join();
t2.join();
std::cout << "共享数据的值: " << sharedData << std::endl;
return 0;
}
在这个示例中,两个线程t1和t2同时尝试增加sharedData的值。使用std::mutex可以确保在任何时候只有一个线程可以访问sharedData,从而避免了数据竞争。
1.3 线程创建与管理
在C++中,线程的创建和管理主要通过std::thread类完成。创建线程时,可以传递一个函数或一个成员函数和对象给std::thread的构造函数。线程的管理包括线程的启动、等待、分离和销毁。
1.3.1 示例:创建和管理线程
#include <iostream>
#include <thread>
class MyClass
{
public:
void myFunction()
{
std::cout << "线程正在执行成员函数..." << std::endl;
}
};
int main()
{
MyClass obj;
// 创建线程并传递成员函数和对象
std::thread t(&MyClass::myFunction, &obj);
// 等待线程结束
t.join();
std::cout << "主线程继续执行..." << std::endl;
return 0;
}
此示例展示了如何创建一个线程来执行类的成员函数。std::thread的构造函数接受一个指向成员函数的指针和一个指向对象的指针。
1.3.2 线程分离
线程分离意味着线程将独立于创建它的线程运行,即使创建线程已经结束,分离的线程也会继续运行直到完成。使用std::thread::detach()方法可以分离线程。
1.3.3 示例:分离线程
#include <iostream>
#include <thread>
void threadFunction()
{
std::cout << "线程正在执行..." << std::endl;
}
int main()
{
std::thread t(threadFunction);
// 分离线程
t.detach();
std::cout << "主线程继续执行,线程已分离..." << std::endl;
return 0;
}
在这个示例中,t.detach()方法被调用来分离线程。分离后,主线程继续执行,而线程t将独立运行直到完成。
通过以上介绍和示例,我们了解了C++中线程和进程的基础,以及如何使用C++11线程库来创建和管理线程。使用线程可以显著提高程序的性能和响应性,但同时也需要小心处理线程同步和数据一致性的问题。
2 线程同步技术
2.1 互斥量与锁
在多线程编程中,互斥量(Mutex)是一种常用的同步机制,用于保护共享资源不被多个线程同时访问,从而避免数据竞争和不一致。C++标准库提供了std::mutex类,可以用来实现互斥锁。
2.1.1 原理
互斥量通过锁定和解锁机制来控制对共享资源的访问。当一个线程试图访问被互斥量保护的资源时,它会尝试锁定互斥量。如果互斥量当前未被锁定,那么该线程将获得锁并可以访问资源。如果互斥量已经被另一个线程锁定,那么当前线程将被阻塞,直到锁被释放。
2.1.2 示例
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 创建互斥量
void print_block(int n, char c) {
// 锁定互斥量
std::lock_guard<std::mutex> lock(mtx);
for (int i = 0; i < n; ++i) {
std::cout << c;
}
std::cout << '\n';
}
int main() {
std::thread t1(print_block, 10000, 'A');
std::thread t2(print_block, 10000, 'B');
t1.join();
t2.join();
return 0;
}
在这个例子中,print_block函数使用std::lock_guard自动锁定和解锁互斥量,确保两个线程不会同时访问std::cout,从而避免输出混乱。
2.2 条件变量
条件变量(Condition Variables)用于线程间的通信,允许一个或多个线程等待某个条件成立,而另一个线程可以通知条件成立或广播给所有等待的线程。
2.2.1 原理
条件变量通常与互斥量一起使用。一个线程在访问共享资源时,先锁定互斥量,检查条件是否满足。如果不满足,线程会释放互斥量并等待条件变量。当另一个线程修改了共享资源并满足了条件时,它会锁定互斥量,修改资源,并通过条件变量通知等待的线程。等待的线程被唤醒后,会重新锁定互斥量并检查条件。
2.2.2 示例
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void wait_for_ready() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{return ready;}); // 等待直到ready为true
std::cout << "Ready is true, thread continues.\n";
}
void set_ready() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知一个等待的线程
}
int main() {
std::thread t1(wait_for_ready);
std::thread t2(set_ready);
t1.join();
t2.join();
return 0;
}
在这个例子中,wait_for_ready线程等待ready变量变为true,而set_ready线程在模拟耗时操作后设置ready为true并通知等待的线程。
2.3 原子操作
原子操作(Atomic Operations)是在多线程环境下,确保操作不会被其他线程中断的机制。C++标准库提供了std::atomic模板类,可以用来创建原子变量。
2.3.1 原理
原子操作在硬件级别上执行,确保了操作的完整性和一致性。例如,原子的读-修改-写操作可以保证在多线程环境下,变量的值不会被其他线程中途改变。
2.3.2 示例
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> counter(0);
void increment_counter() {
for (int i = 0; i < 100000; ++i) {
++counter; // 原子操作
}
}
int main() {
std::thread t1(increment_counter);
std::thread t2(increment_counter);
t1.join();
t2.join();
std::cout << "Counter value: " << counter.load() << '\n';
return 0;
}
在这个例子中,两个线程同时对counter变量进行递增操作。由于counter是原子变量,递增操作是原子的,因此不会出现数据竞争,最终counter的值将是正确的(200000)。
3 并发数据结构与算法
3.1 线程安全的容器
在C++中,标准库提供了线程安全的容器,这些容器在多线程环境中可以安全地使用,无需额外的同步机制。其中,std::shared_mutex和std::shared_timed_mutex是用于保护数据的共享互斥锁,可以允许多个线程同时读取数据,但只允许一个线程写入数据。
3.1.1 示例:使用std::shared_mutex保护std::map
#include <map>
#include <shared_mutex>
#include <thread>
#include <iostream>
std::shared_mutex data_mutex;
std::map<int, int> data;
// 读取数据的函数
void read_data(int key) {
std::shared_lock<std::shared_mutex> lock(data_mutex);
auto it = data.find(key);
if (it != data.end()) {
std::cout << "读取到数据: " << it->second << std::endl;
} else {
std::cout << "未找到数据" << std::endl;
}
}
// 写入数据的函数
void write_data(int key, int value) {
std::unique_lock<std::shared_mutex> lock(data_mutex);
data[key] = value;
std::cout << "写入数据: " << key << " -> " << value << std::endl;
}
int main() {
// 写入数据
std::thread t1(write_data, 1, 100);
// 读取数据
std::thread t2(read_data, 1);
// 再次写入数据
std::thread t3(write_data, 1, 200);
t1.join();
t2.join();
t3.join();
return 0;
}
在这个例子中,data_mutex是一个std::shared_mutex,它被用于保护data这个std::map。read_data函数使用std::shared_lock来读取数据,允许多个读线程同时访问。write_data函数使用std::unique_lock来写入数据,确保在写操作时没有其他线程读取或写入数据。
3.2 并行算法库
C++标准库中的并行算法库,主要通过<execution>头文件提供,允许在并行环境中使用标准算法,如std::sort、std::transform等。并行算法库利用多线程来加速算法的执行,特别是在处理大量数据时。
3.2.1 示例:使用并行算法库进行排序
#include <execution>
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> v = {10, 2, 8, 4, 7, 9, 3, 1, 6, 5};
// 使用并行策略进行排序
std::sort(std::execution::par, v.begin(), v.end());
// 输出排序后的结果
for (int i : v) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
在这个例子中,std::sort函数被调用时使用了std::execution::par策略,这意味着排序操作将在多个线程中并行执行。std::vector<int> v中的元素将被并行地排序。
3.3 无锁编程基础
无锁编程是一种避免使用锁来实现线程安全的技术,通过使用原子操作和内存模型来保证数据的一致性。在C++中,std::atomic类型和相关的原子操作提供了无锁编程的基础。
3.3.1 示例:使用std::atomic进行无锁计数
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter(0);
void increment_counter() {
for (int i = 0; i < 100000; ++i) {
++counter;
}
}
int main() {
std::thread t1(increment_counter);
std::thread t2(increment_counter);
t1.join();
t2.join();
std::cout << "计数器的值: " << counter.load() << std::endl;
return 0;
}
在这个例子中,counter是一个std::atomic<int>类型的原子变量。increment_counter函数在两个线程中被调用,每个线程都将counter的值增加100000次。由于counter是原子的,因此即使在多线程环境中,计数操作也是线程安全的,不会出现数据竞争。
无锁编程可以提高程序的性能,因为它避免了锁的开销,但同时也增加了编程的复杂性,需要对原子操作和内存模型有深入的理解。在实际应用中,应根据具体场景和性能需求来决定是否使用无锁编程。
4 高级并发模式
4.1 生产者-消费者模式
4.1.1 原理
生产者-消费者模式是一种经典的并发设计模式,用于解决多线程环境下的数据生产和消费问题。在这个模式中,生产者线程负责生成数据,而消费者线程负责处理这些数据。两者通过一个共享的缓冲区进行通信,生产者将数据放入缓冲区,消费者从缓冲区取出数据进行处理。这种模式可以有效地避免资源竞争和死锁,同时提高系统的整体性能。
4.1.2 内容
在C++中,实现生产者-消费者模式通常会使用std::queue作为共享缓冲区,并结合std::mutex和std::condition_variable来同步线程间的操作。生产者在数据准备好时通知消费者,而消费者在缓冲区为空时等待生产者生成数据。
4.1.2.1 示例代码
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable cv;
const int buffer_size = 10;
// 生产者函数
void producer() {
for (int i = 0; i < 100; ++i) {
std::unique_lock<std::mutex> lock(mtx);
while (buffer.size() == buffer_size) {
cv.wait(lock);
}
buffer.push(i);
std::cout << "生产者生产数据: " << i << std::endl;
cv.notify_one();
}
}
// 消费者函数
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{return !buffer.empty();});
int data = buffer.front();
buffer.pop();
std::cout << "消费者消费数据: " << data << std::endl;
cv.notify_one();
if (data == 99) break;
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
4.1.3 描述
在上述代码中,我们定义了一个共享的std::queue<int>缓冲区buffer,以及一个std::mutex对象mtx用于保护缓冲区的访问。std::condition_variable对象cv用于线程间的同步。生产者线程在缓冲区满时等待,消费者线程在缓冲区空时等待。通过这种方式,我们确保了数据的正确生产和消费,避免了资源的竞争。
4.2 读写锁
4.2.1 原理
读写锁是一种用于多线程环境下的锁机制,它允许多个读线程同时访问共享资源,但只允许一个写线程访问。读写锁的设计目的是为了提高并发性能,因为在读多写少的场景下,多个读线程可以同时访问共享资源,而不会像互斥锁那样导致线程阻塞。
4.2.2 内容
在C++中,std::shared_timed_mutex可以用来实现读写锁。读线程使用std::shared_lock来获取读锁,写线程使用std::unique_lock来获取写锁。
4.2.2.1 示例代码
#include <iostream>
#include <thread>
#include <shared_timed_mutex>
std::shared_timed_mutex rw_mutex;
int shared_data = 0;
// 读线程函数
void reader() {
std::shared_lock<std::shared_timed_mutex> lock(rw_mutex);
std::cout << "读线程读取数据: " << shared_data << std::endl;
}
// 写线程函数
void writer(int data) {
std::unique_lock<std::shared_timed_mutex> lock(rw_mutex);
shared_data = data;
std::cout << "写线程更新数据: " << shared_data << std::endl;
}
int main() {
std::thread read1(reader);
std::thread read2(reader);
std::thread write(writer, 10);
read1.join();
read2.join();
write.join();
return 0;
}
4.2.3 描述
在这个例子中,我们使用std::shared_timed_mutex来保护共享数据shared_data。读线程通过std::shared_lock获取读锁,可以同时读取数据,而写线程通过std::unique_lock获取写锁,确保在写操作时没有其他线程访问数据。这种机制在读操作远多于写操作的场景下,可以显著提高并发性能。
4.3 未来与承诺
4.3.1 原理
未来与承诺(Future and Promise)是C++中用于异步编程的机制。Promise对象用于存储一个可能在将来可用的值,而Future对象用于获取这个值。Promise和Future通常成对使用,Promise负责设置值,Future负责获取值。
4.3.2 内容
在C++中,std::promise和std::future可以用来实现异步编程。std::promise对象在数据准备好时调用set_value方法,std::future对象则在需要时调用get方法来获取数据。
4.3.2.1 示例代码
#include <iostream>
#include <thread>
#include <future>
std::promise<int> prom;
std::future<int> fut = prom.get_future();
// 异步计算函数
void async_calculate() {
int result = 0;
for (int i = 0; i < 100000000; ++i) {
result += i;
}
prom.set_value(result);
}
int main() {
std::thread calc(async_calculate);
int result = fut.get();
std::cout << "异步计算结果: " << result << std::endl;
calc.join();
return 0;
}
4.3.3 描述
在这个例子中,我们创建了一个std::promise<int>对象prom和一个std::future<int>对象fut。async_calculate函数在另一个线程中执行,计算一个大数的和,并通过prom.set_value将结果存储在Promise中。主线程通过fut.get方法获取这个结果,fut.get会阻塞直到结果可用。这种机制允许我们异步执行计算密集型任务,同时在结果准备好时进行处理,提高了程序的响应性和效率。
5 并发性能优化
5.1 线程池设计与实现
线程池是一种设计模式,用于管理和复用一组线程来执行任务。在C++中,我们可以使用std::thread和std::queue来实现一个简单的线程池。
5.1.1 原理
线程池的核心原理是预先创建一组线程,这些线程处于等待状态,当有任务到来时,线程从任务队列中取出任务并执行。执行完毕后,线程不被销毁,而是返回到等待状态,等待下一个任务。这样可以避免频繁创建和销毁线程的开销,提高并发性能。
5.1.2 示例代码
#include <iostream>
#include <queue>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
class ThreadPool {
public:
ThreadPool(int numThreads) {
for (int i = 0; i < numThreads; ++i) {
threads.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty())
return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (std::thread &worker : threads) {
worker.join();
}
}
template <class F, class... Args>
auto enqueue(F &&f, Args &&...args) -> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if (stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task]() { (*task)(); });
}
condition.notify_one();
return res;
}
private:
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
std::vector<std::thread> threads;
bool stop;
};
// 使用示例
int main() {
ThreadPool pool(4); // 创建包含4个线程的线程池
// 向线程池中添加任务
auto future1 = pool.enqueue([] {
std::cout << "Task 1 executed by thread " << std::this_thread::get_id() << std::endl;
});
auto future2 = pool.enqueue([] {
std::cout << "Task 2 executed by thread " << std::this_thread::get_id() << std::endl;
});
// 等待任务完成
future1.get();
future2.get();
return 0;
}
5.1.3 解释
在上述代码中,ThreadPool类使用std::queue来存储任务,std::mutex和std::condition_variable来同步线程和任务队列。enqueue方法用于向线程池添加任务,std::packaged_task用于将任务包装成可以返回结果的形式。
5.2 并发性能分析工具
在C++中,有多种工具可以帮助我们分析并发性能,例如gperftools、Valgrind和Intel VTune等。这些工具可以帮助我们识别性能瓶颈,如CPU热点、内存访问模式和线程同步问题。
5.2.1 示例
使用gperftools中的ThreadSanitizer来检测线程同步问题:
# 编译代码,启用ThreadSanitizer
g++ -std=c++11 -pthread -fsanitize=thread -o my_program my_program.cpp
# 运行程序
./my_program
5.2.2 解释
ThreadSanitizer是一种运行时错误检测工具,可以检测线程同步问题,如数据竞争、死锁和未初始化的内存访问等。通过在编译时添加-fsanitize=thread选项,我们可以启用ThreadSanitizer。
5.3 死锁与活锁避免
死锁和活锁是并发编程中常见的问题。死锁发生在两个或多个线程互相等待对方释放资源,而活锁发生在两个或多个线程互相让步,导致任务无法完成。
5.3.1 原理
避免死锁的常见方法是使用资源分配图,确保系统中不存在循环等待资源的情况。避免活锁的方法是使用公平的调度策略,确保每个线程都有机会执行。
5.3.2 示例代码
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1;
std::mutex mutex2;
void function1() {
mutex1.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
mutex2.lock();
std::cout << "Function 1 executed" << std::endl;
mutex2.unlock();
mutex1.unlock();
}
void function2() {
mutex2.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
mutex1.lock();
std::cout << "Function 2 executed" << std::endl;
mutex1.unlock();
mutex2.unlock();
}
int main() {
std::thread t1(function1);
std::thread t2(function2);
t1.join();
t2.join();
return 0;
}
5.3.3 解释
在上述代码中,function1和function2分别尝试锁定mutex1和mutex2,然后在锁定另一个互斥锁之前等待1秒。如果两个线程同时启动,它们可能会互相等待对方释放资源,导致死锁。
为了避免死锁,我们可以使用std::lock函数来同时锁定多个互斥锁,或者使用std::mutex::try_lock函数来尝试锁定互斥锁,如果锁定失败,则立即返回,而不是阻塞等待。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1;
std::mutex mutex2;
void function1() {
std::lock(mutex1, mutex2);
std::cout << "Function 1 executed" << std::endl;
mutex1.unlock();
mutex2.unlock();
}
void function2() {
std::lock(mutex2, mutex1);
std::cout << "Function 2 executed" << std::endl;
mutex2.unlock();
mutex1.unlock();
}
int main() {
std::thread t1(function1);
std::thread t2(function2);
t1.join();
t2.join();
return 0;
}
在这个修改后的示例中,我们使用std::lock函数来同时锁定mutex1和mutex2,这样可以避免死锁。
6 C++并发实战
6.1 多线程网络编程
在C++中,多线程网络编程是实现高性能服务器的关键技术之一。通过使用多线程,可以同时处理多个网络连接,提高服务器的响应能力和吞吐量。下面是一个使用std::thread和boost.asio库进行多线程网络编程的示例。
6.1.1 示例:多线程TCP服务器
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class MultiThreadedServer {
public:
MultiThreadedServer(boost::asio::io_context& io_context, short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
start_accept();
}
private:
void start_accept() {
auto session = std::make_shared<Session>(io_context_);
acceptor_.async_accept(session->socket(),
boost::bind(&MultiThreadedServer::handle_accept, this, session,
boost::asio::placeholders::error));
}
void handle_accept(std::shared_ptr<Session> session,
const boost::system::error_code& error) {
if (!error) {
session->start();
}
start_accept();
}
void handle_read(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if (!error) {
// Process the received data.
std::cout.write(buffer_.data(), bytes_transferred);
std::cout << "\n";
// Start an async write.
boost::asio::async_write(socket_,
boost::asio::buffer("Echo: ", 6),
boost::bind(&Session::handle_write, shared_from_this(),
boost::asio::placeholders::error));
} else {
delete this;
}
}
void handle_write(const boost::system::error_code& error) {
if (!error) {
// Start an async read.
boost::asio::async_read(socket_,
boost::asio::buffer(buffer_),
boost::bind(&Session::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
};
int main() {
try {
boost::asio::io_context io_context;
MultiThreadedServer s(io_context, 12345);
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(
[&io_context]() { io_context.run(); });
}
for (auto& t : threads) {
t.join();
}
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
6.1.2 解释
此示例展示了如何创建一个多线程TCP服务器,使用boost.asio库处理网络I/O。服务器在端口12345上监听连接请求,每当有新的连接请求时,它会创建一个新的Session对象,并在单独的线程中处理该连接。Session对象负责读取和写入数据,处理完一个请求后,它会继续监听下一个请求。
6.2 并行计算案例
并行计算是C++并发编程的另一个重要应用,它允许在多个处理器核心上同时执行计算任务,从而显著提高计算效率。std::async和std::future是C++标准库中用于并行计算的工具。
6.2.1 示例:并行计算斐波那契数列
#include <iostream>
#include <future>
#include <vector>
// 并行计算斐波那契数列的第n项
long parallel_fib(long n) {
if (n <= 1) return n;
std::future<long> f1 = std::async(parallel_fib, n - 1);
std::future<long> f2 = std::async(parallel_fib, n - 2);
return f1.get() + f2.get();
}
int main() {
std::vector<long> fibs(10);
for (int i = 0; i < 10; ++i) {
fibs[i] = parallel_fib(i);
}
for (auto fib : fibs) {
std::cout << fib << " ";
}
std::cout << std::endl;
return 0;
}
6.2.2 解释
在这个示例中,我们使用std::async来并行计算斐波那契数列的前几项。std::async函数会启动一个异步任务,计算斐波那契数列的第n项。当计算完成时,我们使用std::future::get方法获取结果。由于斐波那契数列的计算是递归的,因此可以有效地利用多个线程并行计算。
6.3 并发编程最佳实践
并发编程中,正确处理线程安全和资源管理是至关重要的。以下是一些C++并发编程的最佳实践:
- 使用智能指针:在多线程环境中,使用std::shared_ptr和std::unique_ptr来管理资源,可以避免资源管理错误。
- 避免数据竞争:使用std::mutex和std::lock_guard来保护共享数据,确保在任何时刻只有一个线程可以访问共享资源。
- 使用原子操作:对于简单的共享变量操作,使用std::atomic类型可以避免锁的开销,提高性能。
- 线程局部存储:使用thread_local关键字来声明线程局部变量,每个线程都有自己的副本,避免了线程间的竞争。
6.3.1 示例:使用std::mutex保护共享数据
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Shared data: " << shared_data << std::endl;
return 0;
}
6.3.2 解释
在这个示例中,我们创建了10个线程,每个线程都会对shared_data变量进行100000次递增操作。为了确保线程安全,我们使用std::mutex来保护shared_data。std::lock_guard是一个RAII(资源获取即初始化)类型的锁,它会在构造时自动锁定std::mutex,并在析构时自动解锁,从而简化了锁的管理,避免了死锁和资源泄漏的风险。
通过遵循这些最佳实践,可以有效地避免并发编程中常见的错误,如数据竞争、死锁和资源泄漏,从而编写出更安全、更高效的多线程C++程序。
- 上一篇: 《国家计算机二级c语言历年真题及答案》
- 下一篇: 探索C++与Rust的转译 rust c++调用
猜你喜欢
- 2024-11-11 C++经典算法问题:背包问题(迭代+递归算法)!含源码示例
- 2024-11-11 C++进阶教程:C#嵌套循环 c++嵌套循环break
- 2024-11-11 C++经典算法 穷举法 穷举算法的优点
- 2024-11-11 C++数据结构-- 递归 排序 c++使用递归函数实现全排列
- 2024-11-11 如何使用c++发送window消息通知 c++怎么发给别人
- 2024-11-11 C++ replace函数-C++字符串替换函数
- 2024-11-11 C++学习:循环练习题(一) c++循环结构例题解析
- 2024-11-11 C/C++最细循环解析 c++循环结构23道题
- 2024-11-11 网络编程——C++实现socket通信(TCP)
- 2024-11-11 C++ GESP 2023年6月真题 c++历年真题解析
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)