跳到主要内容

C++ 线程管理

什么是线程?

线程是程序执行的最小单位,它允许程序同时执行多个操作。在现代多核处理器上,多线程编程可以显著提高应用程序的性能和响应性。C++11标准引入了原生的线程支持,使得在C++中进行多线程编程变得更加简单和标准化。

线程与进程

进程是程序的一个实例,包含代码、数据和系统资源;而线程是进程中的执行流,同一进程中的多个线程共享该进程的资源。

C++ 线程库介绍

C++11引入了<thread>头文件,它提供了创建和管理线程的类和函数。核心类包括:

  1. std::thread - 表示一个执行线程
  2. std::mutex - 提供互斥锁功能
  3. std::condition_variable - 线程同步原语
  4. std::futurestd::promise - 异步操作结果的传递机制

创建线程

在C++中创建线程非常简单,只需要构造一个std::thread对象,并传入一个可调用对象(函数、函数对象、lambda表达式等)作为线程的入口点。

基本线程创建

cpp
#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表达式

cpp
#include <iostream>
#include <thread>

int main() {
// 使用lambda表达式创建线程
std::thread t([]() {
std::cout << "Hello from lambda thread!" << std::endl;
});

t.join();
return 0;
}

线程参数传递

可以向线程传递参数,方法是在构造std::thread对象时,在函数名之后添加参数。

cpp
#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(),否则会被复制。

cpp
#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()方法获取。

cpp
#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()方法会阻塞当前线程,直到调用它的线程完成执行。

cpp
std::thread t(someFunction);
// t开始执行

// 阻塞当前线程,直到t完成
t.join();

// 继续执行

线程分离 - detach()

detach()方法将线程与std::thread对象分离,允许线程独立运行。一旦分离,就不能再通过std::thread对象控制该线程。

cpp
std::thread t(backgroundTask);
// 分离线程,允许它在后台独立运行
t.detach();

// 继续执行,不等待t完成
注意

分离的线程在主程序结束时可能会被操作系统终止。确保在程序退出前,分离的线程已经完成工作,或者有适当的关闭机制。

检查线程是否可join

使用joinable()方法可以检查线程是否可以被join。

cpp
std::thread t(someFunction);

if (t.joinable()) {
t.join();
}

线程同步基础

数据竞争问题

当多个线程同时访问同一数据,并且至少有一个线程修改数据时,就会发生数据竞争。

cpp
#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保护共享数据

cpp
#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自动管理锁

cpp
#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在离开作用域时自动释放锁
}
}

实际应用案例

多线程文件处理器

下面是一个简单的多线程文件处理程序,它使用多个线程并行处理文件:

cpp
#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;
}

线程池实现

线程池是一种常用的线程管理模式,它预先创建一定数量的线程,然后复用这些线程来执行任务,避免了频繁创建和销毁线程的开销。

cpp
#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++中的线程管理基础,包括:

  1. 线程的创建与基本操作
  2. 参数传递给线程
  3. 线程同步机制(mutex、lock_guard)
  4. 实际应用案例
  5. 线程池实现

理解多线程编程对于开发高性能的C++应用程序至关重要。正确管理线程可以充分利用多核处理器的能力,但同时也需要注意避免数据竞争和死锁等问题。

练习

  1. 创建一个程序,使用多线程计算大数组的和,将数组分成多个部分,每个线程计算一部分,最后合并结果。
  2. 实现一个简单的生产者-消费者模式,使用一个线程生产数据,另一个线程消费数据。
  3. 扩展线程池示例,添加返回任务结果的功能(提示:使用std::future和std::packaged_task)。
  4. 创建一个多线程日志系统,主线程产生日志消息,工作线程将日志写入文件。

扩展资源

  • C++ 参考文档 - std::thread
  • 《C++ Concurrency in Action》by Anthony Williams - 深入讲解C++并发编程的书籍
  • 《Effective Modern C++》by Scott Meyers - 包含一些关于C++线程安全的内容

通过实践和深入学习,你将能够掌握C++多线程编程的技巧,开发出高效、可靠的多线程应用程序。