简介
在项目开发中必然会遇到多线程的应用,像Java中的Thread类一样,C++ 11后添加了新的线程库std:thread
用法
格式
#include<thread>
std::thread thread_object(callable)
其中callable可以是以下三个中的任何一个:
- 函数指针
- 函数对象
- lambda 表达式
简单例子
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
void f(int num) // 1 函数指针
{
for (int i = 0; i < num; i++)
{
cout << "线程使用函数指针作为callable\n";
Sleep(100);
}
}
class thread_obj
{ // 2 函数对象
public:
void operator()(int num) //()操作符重载
{
for (int i = 0; i < num; i++)
{
cout << "线程使用函数对象作为callable\n";
Sleep(100);
}
}
};
int main()
{
thread th1(f, 3);
th1.join(); //阻塞主线程
thread th2(thread_obj(), 3);
th2.detach(); //不阻塞主线程
auto f = [](int x) { // 3 Lambda 表达式
for (int i = 0; i < x; i++)
{
cout << "线程使用 lambda 表达式作为callable\n";
Sleep(100);
}
};
thread th3(f, 3);
th3.detach(); //不阻塞主线程
for (int i = 0; i < 3; i++)
{
cout << "主线程运行中!\n";
Sleep(100);
}
return 0;
}
//运行结果:
线程使用函数指针作为callable
线程使用函数指针作为callable
线程使用函数指针作为callable
主线程运行中!
线程使用函数对象作为callable
线程使用 lambda 表达式作为callable
主线程运行中!
线程使用函数对象作为callable
线程使用 lambda 表达式作为callable
线程使用 lambda 表达式作为callable
线程使用函数对象作为callable
主线程运行中!
从上例可以看出:
- 函数指针、函数对象、lambda 表达式,都可以作为thread类创建时候的第一个参数。
- 线程可以调用join()或者detach()启动线程,区别是join()是同步的会阻塞当前线程,而detach()是异步的会不阻塞。
- 对于注释2的函数对象,是指即一个重载了括号操作符“()”的对象。此对象使用时候和函数很像。
同步
和Java多线程一样,当开启多个线程处理一个共享变量的时候,由于CPU分配给线程的随机性,会出现数据没有按照预想的顺序处理,出现不同步的问题,如下例:
#include <iostream>
#include <thread>
#include <windows.h>
using namespace std;
int total = 10;
void run()
{
while (total > 0)
{
cout << total << endl;
total--;
Sleep(10);
}
}
int main()
{
thread task1(run);
thread task2(run);
task1.detach();
task2.detach();
system("pause");
}
//输出:
10
9
8
8
6
5
4
3
2
2
Press any key to continue . . .
可以看出,当开启两个线程执行run()的时候,因为线程执行的不确定性,同时操作total变量,会导致total没有按顺序递减。在Java中,我们可能会用synchronized关键字来处理这种情况,而在C++中也有类似的方法处理线程同步问题。
mutex对象
Mutex 又称互斥量,定义在std::mutex中,可以通过互斥量的lock和unlock来实现线程的同步和互斥
例:
#include <iostream>
#include <thread>
#include <windows.h>
#include <mutex>
using namespace std;
mutex mu; //线程互斥对象
int total = 10;
void run()
{
while (total > 0)
{
mu.lock(); //同步数据锁
cout << total << endl;
total--;
Sleep(10);
mu.unlock(); //同步数据锁
}
}
int main()
{
thread task1(run);
thread task2(run);
task1.detach();
task2.detach();
system("pause");
}
//输出
10
9
8
7
6
5
4
3
2
1
0
Press any key to continue . . .
mutex的主要方法就是lock和unloc函数:
- lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
- 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
- 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
- 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
- unlock(), 解锁,释放对互斥量的所有权。
上例是mutex的基本用法,mutex还有其他几个类型,分别针对不同的场景
- std::mutex,最基本的 Mutex 类。
- std::recursive_mutex,递归 Mutex 类。
- std::time_mutex,定时 Mutex 类。
- std::recursive_timed_mutex,定时递归 Mutex 类。