作者:洪泽湖沟鼠_203 | 来源:互联网 | 2023-09-12 19:22
对于单处理器系统,处理器咋一个单元时间内只能执行一个进程,操作系统系统以极快的速度在多个线程之间进行切换,营造了一种多个进程同时运行的假象。1.一些基本概念:c++中的静态
对于单处理器系统,处理器咋一个单元时间内只能执行一个进程,操作系统系统以极快的速度在多个线程之间进行切换,营造了一种多个进程同时运行的假象。
1. 一些基本概念:
c++中的静态库与动态库:
1. 静态库:*.lib 是指一些已经编译过的代码,在程序运行之前,静态库在编译的时候被放入到可执行文件中。
静态库在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,对应的链接方式称为静态链接。静态库与汇编生成的目标文件(.o文件)一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的归档集合,即很多目标文件经过压缩打包后形成的一个文件。
静态库有两个重大缺点:
1)空间浪费
2)静态链接对程序的更新、部署和发布会带来很多麻烦。一旦程序中有任何模块更新,整个程序就要重新链接,发布给用户。
2. 动态库:*.dll 与静态库不同的是,动态库在程序开始执行后才开始进行链接,可以将许多程序都会用到的函数放入到动态库中。在这样就不必在每个程序中都包含这些函数了,只需在运行时链接一个动态库就可以了。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
一般推荐使用lock_guard()来替代lock/unlock,因为更加安全。lock_guard在构造时会自动锁定互斥量,在退出作用后进行析构时就会自动解锁互斥量,避免忘记unlock操作。
lock_guard用到了RAII技术:
使用局部对象管理资源的技术通常称为“资源获取就是初始化”,即Resource Acquisition Is Initialization 机制。这一机制是Bjarne Stroustrup首先提出的,要解决的是这样一个问题:
在C++中,如果在这个程序段结束时需要完成一些资源释放的工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。于是Bjarne Stroustrup就想到确保 能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。将初始化和资源释放都放到一个包装类中的好处:
a. 保证了资源的正常释放
b. 省去了在异常处理中冗长而重复甚至有些还不一定执行到的清理逻辑,进而确保了代码的异常安全。
c. 简化代码体积。
所在lock_guard在类的构造函数中和会分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放,上面的例子使用lock_guard后会更加简洁:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
mutex g_lock; // 独占互斥量
void func()
{
std::lock_guard locker(g_lock);
cout <<"Enter thread " < std::this_thread::sleep_for(std::chrono::seconds(2));
cout <<"Leaving thread " <}
int main()
{
thread t1(func);
thread t2(func);
thread t3(func);
t1.join();
t2.join();
t3.join();
return 0;
}
2. std::recursive_mutex: 递归互斥量,不带超时功能。
递归锁允许同一线程多次获得该互斥锁,可以解决同一线程需要多次获取互斥量时的死锁的问题,一个线程多次获取同一互斥量时会发生死锁问题:
例如:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
struct Complex
{
std::mutex mutex;
int i;
Complex() : i(2) {}; // 构造函数
void mul(int x)
{
std::lock_guard lock(mutex); // 获取互斥量
i *= x;
}
void div(int y)
{
std::lock_guard lock(mutex); // 获取互斥量
i /= y;
}
void both(int x, int y)
{
std::lock_guard lock(mutex); // 获取互斥量
mul(x);
div(y);
}
};
int main()
{
Complex cmp;
cmp.both(21, 2);
cout < return 0;
}
在主线程中多次获取互斥量,就会发生死锁。因为互斥量已被当前线程获取,无法释放,就会导致这样的问题。
递归锁可以解决这种问题:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
struct Complex
{
std::recursive_mutex mutex;
int i;
Complex() : i(2) {}; // 构造函数
void mul(int x)
{
std::lock_guard lock(mutex); // 获取互斥量
i *= x;
}
void div(int y)
{
std::lock_guard lock(mutex); // 获取互斥量
i /= y;
}
void both(int x, int y)
{
std::lock_guard lock(mutex); // 获取互斥量
mul(x);
div(y);
}
};
int main()
{
Complex cmp;
cmp.both(21, 2);
cout < return 0;
}
但是递归锁会存在如下的缺点:
a. 用到递归锁的多线程互斥处理本身是可以简化的。递归互斥量很容易产生复杂的逻辑,会导致线程同步引起的晦涩的问题。
b. 与非递归锁相比,递归锁的效率会更低。
c. 递归锁没有说明一个线程最多可以重复获得几次互斥量,一旦超过一定的次数,再调用lock就会抛出std::system的错误。
3. std::timed_mutex: 带超时的互斥量,不能递归使用。
timed_mutex在获取锁时增加超时等待功能,因为有时候不知道获取锁需要等待多久,为了不至于一直在等待获取互斥量,可以设置一个超时时间,在超时后还可以做其他的事情。多出的两个接口为:try_lock_for, try_lock_until.
例如:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
void work()
{
std::timed_mutex mutex;
// 定义函数
std::chrono::milliseconds timeout(100); // 100ms
while (true)
{
if (mutex.try_lock_for(timeout)) // 获取到互斥量
{
cout <<"Thread " < std::chrono::milliseconds sleep_time(250);
this_thread::sleep_for(sleep_time);
mutex.unlock(); // 释放互斥量
this_thread::sleep_for(sleep_time);
}
else // 未获取到互斥量 处理其他事务
{
cout <<"Thread " < std::chrono::milliseconds sleep_time(100);
this_thread::sleep_for(sleep_time);
}
}
}
int main()
{
thread t1(work);
thread t2(work);
t1.join();
t2.join();
return 0;
}
------------------------------------------------------------分割线------------------------------------------------------------------