8、cpp多线程

28 阅读2分钟

thread

语言层面实现了线程,则支持了跨平台,由底层决定具体调用哪种函数来创建线程(linux下使用pthread_create()) STL中的容器都不是线程安全的,所以一般需要自己封装这些容器,然后再在多个线程间共享

  • std::thread t1(函数对象/指针,参数);
  • t1.join();//主线程等待t1子线程结束,主线程继续向下执行
  • t1.detach();//t1子线程分离,主线程结束,整个进程结束,所有子线程被杀死

同步与互斥

mutex

  • 临界区:多线程间读写共享资源的代码片段(如全局变量、内存、文件等)
  • 对临界区上锁
  • 存在共享变量判断时,最好使用锁加双重判断,能增大并发 image.png
  • std::mutex mtx;
  • mtx.lock();
  • mtx.unlock();//不释放会可能导致死锁

lock_guard

如果在临界区退出了,那锁就不会释放,导致出现死锁 采用与智能指针类似思路,使用栈对象来管理锁,出作用域后栈对象析构的同时调用释放锁的函数 不允许复制,也不存在移动语句,所以不能进行函数之间的传递 image.png

  • std::mutex mtx;
  • lock_guard<std::mutex> lock(mtx);//构造函数中上锁,析构函数中释放锁

unique_lock

也不允许复制,但是存在移动语义,允许将锁转移 也是在析构时会调用unlock()

  • std::mutex mtx;
  • unique_lock<std::mutex> lck(mtx);
  • lck.lock();
  • lck.unlock();

condition_variavble

  • 进程间同步机制,两个进程都需要访问临界变量,但是两者又有牵制,比如生产和消费,由于消费者进入临界区已经上了锁,就必须在内部释放锁后等待生产者进入临界区生成,生成完后再告知消费者;
  • 传入的是unique_lock(),而不是简单的mutex;
  • 使用wait后会释放锁,并阻塞在条件变量上,等待其他线程通知,通知之后,会立即加锁然后向下执行,但是由于生产者生成了一个,可能唤醒多个消费者,所以需要唤醒后依然判断是否为空,为空代表被其他消费者消费,则本消费者又该进入阻塞态;
  • std::mutex mtx;
  • std::condition_variable cv;
  • std::unique_lock<std::mutex> lck(mtx);
  • cv.wait(lck);//这里是引用传递
  • cv.notify_one();
  • cv.notify_all();

atomic

互斥锁开销比较大的,如果临界区操作简单,则不建议使用互斥锁 atomic就是一个类模板,重载了部分操作符,如++等,保证了原子性,且不允许拷贝和移动 线程访问全局变量存在cpu缓存,如果没有感知该变量变化(如声明了const或其他线程修改),则直接从该缓存中读取,导致其他线程修改后对本线程不可见而出现问题,使用关键字volatile来确保每次读取都是从内存

  • `std::atomic value(10)