还可以使用 try_lock() 获取互斥锁的所有权并对互斥锁加锁:
和 lock()的区别在于 ,try_lock()不会阻塞线程,lock()会阻塞线程:
互斥锁被锁定之后可以通过 unlock()进行解锁,但是需要注意:只有拥有互斥锁所有权的线程(对互斥锁上锁的线程)才能将其解锁,其他线程没有权限做这件事;
线程同步的目的:使多线程按照顺序依次进行执行临界区代码,对共享资源的访问从并行访问变成线性访问,访问效率降低了但是保证了数据的正确性;
当线程对互斥锁对象加锁,并且执行完临界区代码之后,一定要使用这个线程对互斥锁解锁,否则最终会造成线程的死锁。死锁之后当前应用程序中的所有线程都会被阻塞,并且阻塞无法解除,应用程序也无法继续运行。
注意:
1.在所有线程的任务函数执行完毕之前,互斥锁对象是不能被析构的,一定要在程序中保证对象的可用性;
2.互斥锁的个数和共享资源的个数相等,每一个共享资源对应一个互斥锁对象,与线程数无关;
3.1.2 std::lock_guard
lock_guard 是C++11新增的一个模板类,可以简化互斥锁 lock()和unlock()的写法,同时也更安全。
lock_guard 在使用构造函数构造对象时,会自动锁定互斥量,且在退出作用域后进行析构就会自动解锁,以保证互斥量的正确性,避免忘记 unlock() 而导致的线程死锁。
3.1.3 std::recursive_mutex
递归互斥锁: std::recursive_mutex 允许同一线程多次获得互斥锁,可以用来解决同一线程需要多次获取互斥量时的死锁问题 ,以下案例使用独占非递归互斥量会发生死锁:
在执行到 cal.both(3,4); 调用之后程序会发生死锁,在 both() 中已经对互斥锁加锁了,继续调用 mult() 函数,已经得到互斥锁所有权的线程再次获取这个互斥锁的所有权便会造成死锁 (C++异常退出);使用递归互斥锁 std::recursive_mutex ,其允许一个线程多次获取互斥锁的所有权。
3.1.4 std::timed_mutex
std::timed_mutex 是独占超时互斥锁 ,在获取互斥锁资源是增加一个超时等待功能 ,因为不知道获取锁资源需要等待多长时间,为了保证不一直等待下去,设置一个超时时长,超时后线程会解除阻塞做其他事情。
std::timed_mutex 比 std::mutex 多了两个成员函数:try_lock_for() 和 try_lock_until() :
1.try_lock_for 函数是当线程获取不到互斥锁资源之后,让线程阻塞一定的时间长度;
2.try_lock_until 函数是当线程获取不到互斥锁资源时,让线程阻塞到某一个时间点;
当两个函数返回值:当得到互斥锁所有权后,函数会马上解除阻塞 ,返回true ,如果阻塞的时长用完或达到某时间点后,函数会解除阻塞 ,返回false。
关于递归超时互斥锁 std::recursive_timed_mutex 的使用方式和 std::timed_mutex 是一样的,只不过它可以允许一个线程多次获得互斥锁所有权,而 std::timed_mutex 只允许线程获取一次互斥锁所有权。另外,递归超时互斥锁 std::recursive_timed_mutex 也拥有和 std::recursive_mutex 一样的弊端,不建议频繁使用。
3.1.5 std::unique
std::unique_lock 是 C++ 标准库中的一个互斥量封装类,用于提供对互斥量的更灵活的控制。与 std::lock_guard 类似,std::unique_lock 也是用于管理互斥锁的加锁和解锁,但相比之下,std::unique_lock 提供了更多的功能和灵活性。
std::unique_lock 的基本特点包括:
- 构造和析构:可以在构造时加锁,析构时解锁。
- 手动加锁和解锁:可以手动控制锁的加锁和解锁时机。
- 条件变量支持:可以与条件变量一起使用,支持条件等待。
- 可移动性:支持移动语义。
基本的构造和析构:
手动加锁和解锁:
条件变量的使用:
在这个例子中,std::unique_lock 与条件变量一起使用,通过 myCondVar.wait(myLock) 在等待条件变量时自动释放锁,待条件满足时重新获取锁。
使用 std::adopt_lock 标记:
在这个例子中, 表示 已经拥有锁,而不是在构造函数中进行加锁。
C++11 提供了另一种用于等待的同步机制,能阻塞一个或多个线程,直到收到另一个线程发出的通知或超时时,才能唤醒当前阻塞的线程。条件变量需要和互斥量配合使用。
3.2.1 condition_variable
std::condition_variable 是 C++ 标准库中用于线程间同步的条件变量类。它通常与 std::mutex 和 std::unique_lock 一起使用,以实现线程的等待和通知机制。std::condition_variable 提供了一种等待某个条件为真的机制,当条件不满足时,线程可以等待在条件变量上,而不是忙等待。当其他线程改变了共享数据并满足了条件时,可以通过条件变量通知等待的线程,使其继续执行。
相关函数介绍:
notify_one()函数
它用于通知等待在条件变量上的一个线程,使其从阻塞状态恢复,继续执行。notify_one 不返回任何值,因此它的返回类型是 void。当调用 notify_one 时,它会选择一个等待在条件变量上的线程(如果有的话),唤醒这个线程。如果没有线程在等待,notify_one 不执行任何操作。如果有多个线程在等待,notify_one 会选择其中的一个线程唤醒,但不能确定是哪个线程。
wait()函数
类的 函数是用于在条件不满足的情况下将线程置于阻塞状态,等待其他线程通知满足条件并唤醒它。wait 函数接受一个 std::unique_lock<std::mutex> 对象和一个条件,通常是一个可调用的谓词(Predicate)。wait 会释放 lock 所关联的互斥锁,并将当前线程阻塞直到满足条件(即 pred 返回 true)或者等待被中断(例如,收到信号)。在成功返回时,wait 会重新获取互斥锁并继续执行。
wait_for()函数
std::condition_variable::wait_for 它是在一段时间内等待条件满足的函数,如果超过指定的时间仍未满足条件,则线程会被唤醒。
示例演示:
等待条件满足示例:
在这个例子中,waitForData 函数等待 dataReady 变量变为 true,如果条件不满足,它会阻塞并等待条件变为真。主线程在一定时间后将 dataReady 设置为 true,并通过 myCondVar.notify_one() 发送通知,使 waitForData 中的线程继续执行。
等待超时示例:
在这个例子中,waitForDataOrTimeout 函数等待条件满足,但是如果等待超过3秒仍未满足条件,就会认为是超时。std::condition_variable 在多线程编程中是一个强大的工具,能够有效地实现线程之间的同步。需要注意的是,使用条件变量时通常需要搭配互斥锁(例如 std::mutex)来确保线程安全。
3.2.1 condition_variable_any
std::condition_variable_any 是一个模板类,接受一个 Lockable 参数,表示与条件变量一起使用的互斥锁类型。与 std::condition_variable 不同,std::condition_variable_any 可以与任何满足 Lockable 要求的互斥锁一起使用,例如 std::mutex、std::unique_lock、std::shared_lock 等。
下面是一个使用 std::condition_variable_any的示例:
在这个例子中,std::condition_variable_any 与 std::mutex 一起使用。waitForData 函数等待 dataReady 变量变为 true,在 setDataReady 函数中,dataReady 变为 true 后通过 myCondVar.notify_one(); 唤醒等待的线程。使用 std::condition_variable_any 时,需要确保互斥锁类型满足 Lockable 要求。它提供了一种更灵活的选择,可以在不同类型的互斥锁上使用。
四、线程池的实现
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/1250.html