C++ 多线程编程允许程序同时执行多个任务,从而提高性能和响应能力。C++11 引入了 <thread>
库,使得多线程编程更加方便。以下是一些基本概念和示例,帮助你理解如何在 C++ 中进行多线程编程。
1. 创建线程
使用 std::thread
类可以创建一个新线程。你需要将一个函数或可调用对象传递给 std::thread
构造函数。
#include <iostream>
#include <thread>void threadFunction() {std::cout << "Hello from thread!\\\\n";
}int main() {std::thread t(threadFunction); // 创建线程并执行 threadFunctiont.join(); // 等待线程结束std::cout << "Hello from main!\\\\n";return 0;
}
2. 传递参数给线程函数
你可以通过 std::thread
构造函数传递参数给线程函数。
#include <iostream>
#include <thread>void printMessage(const std::string& message) {std::cout << message << "\\\\n";
}int main() {std::thread t(printMessage, "Hello from thread!");t.join();std::cout << "Hello from main!\\\\n";return 0;
}
3. 线程同步
多个线程可能会同时访问共享资源,导致数据竞争。为了避免这种情况,可以使用互斥锁(std::mutex
)来保护共享资源。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>std::mutex mtx; // 互斥锁void printNumber(int num) {mtx.lock(); // 加锁std::cout << "Number: " << num << "\\\\n";mtx.unlock(); // 解锁
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(printNumber, i);}for (auto& t : threads) {t.join();}return 0;
}
4. 使用 std::lock_guard
自动管理锁
std::lock_guard
是一个 RAII 风格的简单的锁管理器,它在构造时自动加锁,在析构时自动解锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>std::mutex mtx;void printNumber(int num) {std::lock_guard<std::mutex> lock(mtx); // 自动加锁和解锁std::cout << "Number: " << num << "\\\\n";
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(printNumber, i);}for (auto& t : threads) {t.join();}return 0;
}
5. 条件变量
条件变量(std::condition_variable
)用于线程间的同步,允许一个线程等待另一个线程满足某些条件。
配合std::condition_variable::wait()
函数的第一个参数的必须是比lock_guard更灵活控制也更复杂重度的锁:std::unique_lock。它可以RAII自动析构,也可以手动lock/unlock,中间有的代码段就可以释放锁。手动把它unlock之后只是解锁,没有销毁,后续可以按需复用再次 lock/unlock。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void printMessage() {std::**unique_lock**<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; }); // 等待条件满足std::cout << "Hello from thread!\\\\n";
}int main() {std::thread t(printMessage);{std::lock_guard<std::mutex> lock(mtx);ready = true; // 设置条件为 true}cv.notify_one(); // 通知等待的线程t.join();std::cout << "Hello from main!\\\\n";return 0;
}相比lock_guard的优势:
1. 灵活性:unique_lock 支持延迟锁定(可以先构造对象而不立即加锁),而 lock_guard 在构造时就必须加锁。这意味着你可以先创建 unique_lock 对象,然后根据程序逻辑需要时再调用 lock() 或 unlock() 方法进行手动加锁或解锁。
2. 条件变量的支持:unique_lock 可以与标准库中的条件变量一起使用,如 std::condition_variable,这是 lock_guard 所不具备的功能。这是因为条件变量需要能够原子地释放锁并进入等待状态,这正是 unique_lock 提供的能力之一。
3. 锁的所有权转移:unique_lock 支持移动语义(move semantics),允许将锁的所有权从一个 unique_lock 对象转移到另一个对象,从而使得锁可以在不同的作用域中传递。而 lock_guard 不支持这种操作,它的锁所有权是固定的。
4. 尝试锁定(try-locking):除了基本的 lock() 和 unlock() 方法外,unique_lock 还提供了 try_lock() 方法,该方法尝试获取锁但不会阻塞线程,如果无法获得锁则立即返回失败结果。这对于避免线程长时间阻塞非常有用。wait第二个参数predicate谓词的用法参见:
<https://en.cppreference.com/w/cpp/thread/condition_variable/wait>
predicate不满足不会结束等待执行后续语句。
6. 线程池
C++ 标准库没有直接提供线程池的实现,但你可以使用第三方库(如 Boost
)或自己实现一个简单的线程池。
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>class ThreadPool {
public:ThreadPool(size_t numThreads) {for (size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queueMutex);this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });if (this->stop && this->tasks.empty()) return;task = std::move(this->tasks.front());this->tasks.pop();}task();}});}}template<class F>void enqueue(F&& f) {{std::unique_lock<std::mutex> lock(queueMutex);tasks.emplace(std::forward<F>(f));}condition.notify_one();}~ThreadPool() {{std::unique_lock<std::mutex> lock(queueMutex);stop = true;}condition.notify_all();for (std::thread &worker : workers) {worker.join();}}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queueMutex;std::condition_variable condition;bool stop = false;
};int main() {ThreadPool pool(4);for (int i = 0; i < 8; ++i) {pool.enqueue([i] {std::cout << "Task " << i << " is running on thread " << std::this_thread::get_id() << "\\\\n";});}return 0;
}
7. 线程局部存储
线程局部存储(Thread Local Storage, TLS)允许每个线程拥有自己的变量实例。C++11 引入了 thread_local
关键字来实现这一点。
#include <iostream>
#include <thread>thread_local int threadLocalVar = 0;void threadFunction(int id) {threadLocalVar = id;std::cout << "Thread " << id << " has threadLocalVar = " << threadLocalVar << "\\\\n";
}int main() {std::thread t1(threadFunction, 1);std::thread t2(threadFunction, 2);t1.join();t2.join();return 0;
}
8. 异步任务
C++11 还引入了 std::async
和 std::future
,用于异步执行任务并获取结果。
#include <iostream>
#include <future>int compute() {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;
}int main() {// 启动异步任务std::future<int> fut = std::async(std::launch::async, compute);// 获取结果int result = fut.get();std::cout << "Result: " << result << std::endl;return 0;
}//使用 std::packaged_task (包在线程函数外)------------------------------------------
#include <iostream>
#include <future>
#include <thread>int compute() {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;
}int main() {// 创建 packaged_task std::packaged_task<int()> task(compute);// 获取 futurestd::future<int> fut = task.get_future();// 在另一个线程中执行任务std::thread t(std::move(task));t.join();// 获取结果int result = fut.get();std::cout << "Result: " << result << std::endl;return 0;
}// 使用 std::promise (作为线程函数参数) -----------------------------------------------#include <iostream>
#include <future>
#include <thread>void compute(std::promise<int> prom) {std::this_thread::sleep_for(std::chrono::seconds(2));prom.set_value(42);
}int main() {// 创建 promise 和 futurestd::promise<int> prom;std::future<int> fut = prom.get_future();// 在另一个线程中执行任务std::thread t(compute, std::move(prom));// 获取结果int result = fut.get();std::cout << "Result: " << result << std::endl;t.join();return 0;
}
异步机制总结: C++11 的异步机制(std::future、std::async、std::packaged_task 和 std::promise)相比传统的多线程编程,提供了以下额外的好处:
更高的抽象层次,简化了异步操作的管理。
自动化的结果传递和异常处理。
更灵活的线程管理和任务执行策略。
更清晰的代码结构和更低的耦合度。
支持任务组合和超时等待。
这些机制使异步编程更加直观、安全和高效,是现代 C++ 并发编程的重要组成部分。
总结
C++ 多线程编程提供了强大的工具来处理并发任务。通过使用 std::thread
、std::mutex
、std::condition_variable
等工具,你可以编写高效且安全的多线程程序。需要注意的是,多线程编程容易引入数据竞争和死锁等问题,因此需要仔细设计和测试。