c++多线程编程(含例子)

多线程编程知识

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 
class ThreadSafeQueue{
public:
         void Insert(T value);
         void Popup(T &value);
         bool Empety();
 
private:
       mutable std::mutex mut_;
       std::queue que_;
       std::condition_variable cond_;
};

template 
void ThreadSafeQueue::Insert(T value){
    std::lock_guard lk(mut_);
    que_.push_back(value);
    cond_.notify_one();
}
 
 
template 
void ThreadSafeQueue::Popup(T &value){
    std::unique_lock lk(mut_);
//lambda表达式,是一种匿名函数。方括号内表示捕获变量。
//当lambda表达式返回true时(即queue不为空),wait函数会锁定mutex。
//当lambda表达式返回false时,wait函数会解锁mutex同时会将当前线程置于阻塞或等待状态,
//等待被唤醒(Insert函数在插入数据后有唤醒操作)。
    cond_.wait(lk, [this]{return !que_.empety();}); // 这里[this]表示this为被捕获的参数(输入)
    value = que_.front();
    que_.pop();
}
 
 
template 
bool ThreadSafeQueue::Empty() const{
        std::lock_guard 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的typedef,在C++11中已经禁用。

2.信号量

信号量讲解参考网站

名称 作用域 上锁时
信号量 进程间或线程间(linux仅线程间) 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一
互斥锁 线程间 只要被锁住,其他任何线程都不可以访问被保护的资源成功后否则就阻塞

条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。

信号量通俗释义

以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
抽象的来讲,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了由信号量守护的资源。

互斥量和信号量的区别

  1. 互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

以上区别是主要想记住的。

note:信号量可以用来实现互斥量的功能

  1. 互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是0/1,也可以完成一个资源的互斥访问。

  1. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

题目:

1、如何让两个线程交替打印数据

这里主要用到了条件量condition_variable, 互斥量mutex, unique_lock自动锁

重点注意这里的condition.wait 和 condition.notify_one();
在一个线程中执行了wait同时条件不满足(wait函数的第二个参数为返回值为bool类型的函数)后就进入阻塞,直到被其他的线程执行notify才会苏醒,否则就算后面条件满足了,也无法继续执行,仍旧处于休眠状态。

#include 
#include 
#include 
#include 

using namespace std;

std::mutex mtx;
condition_variable cond;
bool flag = true;


void thread1() {
    while (true) {
        unique_lock lck(mtx);
        cond.wait(lck, [] { return flag; });
        cout << "thread1" << endl;
        flag = false;
        cond.notify_one();
        this_thread::sleep_for(chrono::seconds(1));
    }
}

void thread2() {
    while (true) {
        unique_lock lck(mtx);
        cond.wait(lck, [] { return !flag; });
        cout << "thread2" << endl;
        flag = true;
        cond.notify_one();
        this_thread::sleep_for(chrono::seconds(1));
    }
}

int main()
{
    thread t1(thread1);
    thread t2(thread2);
    t1.join();  // 这里是避免主线程退出导致其他子线程也结束了,阻塞主线程直到t1运行完成
    return 0;
}

你可能感兴趣的:(c++多线程编程(含例子))