1、C++多线程介绍C++语言级别的多线程编程=》代码可以跨平台 windows/linux/mac
thread(线程类) mutex(互斥锁) condition_variable(线程间的通信,条件变量) 智能锁:(自动的加锁解锁) lock_guard unique_lock atomic 原子类型 基于CAS操作的原子类型 线程安全的 sleep_for(睡眠)
C++语言层面 thread(底层用的还是下面平台的方法)
windows linux strace ./a.out(程序启动的跟踪打印的命令)
| |
createThread pthread_create
- 可以通过编译器的编译,加个宏,识别当前的操作系统来适配通过语言层面编写thread底层自动调用相应的函数。
- 底层还是调用系统创建的API,只是语言层面加个封装,让用户更加方便使用。
线程内容:
-
1、怎么创建启动一个线程?
std::thread定义一个线程对象,传入线程所需要的线程函数和参数,
线程自动开启 -
2、子线程如何结束
子线程函数运行完成,线程就结束了 -
3、主线程如何处理子线程
t.join() : 等待t线程结束,当前线程继续往下运行
t.detach() : 把t线程设置为分离线程,主线程结束,整个进程结束,所有子线程都自动结束了!
测试结果: -
join(): 主线程会等待子线程运行结束,才继续执行
-
hello thread1会永远运行在main thread之前的,因为主线程join了,在等待子线程。
子线程睡眠:
this_thread(): 是一个namespace,里面有一些方法;
- get_id():获取线程id;
- yield():放弃当前线程这一轮的时间片
- sleep_for():睡眠多长时间
- sleep_until():睡到哪一个时间点
std::chrono():定义了一些和时间相关的常量。
线程函数中可以传入多个参数:
问题:
两种解决方法:
- t.join(): 主线程等待子线程t运行完,主线程继续向下执行;
- t.detach():将子线程t设置为分离线程,就和主线程没有关系了 ,主线程结束,所有子线程都自动结束了;
#include3、线程间互斥—mutex互斥锁和lock_gard#include using namespace std; void threadHandle1(int time) { //让子线程睡眠time秒 std::this_thread::sleep_for(std::chrono::seconds(time)); cout << "hello thread1!" << endl; } void threadHandle2(int time) { //让子线程睡眠time秒ace this_thread是namespace std::this_thread::sleep_for(std::chrono::seconds(time)); cout << "hello thread2!" << endl; } int main() { //创建了一个线程对象,传入一个线程函数(作为线程入口函数), //新线程就开始运行了,没有先后顺序,随着CPU的调度算法执行 std::thread t1(threadHandle1, 2); std::thread t2(threadHandle2, 3); //主线程(main)运行到这里,等待子线程结束,主线程才继续往下运行 t1.join(); t2.join(); //把子线程设置为分离线程,子线程和主线程就毫无关系了 //主线程结束的时候查看其他线程 //但是这个子线程运行完还是没运行完都和这个主线程没关系了 //这个子线程就从这个main分离出去了 //运行程序时也看不到这个子线程的任何输出打印了 //t1.detach(); cout << "main thread done!" << endl; //主线程运行完成,查看如果当前进程还有未运行完成的子线程 //进程就会异常终止 return 0; }
所有线程都在输出,不能串行执行,因为线程都是并行执行的。
可以看到有很多问题。(每次运行结果都存在差异,程序存在竞态条件)
多线程程序 涉及 线程安全问题;
竞态条件: 多线程执行的结果是一致的,不会随着CPU对线程不同的调用顺序,而产生不同的运行结果。
- 不是线程安全的操作!
- 每个线程在1个指令周期之内是要保证完成的,但是在多个指令完全由CPU的调度决定的,线程在运行完每个指令的时候,都有可能CPU的时间片到了,线程阻塞住。等待下一轮我们再轮到这个线程执行,才能把剩余的时间片给到这个线程,线程继续执行下面的指令 。 两个线程可能减完的值是相同的,然后把相同的值写回去了
- 所以,我们要保证这个操作线程安全!!!每次只有1个线程去做减减操作
使用mutex,包含头文件:#include
上面程序还是有问题的:
- 会导致只有一个窗口在卖票;
修改:
- 将加锁解锁挪到while里面;
代码还是有问题的,出现了-1!!!
原因: - 当ticketCount = 1时,此时线程1进入临界区执行卖票,此时ticketCount还未变为0,线程2进入while循环,阻塞在互斥锁上,等待拿锁,当线程1执行完之后,释放锁了,线程2拿到互斥锁,记性执行卖票,ticketCount变为-1。
解决方法: 锁 + 双重判断。
注意:这里我把休眠时间挪到解锁之后,为了cpu充分调度!
-
还是要采用 智能指针的思想;
-
lock_guard,unique_lock;(保证所有线程都能释放锁,防止死锁问题的发生);
-
两者都可以在构造函数中执行mutex的lock加锁操作,在析构函数中执行mutex的unlock函数。
- 将锁封装了,成员变量是一把互斥锁;
- 控制锁的构造和析构,不允许锁进行拷贝构造和赋值重载。
- lock_guard保证所有线程都能释放锁,防止死锁问题的发生,类似scoped_ptr(拷贝构造和赋值函数删除掉)
- lock_guard构造函数直接加锁;
- lock_guard析构函数直接解锁;
lock_guard问题:在函数调用过程中,就用不了了,因为lock_guard不支持拷贝构造和负值重载。
解决方法: 使用unique_lock
unique_lock- 和unique_ptr类似。
- 把带左值引用参数的拷贝构造函数和赋值重载函数删除了。
- 支持带右值引用参数的拷贝构造函数和赋值重载函数(支持 临时对象拷贝构造一个新对象,临时对象给另一个对象赋值)。
unique_lock源码:
右值引用的拷贝构造:
右值引用的赋值重载:
左值引用参数的拷贝构造函数和赋值重载函数删除
成员变量:
指向一把锁的指针
lock()和unlock()方法:
后面再演示unique_lock。
#include#include using namespace std; void threadHandle1(int time) { //让子线程睡眠time秒 std::this_thread::sleep_for(std::chrono::seconds(time)); cout << "hello thread1!" << endl; } void threadHandle2(int time) { //让子线程睡眠time秒ace this_thread是namespace std::this_thread::sleep_for(std::chrono::seconds(time)); cout << "hello thread2!" << endl; } int main() { //创建了一个线程对象,传入一个线程函数(作为线程入口函数), //新线程就开始运行了,没有先后顺序,随着CPU的调度算法执行 std::thread t1(threadHandle1, 2); std::thread t2(threadHandle2, 3); //主线程(main)运行到这里,等待子线程结束,主线程才继续往下运行 t1.join(); t2.join(); //把子线程设置为分离线程,子线程和主线程就毫无关系了 //主线程结束的时候查看其他线程 //但是这个子线程运行完还是没运行完都和这个主线程没关系了 //这个子线程就从这个main分离出去了 //运行程序时也看不到这个子线程的任何输出打印了 //t1.detach(); cout << "main thread done!" << endl; //主线程运行完成,查看如果当前进程还有未运行完成的子线程 //进程就会异常终止 return 0; }



