19 C++11 标准库中的多线程编程

19 C++11 标准库中的多线程编程

在 C++11 标准中,引入了对多线程编程的支持,下面我们将详细介绍使用 C++11 的多线程编程,包括基本的线程创建、同步、互斥、条件变量等内容。

1. 创建线程

在 C++11 中,创建线程可以使用 std::thread 类。它是一个简单而强大的工具,可以使我们轻松地在程序中创建和管理线程。

1.1 创建线程的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <thread>

void threadFunction() {
std::cout << "Hello from thread!" << std::endl;
}

int main() {
// 创建一个线程,执行 threadFunction
std::thread t(threadFunction);

// 等待线程完成
t.join();

std::cout << "Thread has finished execution." << std::endl;
return 0;
}

关键点:

  • std::thread 构造函数接受一个可调用对象(函数指针、lambda 表达式等)。
  • 使用 t.join() 方法使主线程等待线程 t 的完成。

2. 线程参数传递

我们可以通过 std::thread 的构造函数给线程传递参数。

2.1 线程参数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>

void threadFunction(int id) {
std::cout << "Hello from thread " << id << "!" << std::endl;
}

int main() {
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);

t1.join();
t2.join();

return 0;
}

关键点:

  • 可以在 std::thread 的构造函数中传递参数。

3. 线程同步

在多线程编程中,线程之间的同步至关重要,以防止数据竞争和不一致性。C++11 提供了一系列同步原语。

3.1 互斥量 std::mutex

std::mutex 是用于保护共享数据的简单互斥量。

3.1.1 使用 std::mutex 的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 定义一个互斥量
int counter = 0; // 共享变量

void incrementCounter() {
for (int i = 0; i < 1000; ++i) {
mtx.lock(); // 上锁
++counter; // 对共享变量进行操作
mtx.unlock(); // 解锁
}
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}

关键点:

  • 使用 mtx.lock()mtx.unlock() 来保护对共享变量的访问。

3.2 std::lock_guard

std::lock_guard 是一种 RAII 风格的互斥量管理类,确保异常处理时也能释放锁。

3.2.1 使用 std::lock_guard 的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 定义一个互斥量
int counter = 0; // 共享变量

void incrementCounter() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 自动上锁
++counter; // 对共享变量进行操作
// 自动解锁
}
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}

关键点:

  • std::lock_guard 会在超出作用域时自动解锁,避免泄漏锁。

4. 条件变量 std::condition_variable

条件变量用于线程间的通信,允许一个或多个线程等待某个条件的发生。

4.1 条件变量的基本用法

4.1.1 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void prepare() {
// 模拟准备工作
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知等待的线程
}

void consume() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待条件满足
std::cout << "Data is ready!" << std::endl;
}

int main() {
std::thread producer(prepare);
std::thread consumer(consume);

producer.join();
consumer.join();

return 0;
}

关键点:

  • 使用 std::condition_variable::wait() 可以在条件不满足时阻塞线程,条件满足时被唤醒。
  • notify_one()notify_all() 方法用于唤醒一个或所有等待线程。

总结

C++11 提供的多线程支持使得编写并发程序变得更加简单和安全。我们介绍了如何创建线程、传递参数、同步机制(互斥量和条件变量)等基本概念,理解和掌握这些内容将有助于你在实际编程中应用多线程。

19 C++ 参数传递

19 C++ 参数传递

在 C++ 中,函数可以通过不同的方式来接收参数。掌握这些方式对编写高效的程序非常重要。我们主要讨论以下三种参数传递方式:按值传递、按引用传递和按指针传递。

1. 按值传递

概述

按值传递 中,函数接收的是实参的一个副本。这意味着函数内部对参数的修改不会影响到原始数据。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void modifyValue(int num) {
num += 10; // 尝试修改num的值
cout << "Inside modifyValue: " << num << endl; // 输出修改后的值
}

int main() {
int original = 5;
cout << "Before modifyValue: " << original << endl; // 输出原始值
modifyValue(original); // 传递原始值的副本
cout << "After modifyValue: " << original << endl; // 原值不会改变
return 0;
}

运行结果

1
2
3
Before modifyValue: 5
Inside modifyValue: 15
After modifyValue: 5

结论

在上述案例中,original 的值未受影响,因为 modifyValue 函数操作的是 num 的一个副本,而非原始数据。

2. 按引用传递

概述

按引用传递 允许函数直接访问传递的变量。修改引用参数的值会直接影响到原始数据。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void modifyValueByReference(int &num) {
num += 10; // 修改引用参数的值
cout << "Inside modifyValueByReference: " << num << endl; // 输出修改后的值
}

int main() {
int original = 5;
cout << "Before modifyValueByReference: " << original << endl; // 输出原始值
modifyValueByReference(original); // 传递原始值的引用
cout << "After modifyValueByReference: " << original << endl; // 原值会被改变
return 0;
}

运行结果

1
2
3
Before modifyValueByReference: 5
Inside modifyValueByReference: 15
After modifyValueByReference: 15

结论

通过引用传递,original 的值被修改,因为 modifyValueByReference 操作的是原始变量的引用。

3. 按指针传递

概述

按指针传递 是将变量的地址传递给函数。通过指针,可以在函数内部直接修改原始数据。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void modifyValueByPointer(int *numPtr) {
*numPtr += 10; // 通过指针修改原始数据
cout << "Inside modifyValueByPointer: " << *numPtr << endl; // 输出修改后的值
}

int main() {
int original = 5;
cout << "Before modifyValueByPointer: " << original << endl; // 输出原始值
modifyValueByPointer(&original); // 传递原始值的地址
cout << "After modifyValueByPointer: " << original << endl; // 原值会被改变
return 0;
}

运行结果

1
2
3
Before modifyValueByPointer: 5
Inside modifyValueByPointer: 15
After modifyValueByPointer: 15

结论

使用指针传递参数后,original 的值也被修改了,因为 modifyValueByPointer 通过指针直接操作了原始数据的内存地址。

总结

  • 按值传递:函数接收实参的副本,对实参无影响。
  • 按引用传递:函数接收实参的引用,修改参数会影响原始数据。
  • 按指针传递:函数接收实参的地址,通过指针修改原始数据。

理解这三种参数传递方式能够帮助你更好地控制数据的流动和修改,从而编写出更有效的 C++ 程序。

并发编程与线程池

并发编程与线程池

在现代计算中,并发编程是一项重要的技能,它允许程序同时处理多个任务,从而提高性能和资源利用率。C++标准库提供了一些强大的工具来实现并发编程。

1. C++ 并发编程基础

1.1 线程 (Thread)

在 C++11 及之后的版本中,std::thread 类提供了对线程的支持。你可以通过创建 std::thread 对象来启动一个新线程。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <thread>

void sayHello() {
std::cout << "Hello from thread!" << std::endl;
}

int main() {
std::thread t(sayHello);
t.join(); // 等待线程完成
return 0;
}

1.1.1 关键函数

  • join():等待线程结束。
  • detach():分离线程,让它在后台独立运行。

1.2 互斥量 (Mutex)

在多线程环境中,访问共享资源时需要避免数据竞争。std::mutex 提供了互斥量来保护共享数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁和解锁
++counter;
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}

1.3 条件变量 (Condition Variable)

std::condition_variable 用于线程间的通信,允许线程在某些条件下等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void printId(int id) {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{ return ready; }); // 等待ready为true
std::cout << "Thread " << id << " is running\n";
}

void go() {
std::lock_guard<std::mutex> lck(mtx);
ready = true;
cv.notify_all(); // 唤醒所有等待的线程
}

int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(printId, i);
}
std::cout << "10 threads ready to race...\n";
go(); // 允许线程开始工作
for (auto& th : threads) th.join();
return 0;
}

2. 线程池 (Thread Pool)

线程池是一种管理和复用线程的方式,避免了频繁创建和销毁线程的开销。我们可以使用标准库来实现一个简单的线程池。

2.1 线程池的设计

设计一个线程池需要考虑以下组成部分:

  • 在线程池中管理多个线程。
  • 任务队列用于存储待处理的任务。
  • 任务的提交和执行机制。

2.2 实现一个简单的线程池

以下是一个简单的线程池实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <condition_variable>
#include <future>

class ThreadPool {
public:
ThreadPool(size_t);
template<class F>
auto enqueue(F&& f) -> std::future<typename std::result_of<F()>::type>;

~ThreadPool();

private:
std::vector<std::thread> workers; // 工作线程
std::queue<std::function<void()>> tasks; // 任务队列

std::mutex queue_mutex; // 互斥量
std::condition_variable condition; // 条件变量
bool stop; // 停止标志
};

ThreadPool::ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back(
[this] {
for (;;) {
std::function<void()> task;

{
std::unique_lock<std::mutex> lock(this->queue_mutex);
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>
auto ThreadPool::enqueue(F&& f) -> std::future<typename std::result_of<F()>::type> {
using return_type = typename std::result_of<F()>::type;

auto task = std::make_shared<std::packaged_task<return_type()>>(std::forward<F>(f));
std::future<return_type> res = task->get_future();

{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace([task]() { (*task)(); });
}

condition.notify_one(); // 通知一个线程
return res;
}

ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all(); // 唤醒所有线程
for (std::thread &worker : workers) worker.join(); // 等待所有线程结束
}

// 示例使用
int main() {
ThreadPool pool(4); // 创建一个有4个线程的线程池

auto result1 = pool.enqueue([] { return 1; });
auto result2 = pool.enqueue([] { return 2; });

std::cout << "Result of task 1: " << result1.get() << std::endl;
std::cout << "Result of task 2: " << result2.get() << std::endl;

return 0;
}

2.3 小结

线程池通过复用线程,极大地提高了效率。设计良好的线程池可以显著减少线程创建和管理的开销,使得多线程编程更加高效。

3. 结语

并发编程在现代 C++ 开发中是不可或缺的技能。通过掌握线程、互斥量、条件变量以及线程池的使用,可以构建高效、响应迅速的应用程序。