一、函数介绍
1、构造函数
无参构造函数:
thread thd = thread();
有参构造函数:
template<class Fn, class... Arg>
Fn:可调用对象(函数指针,仿函数,lambda表达式,包装器)
Arg:函数参数包
拷贝构造:
thread(const thread& th) = delete
不希望我们拷贝对象
移动构造:
thread(thread&& th)
支持移动构造
2、获取线程id
新线程因为有对象,所以直接调用函数 get_id()
主线程没有对象,用 this_thread::get_id()
3、其他函数
(1)jionable()
线程是否还在执行,joinable代表的是一个正在执行中的线程。
(2)jion()
该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
(3)detach()
在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离 的线程变为后台线程,创建的线程的"死活"就与主线程无关
4、代码案例
void print(int n)
{
for (int i = 0; i < n; i++)
{
cout << i << endl;
}
}
int main()
{
thread t1(print, 10);
thread t2(print, 15);
cout << "t1.get_id(): " << t1.get_id() << endl;
cout << "t2.get_id(): " << t2.get_id() << endl;
t1.join();
t2.join();
cout << "this thread: " << this_thread::get_id() << endl;
return 0;
}
运行结果:
二、认识锁
1、背景
int x = 0;
void print(int n)
{
for (int i = 0; i < n; i++)
{
x++;
}
}
int main()
{
thread t1(print, 10000);
thread t2(print, 15000);
t1.join();
t2.join();
cout << x << endl;
return 0;
}
预期结果应该是25000
这是因为多线程编程需要确保线程安全问题。
首先要明白线程拥有自己独立的栈结构,但对于全局变量等临界资源,是直接被多个线程共享的。这就会导致代码中的操作如果不是原子的线程(如 x++)安全就无法保障。
具体细节详见http://t.csdnimg.cn/zOevq
2、锁
(1)认识函数
引入锁的概念,如果一个线程竞争到锁,就会进入锁内部执行临界区代码,此时即使被切换,其他线程依然不会进入临界区,只能在临界区外等待锁被释放。
lock:竞争锁函数,如果竞争到锁就会返回,此时就可以向下执行代码。
unlock:解锁函数,执行完临界区代码解锁。
(2)代码案例
int x = 0;
mutex mtx;
void print(int n)
{
//加锁建议在循环外
mtx.lock();
for (int i = 0; i < n; i++)
{
x++;
}
mtx.unlock();
}
int main()
{
thread t1(print, 10000);
thread t2(print, 15000);
t1.join();
t2.join();
cout << x << endl;
return 0;
}
运行结果:
由于加锁之后能保证操作的原子性,结果一定是准确的。
int main()
{
int x = 0;
mutex mtx;
//先创建空的线程
vector<thread> ths(3);
auto func = [&](int n) {
mtx.lock();
for (int i = 0; i < n; i++)
{
x++;
}
mtx.unlock();
};
for (auto& th : ths)
{
//构造匿名对象,由于之前创建了空的线程,
//这里调用 operator= 移动赋值
th = thread(func, 10000);
}
for (auto& th : ths)
{
th.join();
}
cout << x << endl;
return 0;
}
运行结果:
3、其他锁类型
(1)recursive_mutex
递归互斥锁,这把锁主要用来递归加锁的场景中,因为递归会引起死锁问题。
为什么会出现死锁?
因为当前在进入递归函数前,申请了锁资源,进入递归函数后(还没有释放锁资源),再次申请锁资源,此时就会出现锁在我手里,但我还申请不到的现象,也就是死锁。
解决这个 死锁 问题的关键在于 自己在持有锁资源的情况下,不必再申请,此时就要用到recursive_mutex
(2)timed_mutex
时间互斥锁,这把锁中新增了定时解锁的功能,可以在程序运行指定时间后,自动解锁(如果还没有解锁的话)
(3)recursive_time_mutex
递归时间互斥锁,就是对timed_mutex
时间互斥锁做了递归方面的升级,使其在面对递归场景时,不会出现死锁。
(4)RAII风格锁
a、lock_guard
锁守卫,只用lock, unlock会导致抛异常后难以解决死锁问题,lock_guard是一个类模板,构造时加锁,析构时解锁,如果执行管理代码的一部分可以加局部域 {}
b、unique_lock
特殊锁,类似于lock_guard,但是支持手动加锁解锁。
4、原子操作
是一个类模板,里面封装了大部分内置类型的++ -- ^ 等原子性操作,这样可以不用加锁来实现内置类型的一些原子操作。
内置类型如下:
获取参数用 load 函数
int main()
{
vector<thread> vthd;
int n;
cin >> n;
vthd.resize(n);
atomic<int> x = 0;
mutex mtx;
auto func = [&](int n) {
//mtx.lock();
// 局部域
{
//lock_guard<mutex> lock(mtx);
for (size_t i = 0; i < n; i++)
{
++x;
}
//mtx.unlock();
}
};
for (auto& thd : vthd)
{
// 移动赋值
thd = thread(func, 100000);
}
for (auto& thd : vthd)
{
thd.join();
}
printf("%d\n", x.load());
return 0;
}
运行结果: