c++多线程编程
多线程编程知识
[toc]
1.互斥锁
当有一个链表,这个链表需要两个线程互斥访问时,我们就需要互斥锁。为什么呢?因为当一个线程要去使用这个链表时,首先他得先获得锁,一旦发现锁已经被别的线程占用,则无法获得锁将阻塞等待互斥锁被别人解锁,当然也有办法不阻塞,一旦无法获得锁,则直接返回。
1.1 C++11互斥锁的介绍
方法1:直接操作 mutex,即直接调用 mutex 的 lock / unlock 函数
boost::mutex mutex;
mutex.lock();
mutex.unlock();
方法2:使用 lock_guard 自动加锁、解锁。原理是 RAII,和智能指针类似
方法3:使用 unique_lock 自动加锁、解锁
对比lock_guard和unique_lock
std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。
template <typename T>
class ThreadSafeQueue{
public:
void Insert(T value);
void Popup(T &value);
bool Empety();
private:
mutable std::mutex mut_;
std::queue<T> que_;
std::condition_variable cond_;
};
template <typename T>
void ThreadSafeQueue::Insert(T value){
std::lock_guard<std::mutex> lk(mut_);
que_.push_back(value);
cond_.notify_one();
}
template <typename T>
void ThreadSafeQueue::Popup(T &value){
std::unique_lock<std::mutex> lk(mut_);
//lambda表达式,是一种匿名函数。方括号内表示捕获变量。
//当lambda表达式返回true时(即queue不为空),wait函数会锁定mutex。
//当lambda表达式返回false时,wait函数会解锁mutex同时会将当前线程置于阻塞或等待状态。
cond_.wait(lk, [this]{return !que_.empety();});
value = que_.front();
que_.pop();
}
template <typename T>
bool ThreadSafeQueue::Empty() const{
std::lock_guard<std::mutex> lk(mut_);
return que_.empty();
}
如果只是为了保证数据同步,那么lock_guard完全够用;
如果除了同步,还需要使用condition进行阻塞时,那么就需要用unique_lock。
std::unique_lock相对std::lock_guard更灵活的地方在于在等待中的线程如果在等待期间需要解锁mutex,并在之后重新将其锁定。而std::lock_guard却不具备这样的功能。
boost还要一个boost::mutex::scoped_lock,这个是boost::unique_lock<boost::mutex>的typedef,在C++11中已经禁用。
2.信号量
名称 | 作用域 | 上锁时 |
---|---|---|
信号量 | 进程间或线程间(linux仅线程间) | 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一 |
互斥锁 | 线程间 | 只要被锁住,其他任何线程都不可以访问被保护的资源成功后否则就阻塞 |
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。