一条龙建站多少钱,可以推广的软件,银川网站设计联系电话,手机做兼职的网站有哪些互斥量 mutex
• 大部分情况#xff0c;线程使用的数据都是局部变量#xff0c;变量的地址空间在线程栈空间 内#xff0c;这种情况#xff0c;变量归属单个线程#xff0c;其他线程无法获得这种变量。
• 但有时候#xff0c;很多变量都需要在线程间共享#xff0c;这… 互斥量 mutex
• 大部分情况线程使用的数据都是局部变量变量的地址空间在线程栈空间 内这种情况变量归属单个线程其他线程无法获得这种变量。
• 但有时候很多变量都需要在线程间共享这样的变量称为共享变量可以通 过数据的共享完成线程之间的交互。
• 多个线程并发的操作共享变量会带来一些问题。 例如想进 单人自习室要拿钥匙申请的是同一把锁 1. 锁的接口
定义锁
锁也是一个数据类型它的类型是pthread_mutex_t
初始化
静态分配
pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER不需要销毁
动态分配
函数原型int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数
mutex要初始化的互斥量attr初始化互斥量的属性一般设置为nullptr即可
返回值成功返回0失败返回错误码 对于锁的五个基本使用 注意加锁的时候一定要保证加锁的粒度越小越好
实操
//定义pthread_mutex_t mtx;
//初始化
pthread_mutex_init(mtx, nullptr);
//销毁
pthread_mutex_destroy(mtx);
//给线程上锁int n pthread_mutex_lock(td-_pmtx);
//解锁n pthread_mutex_unlock(td-_pmtx);
示例场景
#define THREAD_NUM 5int tickets 10000;class ThreadData
{
public:ThreadData(const string name, pthread_mutex_t *pm): _name(name), _pmtx(pm){}
public:string _name;pthread_mutex_t *_pmtx;
};void *GetTickets(void *args)
{ThreadData *td (ThreadData *)args;while(true){int n pthread_mutex_lock(td-_pmtx);assert(n 0);if(tickets 0){usleep(1000);printf(%s%d\n, td-_name.c_str(), tickets);tickets--;n pthread_mutex_unlock(td-_pmtx);assert(n 0);}else{n pthread_mutex_unlock(td-_pmtx);assert(n 0);break;}}delete td;return nullptr;
}int main()
{pthread_mutex_t mtx;pthread_mutex_init(mtx, nullptr);// 多线程抢票逻辑pthread_t t[THREAD_NUM];for (int i 0; i THREAD_NUM; i){string name thread;name to_string(i 1);ThreadData *td new ThreadData(name, mtx);pthread_create(t i, nullptr, GetTickets, (void *)td);}for (int i 0; i THREAD_NUM; i){pthread_join(t[i], nullptr);}pthread_mutex_destroy(mtx);return 0;
}
加锁的本质是用时间来换取安全加锁的表现线程对于临界区代码串行执行加锁原则尽量的要保证临界区代码越少越好
加锁和解锁之间称之为 临界区 int n pthread_mutex_lock(td-_pmtx);assert(n 0);if(tickets 0){usleep(1000);printf(%s%d\n, td-_name.c_str(), tickets);tickets--;n pthread_mutex_unlock(td-_pmtx);
申请锁成功了才能往后执行不成功阻塞等待
线程对于锁的竞争能力可能会不同 刚执行完离锁近所以可能会某一线程一直更容易拿到锁我们抢到了票我们会立马抢下一张吗其实多线程还要执行得到票之后的后续动作。usleep模拟纯互斥环境如果锁分配不够合理容易导致其他线程的饥饿问题不是说只要有互斥必有饥饿。适合纯互斥的场景就用互斥 观察员 外面来的必须排队出来的人并不能立马重新申请锁必须排队到队尾 让所有的线程人 )获取锁按照一定的顺序。 按照一定的顺序获取资源--同步
锁本身就是共享资源所以申请锁和释放锁本身就是原子的 临界区中线程可以被切换吗可以切换 在线程被切出去的时候是持有锁被切走的。我不在期间照样没有人能进入临界区访问临界资源对于其他线程来讲一个线程要么么有锁要么释放锁当前线程访问临界区的过程对于其他线程是原子的
引入新的解决方案也会伴随着新的问题在于看重什么对立与统一 锁添加的智慧 2. 锁的原理
加锁
tickets-- 不是原子的会变成 3 条汇编语句。原子一条汇编语句就是原子的
为了实现互斥锁操作,大多数体系结构都提供了 swap 或 exchange 指令,该指令 的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使 是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另 一个处理器的交换指令只能等待总线周期。 现在我们把 lock 和 unlock 的伪代码改 一下 三部分的刷入刷出制作动图如下
线程加载到寄存器与内存实现交换后带着数据及上下文游走 交换的本质把内存中的数据共享交换到 CPU 的寄存器中其实是换到 CPU 此时执行的线程的硬件上下文中数字 1 锁只有一个随着上下文走了
把一个共享的锁让一个线程以一条汇编的方式交换到自己的上下文中--当前线程持有锁了
解锁
将 1 还回去通过 unlock
许多奇奇怪怪的问题是需要程序员你自己来维护的这就设计到加锁位置的智慧了 3. 锁的应用--封装
锁的设置降低耦合度
class Mutex
{
public:Mutex(pthread_mutex_t *lock):lock_(lock){}void Lock(){pthread_mutex_lock(lock_);}void Unlock(){pthread_mutex_unlock(lock_);}~Mutex(){}
private:pthread_mutex_t *lock_;
};
封装利用初始化来对锁进行启动
class LockGuard
{
public:LockGuard(pthread_mutex_t *lock):mutex_(lock){mutex_.Lock();}~LockGuard(){mutex_.Unlock();}
private:Mutex mutex_;
};
调用
#include LockGuard.hpp
while (true){{LockGuard lockguard(lock); // 临时的LockGuard对象 RAII风格的锁if (tickets 0){usleep(1000);printf(who%s, get a ticket: %d\n, name, tickets); // ?tickets--;}elsebreak;}usleep(13); // 我们抢到了票我们会立马抢下一张吗其实多线程还要执行得到票之后的后续动作。usleep模拟}
批注
LockGuard lockguard(lock); // 临时的LockGuard对象 RAII风格的锁
while 之后会自动解锁利用了对象的生命周期
第二个{ 明确出了锁的临界区 4. 可重入 VS 线程安全
概念
线程安全--多线程的并发多个线程并发同一段代码时不会出现不同的结果。常见对全局变 量或者静态变量进行操作并且没有锁保护的情况下会出现该问题。重入--函数的特点同一个函数被不同的执行流调用当前一个流程还没有执行完就有其 他的执行流再次进入我们称之为重入。一个函数在重入的情况下运行结果不会 出现任何不同或者任何问题则该函数被称为可重入函数否则是不可重入函数
可重入的一般也是线程安全的 常见的线程不安全的情况
不保护共享变量的函数函数状态随着被调用状态发生变化的函数返回指向静态变量指针的函数调用线程不安全函数的函数 常见的线程安全的情况
• 每个线程对全局变量或者静态变量只有读取的权限而没有写入的权限一般 来说这些线程是安全的
• 类或者接口对于线程来说都是原子操作
• 多个线程之间的切换不会导致该接口的执行结果存在二义性 常见不可重入的情况
• 调用了 malloc/free 函数因为 malloc 函数是用全局链表来管理堆的
• 调用了标准 I/O 库函数标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构例如 STL
• 可重入函数体内使用了静态的数据结构 常见可重入的情况
• 不使用全局变量或静态变量
• 不使用用 malloc 或者 new 开辟出的空间
• 使用本地数据或者通过制作全局数据的本地拷贝来保护全局数据 可重入与线程安全联系
• 函数是可重入的那就是线程安全的
• 函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题
• 如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别
• 可重入函数是线程安全函数的一种
• 线程安全不一定是可重入的而可重入函数则一定是线程安全的。
• 如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入 函数若锁还未释放则会产生死锁因此是不可重入的。 死锁--代码不会再完后推进了例如 while (true){pthread_mutex_lock(lock); // 申请锁成功才能往后执行不成功阻塞等待。pthread_mutex_lock(lock); // 申请锁成功才能往后执行不成功阻塞等待。if(tickets 0){usleep(1000);...}}
下篇文章将继续讲解~