学习C++11,根据网上资料的知识总结。
1. 线程创建
1.1 初始函数
#include <iostream>
#include <thread>
void myfunc(int &a)
{
cout << "a in myfunc:" << a++ << endl;
}
int main()
{
int a = 1;
std::thread mythread(myfunc, std::ref(a)); //带引用参数
mythread.join();//阻塞
cout << "a in main:" << a << endl;
system("pause");
return 0;
}
运行结果:
1.2 类对象
需要重载()运算符,不然编译错误。
#include <iostream>
#include <thread>
class Task
{
static int a;
private:
int v;
public:
Task()
{
v = a++;
}
public:
void operator()() //()重载
{
cout << "v:" << v<< endl;
}
};
int Task::a = 0;
int main()
{
for (int i = 0; i < 4; i++)
{
Task task;
thread t(task);
t.detach();
}
system("pause");
return 0;
}
运行结果不唯一,因为没有阻塞:
1.3 Lambda表达式
#include <thread>
int main()
{
auto myLambda = [](int *a){
for (int i = 0; i < 10; ++i)
{
cout << *a << endl;
}
};
[myLambda]{
int a = 10;
thread mythread(myLambda,&a);
mythread.detach();
}();
system("pause");
return 0;
}
运行结果:
在Lambda线程中使用了局部变量a的指针,并且将该线程的运行方式设置为detach。这样,在lambda表达式执行结束后,变量a已经被销毁,但后台运行的线程仍然在使用已销毁变量a的指针,因此后面输出的值都是错误的。
以detach方式执行线程时,最好将线程访问的局部数据复制到线程的空间(使用值传递),确保线程没有使用局部变量的引用或者指针。若一定要用,除非你能肯定该线程会在局部作用域结束前执行结束。使用join方式(阻塞)就不会出现这种问题,它会在作用域结束前完成退出。
1.4 std::async
std::async是基于任务的,内部有调度器,比线程更高级别的抽象,可以看作是thread + packaged_task的封装。
原型 | async(std::launch::async | std::launch::deferred, f, args...) | |
第一个参数 | std::launch::deferred | 延迟调用,延迟到future对象调用get()或者wait()的时候才执行线程函数f |
std::launch::async | 强制创建新线程,可能失败 | |
std::launch::async |std::launch::deferred | 默认值,系统会自行决定是异步(创建新线程)还是同步(不创建新线程)方式运行 |
std::future与std::async配合使用。std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次的对象。在它之上高一层的是std::packaged_task和std::promise,它们内部都有future以便访问异步操作结果。std::packaged_task包装的是一个异步操作(函数),std::promise包装的是一个值;都是为了方便异步操作的。
future_status三种状态 | |
deferred | 异步操作还没开始 |
ready | 异步操作已经完成 |
timeout | 异步操作超时 |
获取结果三种方式 | |
get | 等待异步操作结束并返回结果 |
wait | 等待异步操作结束,没有返回值 |
wait_for | 超时等待返回结果 |
代码示例:
#include <future>
#include <iostream>
using namespace std;
int main()
{
future<int> future1 = async(launch::async, []() { return 6; });
cout << future1.get() << endl; //output: 6
future<void> future2 = async(launch::async, []() { cout << 8 << endl; });
cout << "before wait" << endl;
future2.wait(); //output: 8
cout << "after wait" << endl;
future<int> future3 = async(launch::async, []() {
this_thread::sleep_for(chrono::seconds(3));
return 9;
});
cout << "waiting...\n";
future_status status;
do {
//查询状态
status = future3.wait_for(chrono::seconds(1));
if (status == future_status::deferred) {
cout << "deferred\n";
}
else if (status == future_status::timeout) {
cout << "timeout\n";
}
else if (status == future_status::ready) {
cout << "ready!\n";
}
} while (status != future_status::ready);
cout << "result is " << future3.get() << endl;
}
运行结果:
before wait后面的数字8是future2输出的,还没来得及换行,说明确实是多线程在输出。
2. 线程同步
2.1 互斥锁
锁类型 | 作用 |
std::mutex | 独占的互斥锁,不能递归使用 |
std::timed_mutex | 带超时的独占互斥锁,不能递归使用 |
std::recursive_mutex | 递归互斥锁,不带超时功能 |
std::recursive_timed_mutex | 带超时的递归互斥锁 |
基本用法如下,建议使用C++11新增的模板类lock_guard,可以简化互斥锁 lock()和unlock()的写法,同时也更安全。还有一个模板类是unique_lock,基本用法和lock_guard一样,提供了更多构造函数,但更占资源。
#include <mutex>
std::mutex _mutex;
_mutex.lock();//加锁,阻塞
_mutex.unlock();//解锁
_mutex.try_lock();//尝试加锁,成功返回bool,失败返回false不阻塞
或
std::lock_guard<std::mutex> lock_mtx(_mutex);
2.2 条件变量
条件变量需要和互斥量配合使用,属于另一种用于等待的同步机制,能阻塞一个或多个线程,直到收到另一个线程发出的通知或超时时,才能唤醒当前阻塞的线程。
使用 | |
condition_variable | 配合 std::unique_lock<std::mutex> 进行 wait 操作,也就是阻塞线程的操作 |
conditon_variable_any | 2.1里的四种锁 |
2.3 自旋锁
自旋锁是一种忙等待形式的锁,会在用户态不停的询问锁是否可以获取(获取不到一直循环),不会陷入到内核态中,所以更加高效,但是可能会对CPU资源造成浪费。C++11中没有直接提供自旋锁的实现,但提供了原子操作的实现,可以借助原子操作实现简单的自旋锁。
相比互斥锁,自旋锁效率更高,但是长时间的自旋可能会使CPU得不到充分的应用。在临界区代码较少,执行速度快的时候应该使用自旋锁。而互斥锁不会浪费CPU资源,在无法获得锁时使线程阻塞,将CPU让给其他线程使用。对于等待资源时间较长的场景,应该用互斥锁。
代码示例:
#include <iostream>
#include <thread>
#include <atomic>
std::atomic_flag flag;
int a = 0;
void foo()
{
for (int i = 0; i < 100; ++i)
{
while (flag.test_and_set())
{
}//加锁
a += 1;
flag.clear();//解锁
}
}
int main()
{
flag.clear();//初始化为clear状态
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
std::cout << a << std::endl;
return 0;
}
a. test_and_set:返回该atomic_flag对象当前状态,检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false。
b. clear:清除atomic_flag对象的标志位,即设置atomic_flag的值为false。