C++ 线程管理
什么是线程?
线程是程序执行的最小单位,它允许程序同时执行多个操作。在现代多核处理器上,多线程编程可以显著提高应用程序的性能和响应性。C++11标准引入了原生的线程支持,使得在C++中进行多线程编程变得更加简单和标准化。
进程是程序的一个实例,包含代码、数据和系统资源;而线程是进程中的执行流,同一进程中的多个线程共享该进程的资源。
C++ 线程库介绍
C++11引入了<thread>
头文件,它提供了创建和管理线程的类和函数。核心类包括:
std::thread
- 表示一个执行线程std::mutex
- 提供互斥锁功能std::condition_variable
- 线程同步原语std::future
和std::promise
- 异步操作结果的传递机制
创建线程
在C++中创建线程非常简单,只需要构造一个std::thread
对象,并传入一个可调用对象(函数、函数对象、lambda表达式等)作为线程的入口点。
基本线程创建
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
// 创建线程
std::thread t(hello);
// 等待线程完成
t.join();
std::cout << "Main thread continues execution" << std::endl;
return 0;
}
输出:
Hello from thread!
Main thread continues execution
使用Lambda表达式
#include <iostream>
#include <thread>
int main() {
// 使用lambda表达式创建线程
std::thread t([]() {
std::cout << "Hello from lambda thread!" << std::endl;
});
t.join();
return 0;
}
线程参数传递
可以向线程传递参数,方法是在构造std::thread
对象时,在函数名之后添加参数。
#include <iostream>
#include <thread>
#include <string>
void greeting(std::string name, int age) {
std::cout << "Hello, " << name << "! You are " << age << " years old." << std::endl;
}
int main() {
std::string userName = "Alice";
int userAge = 25;
// 传递参数到线程
std::thread t(greeting, userName, userAge);
t.join();
return 0;
}
输出:
Hello, Alice! You are 25 years old.
传递引用参数时需要使用std::ref(),否则会被复制。
#include <iostream>
#include <thread>
#include <string>
void modifyString(std::string& str) {
str = "Modified string";
}
int main() {
std::string myString = "Original string";
// 传递引用参数
std::thread t(modifyString, std::ref(myString));
t.join();
std::cout << myString << std::endl;
return 0;
}
输出:
Modified string
线程管理
线程ID
每个线程都有一个唯一的ID,可以通过get_id()
方法获取。
#include <iostream>
#include <thread>
void printThreadId() {
std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t(printThreadId);
std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;
std::cout << "Created thread ID: " << t.get_id() << std::endl;
t.join();
return 0;
}
线程等待 - join()
join()
方法会阻塞当前线程,直到调用它的线程完成执行。
std::thread t(someFunction);
// t开始执行
// 阻塞当前线程,直到t完成
t.join();
// 继续执行
线程分离 - detach()
detach()
方法将线程与std::thread
对象分离,允许线程独立运行。一旦分离,就不能再通过std::thread
对象控制该线程。
std::thread t(backgroundTask);
// 分离线程,允许它在后台独立运行
t.detach();
// 继续执行,不等待t完成
分离的线程在主程序结束时可能会被操作系统终止。确保在程序退出前,分离的线程已经完成工作,或者有适当的关闭机制。
检查线程是否可join
使用joinable()
方法可以检查线程是否可以被join。
std::thread t(someFunction);
if (t.joinable()) {
t.join();
}
线程同步基础
数据竞争问题
当多个线程同时访问同一数据,并且至少有一个线程修改数据时,就会发生数据竞争。
#include <iostream>
#include <thread>
#include <vector>
int counter = 0;
void incrementCounter(int iterations) {
for (int i = 0; i < iterations; ++i) {
// 这是一个不安全的操作,可能导致数据竞争
counter++;
}
}
int main() {
const int iterations = 1000000;
std::thread t1(incrementCounter, iterations);
std::thread t2(incrementCounter, iterations);
t1.join();
t2.join();
// 期望输出2000000,但实际结果可能小于这个值
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
使用mutex保护共享数据
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex counterMutex; // 保护counter的mutex
void incrementCounter(int iterations) {
for (int i = 0; i < iterations; ++i) {
// 锁定mutex,保护counter的访问
counterMutex.lock();
counter++;
counterMutex.unlock();
}
}
int main() {
const int iterations = 1000000;
std::thread t1(incrementCounter, iterations);
std::thread t2(incrementCounter, iterations);
t1.join();
t2.join();
// 现在结果将始终是2000000
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
使用lock_guard自动管理锁
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex counterMutex;
void incrementCounter(int iterations) {
for (int i = 0; i < iterations; ++i) {
// lock_guard在构造时获取锁,在析构时释放锁
// 这样可以防止忘记解锁或异常时的锁泄漏
std::lock_guard<std::mutex> lock(counterMutex);
counter++;
// lock_guard在离开作用域时自动释放锁
}
}
实际应用案例
多线程文件处理器
下面是一个简单的多线程文件处理程序,它使用多个线程并行处理文件:
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <string>
#include <fstream>
#include <queue>
#include <atomic>
class FileProcessor {
private:
std::queue<std::string> fileQueue;
std::mutex queueMutex;
std::mutex printMutex;
std::atomic<bool> processing{true};
std::vector<std::thread> workers;
void processFiles(int workerId) {
while (processing) {
std::string filename;
// 获取一个文件名
{
std::lock_guard<std::mutex> lock(queueMutex);
if (fileQueue.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
filename = fileQueue.front();
fileQueue.pop();
}
// 处理文件
std::ifstream file(filename);
if (!file.is_open()) {
std::lock_guard<std::mutex> lock(printMutex);
std::cout << "Worker " << workerId << ": Failed to open " << filename << std::endl;
continue;
}
// 模拟处理文件内容
int lineCount = 0;
std::string line;
while (std::getline(file, line)) {
lineCount++;
}
// 打印结果
{
std::lock_guard<std::mutex> lock(printMutex);
std::cout << "Worker " << workerId << ": Processed " << filename
<< " with " << lineCount << " lines" << std::endl;
}
}
}
public:
// 添加文件到处理队列
void addFile(const std::string& filename) {
std::lock_guard<std::mutex> lock(queueMutex);
fileQueue.push(filename);
}
// 启动工作线程
void start(int numThreads) {
for (int i = 0; i < numThreads; ++i) {
workers.emplace_back(&FileProcessor::processFiles, this, i);
}
}
// 停止处理
void stop() {
processing = false;
for (auto& thread : workers) {
if (thread.joinable()) {
thread.join();
}
}
}
};
int main() {
FileProcessor processor;
// 添加一些文件
processor.addFile("file1.txt");
processor.addFile("file2.txt");
processor.addFile("file3.txt");
processor.addFile("file4.txt");
// 启动3个工作线程
processor.start(3);
// 给线程一些时间来处理文件
std::this_thread::sleep_for(std::chrono::seconds(2));
// 添加更多文件
processor.addFile("file5.txt");
processor.addFile("file6.txt");
// 等待更长时间
std::this_thread::sleep_for(std::chrono::seconds(3));
// 停止处理
processor.stop();
return 0;
}
线程池实现
线程池是一种常用的线程管理模式,它预先创建一定数量的线程,然后复用这些线程来执行任务,避免了频繁创建和销毁线程的开销。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <vector>
class ThreadPool {
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop;
public:
ThreadPool(size_t numThreads) : stop(false) {
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();
}
}
};
// 使用示例
int main() {
ThreadPool pool(4); // 创建4个工作线程
// 提交一些任务
for (int i = 0; i < 8; ++i) {
pool.enqueue([i] {
std::cout << "Task " << i << " executed by thread "
<< std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
});
}
// 给线程池一些时间来处理任务
std::this_thread::sleep_for(std::chrono::seconds(10));
return 0;
}
总结
本文介绍了C++中的线程管理基础,包括:
- 线程的创建与基本操作
- 参数传递给线程
- 线程同步机制(mutex、lock_guard)
- 实际应用案例
- 线程池实现
理解多线程编程对于开发高性能的C++应用程序至关重要。正确管理线程可以充分利用多核处理器的能力,但同时也需要注意避免数据竞争和死锁等问题。
练习
- 创建一个程序,使用多线程计算大数组的和,将数组分成多个部分,每个线程计算一部分,最后合并结果。
- 实现一个简单的生产者-消费者模式,使用一个线程生产数据,另一个线程消费数据。
- 扩展线程池示例,添加返回任务结果的功能(提示:使用std::future和std::packaged_task)。
- 创建一个多线程日志系统,主线程产生日志消息,工作线程将日志写入文件。
扩展资源
- C++ 参考文档 - std::thread
- 《C++ Concurrency in Action》by Anthony Williams - 深入讲解C++并发编程的书籍
- 《Effective Modern C++》by Scott Meyers - 包含一些关于C++线程安全的内容
通过实践和深入学习,你将能够掌握C++多线程编程的技巧,开发出高效、可靠的多线程应用程序。