小伟的C+日常
C++与线程之间擦出的火花(二)
上文说到了thread对象的一些函数,本文主要讲述一下互斥量以及上文匆忙未说的在线程里面的一些函数。
sleep_for函数: 阻塞当前线程的执行,直至规定的时间结束后,也有可能比规定的时间长,因为调度或资源争议延迟。后面可以加上数字加时间单位,如2000ms,也可以加上固定的时间函数加数字:std::chrono::nanoseconds 纳秒std::chrono::microseconds 微秒
std::chrono::milliseconds 毫秒
std::chrono::seconds 秒
std::chrono::minutes 分
std::chrono::hours 小时
std::chrono::days (C++20 起) 天
std::chrono::weeks (C++20 起) 周
std::chrono::months (C++20 起) 月
std::chrono::years (C++20 起) 年
如std::this_thread::sleep_for(std::chrono::seconds(2))
接下来是我今天要说的主角:锁
互斥锁: 首先看下面这段代码
#include <iostream>
#include <chrono>
#include <thread>
#include <utility>
int g_m = 0;
void foo()
{
g_m += 1;
std::cout << "g_m:" << g_m << std::endl;
}
void bar()
{
g_m += 2;
std::cout << "g_m:" << g_m << std::endl;
}
int main()
{
std::thread t1(foo);
std::thread t2(bar);
t1.join();
t2.join();
return 0;
}
以下是我随机运行了三次的结果,可以看出,虽然是同一段代码,但每次运行产生的结果都不一样:
具体原因,在C语言的概念里,这些一段代码运行,但在汇编下的代码(由于是我自己写出来的,可能真实反汇编出来不是这样,但已经说明问题了):
mov eax, dwrod ptr[g_m]; 1
add eax, 1; 2
mov dwrod ptr[g_m], eax; 3
在t1和t2两个线程运行时,由于两个线程用的同一个变量‘g_m’,在运行时,可能在1步,2步或者3步任意一个地方t1被迫阻塞t2开始运行,然后t2 也对应会有这种情况,然后t1继续运行,t2被阻塞,所以就产生了如上的结果,当然如上的结果并没有把全部的情况展示出来。我觉得也够说明问题了,这个时候就需要引出互斥量的使用。
运行如下的代码
#include <iostream>
#include <chrono>
#include <thread>
#include <utility>
#include<mutex>
int g_m = 0;
std::mutex mux;
void foo()
{
mux.lock();
g_m += 1;
std::cout << "g_m:" << g_m << std::endl;
mux.unlock();
}
void bar()
{
mux.lock();
g_m += 2;
std::cout << "g_m:" << g_m << std::endl;
mux.unlock();
}
int main()
{
std::thread t1(foo);
std::thread t2(bar);
t1.join();
t2.join();
return 0;
}
运行结果:
如图可以看出,输出结果的格式正确了,但会有两种结果,两种结果就引出了互斥锁的另一个特点:它只能保证上锁与解锁之间的区域正常的按顺序运行不被别的线程打断,但不能控制两个线程的运行的先后顺序,比如,g_m=1的结果是因为t1线程先运行,而g_m=2的结果是因为t2线程先运行而导致的结果。
其次,对于互斥锁的创建,必须是全局的,要不然无法让线程运行时使用同一个锁变量。
保护锁: 保护锁的使用,使我们可以在线程的函数有返回值时,锁可以保护到值返回之后,就不会产生死锁的问题。
std::mutex mux;
void foo()
{
std::lock_guard<std::mutex> lock(mux);
//mux.lock();
g_m += 1;
std::cout << "g_m:" << g_m << std::endl;
//mux.unlock();//~lock();
}
保护锁的使用,可以看出,直接调用保护锁的构造函数,在函数结束时,自动调用保护锁的析构函数,这样,锁的使用就变得简单而又安全。
条件变量: 利用线程间共享全局变量进行线程间的同步的一种机制。主要包括两个动作:
1、一个线程因等待”条件变量的条件成立“而挂起;
2、另一个线程使”条件成立“,给出信号,从而唤醒被等待的线程。
条件:1、全局变量 2、互斥锁 3、条件变量
使用前引入<condition_variable>头文件。
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "Waiting... \n";
cv.wait(lk);
std::cout << "...finished waiting. i == 1\n";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying falsely...\n";
cv.notify_one(); // 等待线程被通知 i == 0.
// cv.wait 唤醒,检查 i ,再回到等待
std::unique_lock<std::mutex> lk(cv_m);
i = 1;
while (!done)
{
std::cout << "Notifying true change...\n";
lk.unlock();
cv.notify_one(); // 等待线程被通知 i == 1 , cv.wait 返回
std::this_thread::sleep_for(std::chrono::seconds(1));
lk.lock();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join();
t2.join();
}
上面是我在帮助手册上粘贴的代码,具体的运行:
首先,由于t2进程等待了一秒,所以,t1进程先执行,打印出‘Waiting… ’,然后,利用条件变量的wait()函数,这个函数执行两个操作:1、解锁互斥量cv_m 2、将线程t1挂起等待信号函数再次唤醒。由于t1的挂起(阻塞),那么t2线程开始运行,打印出‘Notifying falsely…’,然后,利用条件变量的notify_one()函数,唤醒被wait()函数阻塞的线程,这时两个线程同时都在运行,谁先运行都有可能,但t2再次给cv_m加锁,t1线程就还是处于阻塞状态,t2线程进入while循环,第一次进入循环条件为真,即打印出‘Notifying true change…’,再次解锁cv_m,利用notify_one()函数唤醒t1线程,此时,t1、t2又同时运行,由于t2进程睡眠了1秒,t1线程成功抢占到资源,打印出‘…finished waiting. i == 1’并将done的值改为true,此时t2线程就无法再次进入循环,程序运行结束。
可以看出,wait()函数的作用是解锁互斥量并挂起当前进程直至被notify_one()函数再次唤醒,而notify_one()函数的作用不言而喻,就是用于唤醒被wait()挂起的进程,而当进程被挂起的过多时,notify_one()函数应唤醒哪一个是我还无法解决的问题。
学以致用:运用三个线程循环打印‘ABC’。
#include <utility>
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int iRead = 1;
const int n = 10;
void print_1()//打印'A'
{
std::unique_lock<std::mutex> lk(cv_m);
int i = 0;
while (i < n)
{
while (iRead != 1)
{
cv.wait(lk);
}
std::cout << "A";
iRead = 2;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all();
i++;
}
}
void print_2()//打印'B'
{
std::unique_lock<std::mutex> lk(cv_m);
int i = 0;
while (i < n)
{
while (iRead != 2)
{
cv.wait(lk);
}
std::cout << "B";
iRead = 3;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all();
i++;
}
}
void print_3()//打印'C'
{
std::unique_lock<std::mutex> lk(cv_m);
int i = 0;
while (i < n)
{
while (iRead != 3)
{
cv.wait(lk);
}
std::cout << "C ";
iRead = 1;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all();
i++;
}
}
int main()
{
std::thread t1(print_1);
std::thread t2(print_2);
std::thread t3(print_3);
t1.join();
t2.join();
t3.join();
return 0;
}
太懒了,代码就不解释了
今天这个文章用了半天的时间…
晚安