您的位置:首页 > 新闻 > 资讯 > 大连云app官方下载_关键词优化seo多少钱一年_湖北网站建设制作_福州百度首页优化

大连云app官方下载_关键词优化seo多少钱一年_湖北网站建设制作_福州百度首页优化

2024/12/21 22:49:36 来源:https://blog.csdn.net/yachihaoteng/article/details/142701340  浏览:    关键词:大连云app官方下载_关键词优化seo多少钱一年_湖北网站建设制作_福州百度首页优化
大连云app官方下载_关键词优化seo多少钱一年_湖北网站建设制作_福州百度首页优化

来源:多线程学习

互斥量死锁

假设有两个线程 T1 和 T2,它们需要对两个互斥量 mtx1 和 mtx2 进行访问,而且需要按照以下顺序获取互斥量的所有权:

  • T1 先获取 mtx1 的所有权,再获取 mtx2 的所有权。
  • T2 先获取 mtx2 的所有权,再获取 mtx1 的所有权。

如果两个线程同时执行,就会出现死锁问题。因为 T1 获取了 mtx1 的所有权,但是无法获取 mtx2 的所有权,而 T2 获取了 mtx2 的所有权,但是无法获取 mtx1 的所有权,两个线程互相等待对方释放互斥量,导致死锁。 

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int share_data = 0;
mutex mtx1;
mutex mtx2;
void fun1() {for (int i = 0; i < 100000; ++i) {mtx1.lock();mtx2.lock();mtx1.unlock();mtx2.unlock();}
}void fun2() {for (int i = 0; i < 100000; ++i) {mtx2.lock();mtx1.lock();mtx2.unlock();mtx1.unlock();}
}int main() {thread t1(fun1);thread t2(fun2);t1.join();t2.join();cout << share_data << endl;return 0;
}

解决该问题的方法,可以让两个线程按照相同的顺序获取互斥量的所有权。例如,都先获取 mtx1 的所有权,再获取 mtx2 的所有权,或者都先获取 mtx2 的所有权,再获取 mtx1 的所有权。这样就可以避免死锁问题。

原因在于每个程序在运行的时候,都要先判断mtx1是否被占用,如果被占用就不能继续往下了,也就取不到mtx2了,而如果没有被占用则就可以继续往下。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int share_data = 0;
mutex mtx1;
mutex mtx2;
void fun1() {for (int i = 0; i < 100000; ++i) {mtx1.lock();mtx2.lock();mtx1.unlock();mtx2.unlock();}
}void fun2() {for (int i = 0; i < 100000; ++i) {mtx1.lock();mtx2.lock();mtx2.unlock();mtx1.unlock();}
}int main() {thread t1(fun1);thread t2(fun2);t1.join();t2.join();cout << share_data << endl;return 0;
}

lock_guard 与 unique_lock

lock_guard

lock_guard是 C++ 标准库中的一种互斥量封装类,用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题。 

lock_guard的特点是:

  • 当构造函数被调用时,该互斥量会被自动锁定。

  • 当析构函数被调用时,该互斥量会被自动解锁。

  • lock_guard对象不能复制或移动,因此它只能在局部作用域中使用。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int share_data = 0;
mutex mtx;
void fun() {for (int i = 0; i < 1000000; ++i) {//创建即加锁lock_guard<mutex> lg(mtx);share_data += 1;}
}int main() {thread t1(fun);thread t2(fun);t1.join();t2.join();cout << share_data << endl;return 0;
}

lock_guard还有第二种构造函数,除了传递mtx以外,还可以传递一个参数adopt_lock,表示之前以及上过锁了。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int share_data = 0;
mutex mtx;
void fun() {for (int i = 0; i < 1000000; ++i) {//创建即加锁mtx.lock();lock_guard<mutex> lg(mtx, adopt_lock);share_data += 1;}
}int main() {thread t1(fun);thread t2(fun);t1.join();t2.join();cout << share_data << endl;return 0;
}

unique_lock 

unique_lock 是 C++ 标准库中提供的一个互斥量封装类,用于在多线程程序中对互斥量进行加锁和解锁操作。它的主要特点是可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等。 

unique_lock提供了以下几个成员函数:

  • lock():尝试对互斥量进行加锁操作(这是个手动加锁的操作),如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。
  • try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回false,否则返回true,和lock()不同的是,使用try_lock()不会发生堵塞。
  • try_lock_for(const chrono::duration(Rep, Period>& rel_time) :与上一个函数相比,增加了一个时间参数,也是尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被另一个线程解锁,当前线程成功加锁,或者超过了给定的等待时间。
  • try_lock_until(const chrono::time_point<Clock, Duration>& abs_time):和上一个函数效果类似,不同的在于,增加的参数是一个时间点,效果是对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
  • unlock():对互斥量进行解锁操作(手动解锁)
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int share_data = 0;
//由于后续要使用到时间操作,因此这里我们需要使用到时间锁
timed_mutex mtx;
void fun() {for (int i = 0; i < 10; ++i) {//增加defer_lock参数,表示创建锁,但是不进行枷锁操作unique_lock<timed_mutex> lg(mtx, defer_lock); //try_lock_for返回的是一个bool值,成功加锁返回true,否则返回falseif (lg.try_lock_for(chrono::seconds(1))) {share_data += 1;//表示当前线程休眠2sthis_thread::sleep_for(chrono::seconds(2));}}
}int main() {thread t1(fun);thread t2(fun);t1.join();t2.join();cout << share_data << endl;return 0;
}

unique_lock提供了以下几个构造函数:

  • unique_lock() noexcept = default:默认构造函数,创建一个未关联任何互斥量的unique_lock对象。
  • explicit unique_lock(mutex_type& m):构造函数,使用给定的互斥量m 进行初始化,并默认对该互斥量进行枷锁操作。(只支持显示构造)
  • unique_lock(mutex_type& m, defer_lock_t) noexcept:构造函数,使用给定的互斥量m 进行初始化,但是不对该互斥量进行加锁操作。
  • unique_lock(mutex_type& m, try_lock_t) noexcept:构造函数,使用给定的互斥量m 进行初始化,并尝试对该互斥量进行加锁,如果加锁失败,则创建的unqiue_lock 对象不与任何互斥量关联。
  • unique_lock(mutex_type& m, adopt_lock_t) noexcept:构造函数,使用给定的互斥量m 进行初始化,并假设互斥量已经被当前线程成功加锁。

总结:相比于lock_guard,unqiue_lock使用上非常灵活方便。


call_once与其使用场景 

该使用场景主要就是单例设计模式。单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。

代码实现:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;class Log {
public:Log() {};//单例模式,类只能有一个对象,因此我们需要禁用其拷贝构造和等于号Log(const Log& log) = delete;Log& operator=(const Log& log) = delete;//全局只需要一个,我们可以采用静态构造的方法//static Log& GetInstance() {//	static Log log; //饿汉单例模式//	return log;//}static Log& GetInstance() {static Log* log = nullptr; //懒汉模式if (!log) log = new Log;return *log;}void PrintLog(string msg) {cout << __TIME__ << " " << msg << endl;}
};int main() {Log::GetInstance().PrintLog("这里有问题");return 0;
}

上述中,我们创建了一个日志类,并给了懒汉模式和饿汉模式两种静态构造方法。 由于静态局部变量只会被初始化一次,因此该实现可以确保单例实例只会被创建一次。

但是,该实现并不是线程安全的。如果多个线程同时调用 getInstance() 函数,可能会导致多个对象被创建,从而违反了单例模式的要求。

为例解决该问题,我们可以使用call_once来实现一次性初始化,从而确保单例实例只会被创建一次。

call_once函数的原型为:void call_once(std::once_flag& flag, Callable&& func, Args&&... args);

其中`flag` 是一个 `std::once_flag` 类型的对象,用于标记函数是否已经被调用;`func` 是需要被调用的函数或可调用对象;`args` 是函数或可调用对象的参数。

`std::call_once` 的作用是,确保在多个线程中同时调用 `call_once` 时,只有一个线程能够成功执行 `func` 函数,而其他线程则会等待该函数执行完成。

class Singleton {
public:static Singleton& getInstance() {std::call_once(m_onceFlag, &Singleton::init);return *m_instance;}    void setData(int data) {m_data = data;}    int getData() const {        return m_data;}
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;    static void init() {m_instance.reset(new Singleton);}    static std::unique_ptr<Singleton> m_instance;    static std::once_flag m_onceFlag;    int m_data = 0;
};
std::unique_ptr<Singleton> Singleton::m_instance;
std::once_flag Singleton::m_onceFlag;

在这个实现中,我们使用了一个静态成员变量 m_instance 来存储单例实例,使用了一个静态成员变量 m_onceFlag 来标记初始化是否已经完成。在 getInstance() 函数中,我们使用 std::call_once 来调用 init() 函数,确保单例实例只会被创建一次。在 init() 函数中,我们使用了 std::unique_ptr 来创建单例实例。

使用 std::call_once 可以确保单例实例只会被创建一次,从而避免了多个对象被创建的问题。此外,使用 std::unique_ptr 可以确保单例实例被正确地释放,避免了内存泄漏的问题。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com