thread
# thread
thread 对线程进行了支持,使得 C++在并行编程时不需要依赖第三方库,增强可移植性。
| 成员函数 | 功能 |
|---|---|
join | 对该线程进行等待,在等待的线程返回之前,调用 join 函数的线程将会被阻塞 |
joinable | 判断该线程是否已经执行完毕,如果是则返回 true,否则返回 false |
detach | 将该线程与创建线程进行分离,被分离后的线程不再需要创建线程调用 join 函数对其进行等待 |
swap | 将两个线程对象关联线程的状态进行交换 |
- join:主线程创建新线程后,可以调用 join 函数等待新线程终止,当新线程终止时 join 函数就会自动清理线程相关的资源。
- detach:将新线程与主线程进行分离,分离后新线程会在后台运行,其所有权和控制权将会交给 C++运行库,此时 C++运行库会保证当线程退出时,其相关资源能够被正确回收。
# this_thread 命名空间
| 函数名 | 功能 |
|---|---|
get_id | 获取该线程的 id |
yield | 当前线程“放弃”执行,让操作系统调度另一线程继续执行 |
sleep_until | 让当前线程休眠到一个具体时间点 |
sleep_for | 让当前线程休眠一个时间段 |
# 互斥量库
# mutex
int tickets = 100;
mutex m;
void sellTicket() {
while (tickets > 0) {
m.lock();
if(tickets > 0) {
cout << tickets << endl;
tickets--;
}
m.unlock();
this_thread::sleep_for(std::chrono::microseconds(100));
}
}
void TestThread() {
vector<thread> threads;
for(int i = 0;i < 3;++i) {
threads.push_back(thread(sellTicket));
}
for(int i = 0;i < 3;++i) {
threads[i].join();
}
}
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
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
使用 mutex 时,如果加锁的范围太大,可能在中途出错没有解锁,此后申请这个互斥锁的线程就会被阻塞住,造成了死锁问题,推荐使用 lock_guard 和 unique_lock。
# lock_guard
lock_guard 类模板主要是通过 RAII 的方式,对其管理的互斥锁进行了封装。
在需要加锁的地方,用互斥锁实例化一个 lock_guard 对象,在 lock_guard 的构造函数中会调用 lock 进行加锁,当 lock_guard 对象出作用域前会调用析构函数,在 lock_guard 的析构函数中会调用 unlock 自动解锁。
void sellTicket() {
while (tickets > 0) {
{
std::lock_guard<std::mutex> lock(m);
if(tickets > 0) {
std::cout << tickets << std::endl;
tickets--;
}
}
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# unique_lock
lock_guard 不能用在函数参数传递和返回过程中,C++11 提供了 unique_lock,与 wait 结合使用。
# 条件变量库
- wait
void wait(unique_lock<mutex>& lck);
1
wait 函数一般在临界区中调用的,为了让当前线程调用 wait 阻塞时其他线程能够获取到锁,因此调用 wait 系列函数时需要传入一个互斥锁,当线程被阻塞时这个互斥锁会被自动解锁,而当这个线程被唤醒时,又会自动获得这个互斥锁。因此 wait 系列函数实际上有两个功能,一个是让线程在条件不满足时进行阻塞等待,另一个是让线程将对应的互斥锁进行解锁。
- notify 系列成员函数的作用就是唤醒等待的线程,包括 notify_one 和 notify_all。条件变量下可能会有多个线程在进行阻塞等待,这些线程由等待状态转为阻塞状态获取互斥锁之后,继续执行。
- notify_one:唤醒等待队列中的首个线程,如果等待队列为空则什么也不做。
- notify_all:唤醒等待队列中的所有线程,如果等待队列为空则什么也不做。
使用规范:
等待条件变量的代码:
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(&cond, &mutex);
修改条件
pthread_mutex_unlock(&mutex);
1
2
3
4
5
2
3
4
5
唤醒等待线程的代码:
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
1
2
3
4
2
3
4
# 多线程编程
多线程编程的两个问题:
- 线程的互斥:静态条件-》临界区-》mutex/CAS
- 线程的同步:信号量/条件变量
示例:线程间同步通信,生产者消费者
class Queue {
public:
Queue(int max_size = 20) : max_size_(max_size) {}
void Push(int &data) {
std::unique_lock<std::mutex> lock(m_);
while (IsFull()) {
// producer waiting...
p_cond_.wait(lock);
}
q_.push(data);
std::cout << "producer:" << data << std::endl;
c_cond_.notify_one();
}
void Pop() {
std::unique_lock<std::mutex> lock(m_);
while (IsEmpty()) {
c_cond_.wait(lock);
}
std::cout << "consumer:" << q_.front() << std::endl;
q_.pop();
p_cond_.notify_one();
}
int Front() const {
return q_.front();
}
bool IsEmpty() const {
return q_.empty();
}
bool IsFull() const {
return q_.size() == max_size_;
}
private:
int max_size_;
std::queue<int> q_;
std::mutex m_;
std::condition_variable p_cond_;
std::condition_variable c_cond_;
};
void consumer(Queue *q) {
for (int i = 0; i < 5; ++i) {
q->Pop();
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
void producer(Queue *q) {
for (int i = 0; i < 5; ++i) {
int data = rand() % 10 + 1;
q->Push(data);
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
int main() {
Queue *queue = new Queue(30);
std::thread c(consumer, queue);
std::thread p(producer, queue);
// ...
c.join();
p.join();
return 0;
}
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
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