1.创建线程,等待线程,获取线程id
2.全局变量,局部变量,互斥锁
要让不同的线程访问同一个变量和同一把锁,有两种方法:
2.1方法一
定义全局的变量和全局的锁,这样自然就能访问到。
但全局变量在多个源文件重命名的话,在链接时容易出现链接错误。
2.2方法二
在主线程处定义局部的变量和局部的锁,不过要传引用。
特别注意:在传参的时候要用ref修饰变量和锁,因为C++的线程库只是对底层系统调用的封装。
Linux系统中,系统调用pthread_create要求传进来函数参数要是void*,返回值是void*,下面一个参数是作为该函数的参数,类型是void*。所以C++的线程库要对100,x,mtx封装成结构体,后面还要把这个结构体的每一个成员解析出来交给线程函数去调用,这其中有很多步骤只是值拷贝,而不是引用传参。
所以用ref修饰一下就是强制要求在上述步骤中的传参全部用引用的方式传参,这样多个线程函数才能访问到同一个局部变量。
当然也可以直接传指针,这样底层对指针进行值拷贝也没事。
3.lambda表达式构造线程
方式一:
方式二:
3.1构造多线程
4.解决死锁问题:lock_guard
试想一下,如果有一个线程在lock和unlock之间抛异常了的话,那就不会解锁,就出现了死锁问题。
为了解决死锁问题,C++11封装了一个LockGuard的类。它的构造函数就是把传进来的锁保存起来,然后上锁。析构函数就是解锁。这样一来,就算线程在访问临界区抛异常了,线程结束前也会调用析构函数解锁。这里注意:锁不支持拷贝构造,所以LockGuard的成员变量要用mutex&。
那么如何让新封装出来的锁只锁住临界区呢:
简单定义一个局部域就可以了!花括号是可以定义局部域的,出了一个局部域之后,会自动调用析构函数。当然这个是我们自己封装出来的,调用库中的接口如下:
5.atomic
头文件是<atomic>。
atomic<int> x=0;表示下面对x的一些加减等简单的操作是原子的。
可用于一些小的修改,小的操作,保证线程安全。在部分情况下可代替锁。
这样做的效率和锁的效率差不多。
这样可以直接cout打印,但是要用printf的话,需要x.load().来打印。
6.条件变量
直播样例:
这个程序就是构建出两个线程,一个线程打印偶数一个线程打印奇数,且必须先打印0后打印1,这样来回交替。
其中条件变量是配合flag使用的,一旦调用c.wait()(非原子,需要调用互斥锁),该线程必定会阻塞(且在阻塞前会解锁),等待其他线程唤醒。c.notify()是随机唤醒一个线程,如果没有线程阻塞,那么啥也不干。
unique_lock和lock_guard功能类似。
课件样例:
这是条件变量的第二种等待方式,与第一种类似。第一个参数是锁,在阻塞时会解锁。第二个参数是一个仿函数,它的返回值为真就不阻塞,返回值为假就阻塞。