公司网站备案信息查询,小说投稿赚钱的网站,公司注册费用大概多少钱,南宁百度seo排名价格互斥的相关概念
共享资源#xff1a;指多个进程或线程可以共同访问和操作的资源临界资源#xff1a;被保护的共享资源就叫做临界资源临界区#xff1a;每个线程内部#xff0c;访问临界资源的代码#xff0c;就叫做临界区互斥#xff1a;任何时刻#xff0c;互斥保证有…互斥的相关概念
共享资源指多个进程或线程可以共同访问和操作的资源临界资源被保护的共享资源就叫做临界资源临界区每个线程内部访问临界资源的代码就叫做临界区互斥任何时刻互斥保证有且只有一个执行流进⼊临界区访问临界资源通常对临界资源起保护作用原子性不会被任何调度机制打断的操作该操作只有两态要么完成要么未完成。
一个不加保护的Demo
这里使用多个线程共同执行上面的方法代码很简单。但是运行结果怎么出现了负数等于0不就直接break了吗 原因有以下两点 代码if(XXXX)不是原子操作ticketnum--也不是原子操作所有的线程在尽可能多的进行调度切换执行 --- 线程或者进程什么时候会切换 a.时间片耗尽b.更高优先级的进程要调度c.通过sleep从内核返回用户时会进行时间片是否到达的检测进而导致切换 当我们执行上述代码时每个线程都要这样执行上面的逻辑但cpu的寄存器只有一套但是寄存器中的数据有多套且数据为线程私有。由于ticketnum--操作不是原子的即将ticketnum的值移动到CPUCPU做运算再将结果写回内存。共三步。当一个线程正走到以上逻辑的第二步时正准备判断此时这个线程被切换了一旦被切换当前线程在寄存器中数据都会保存下来等在被切回来的时候再恢复 当票数为1时a线程会做判断符合逻辑进入if走到usleep语句此时b线程也进来了a将寄存器中的数据带走此时b线程见到的票数也是1b线程也符合逻辑进入if也会走到usleep同样的c和d线程都会做以上线程的动作都会进入if。当a过了usleep时间会执行--操作1.重读数据2.--数据3.写回数据此时票数为0了同样的bcd线程也会做--因为它们已经进入了if中。最后就导致票数为-2的情况了。
互斥量mutex
在Linux中互斥量就是锁。
要解决上述多线程并发引起的安全问题我们只需在进入临界区之前加上一把锁就可以完美解决。 互斥量(锁)的相关接口
pthread_mutex_init: 初始化互斥锁。
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER;// 使用宏值初始化(全局)mutex指向要初始化的互斥锁对象的指针。 attr指定互斥锁属性的对象如果传递NULL则使用默认的互斥锁属性。 pthread_mutex_init 函数若调用成功会返回 0。若发生错误会返回一个非零的错误码。 pthread_mutex_destroy: 销毁互斥锁。
int pthread_mutex_destroy(pthread_mutex_t *mutex);pthread_mutex_lock: 锁定互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);pthread_mutex_unlock: 解锁互斥锁。 int pthread_mutex_unlock(pthread_mutex_t *mutex);锁接口的使用
全局锁
// 定义一个全局锁
pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER;
int ticketnum 10000; // 共享资源,临界资源void Ticket()
{while (true){pthread_mutex_lock(lock); // 加锁if (ticketnum 0){usleep(1000);printf(get a new ticket, id: %d\n, ticketnum--);pthread_mutex_unlock(lock); // 解锁}else{pthread_mutex_unlock(lock); // 解锁break;}}
}
int main()
{// 创建多线程的逻辑调用Tichetreturn 0;
} 局部锁
使用ThreadData接收参数包括锁的接收保证每一个线程都能看到同一把锁
#include iostream
#include string
#include pthread.h
#include functional
#include sys/types.h
#include unistd.hnamespace ThreadModule
{// 要传递的参数struct ThreadData{ThreadData(const std::string name, pthread_mutex_t *lock_ptr): _name(name), _lock_ptr(lock_ptr){}std::string _name;pthread_mutex_t *_lock_ptr;};// 执行任务的方法using func_t std::functionvoid(ThreadData*);// 线程状态-枚举enum class TSTATUS{NEW,RUNNING,STOP};class Thread{private:// 成员方法具备this指针置为static之后就不具备this指针了static void *Routine(void *args){// t就拿到了this指针Thread *t static_castThread *(args);t-_status TSTATUS::RUNNING;t-_func(t-_td); // 就可以执行相应的类内方法了return nullptr;}public:// 线程要执行的方法直接传进来Thread(const std::string name, func_t func, ThreadData* td): _name(name), _func(func), _td(td), _status(TSTATUS::NEW), _joinable(true){}bool Start(){if (_status ! TSTATUS::RUNNING){int n ::pthread_create(_tid, nullptr, Routine, this); // 将this指针通过参数传过去if (n ! 0)return false;return true;}return false;}bool Stop(){if (_status TSTATUS::RUNNING){int n ::pthread_cancel(_tid);if (n ! 0)return false;_status TSTATUS::STOP;return true;}return false;}bool Join(){if (_joinable){int n ::pthread_join(_tid, nullptr);if (n ! 0)return false;_status TSTATUS::STOP;return true;}return false;}std::string Name() { return _name; }~Thread() {}private:std::string _name; // 线程名字pthread_t _tid; // 线程idbool _joinable; // 是否是分离状态默认不是func_t _func; // 线程未来要执行的方法TSTATUS _status; // 线程状态ThreadData* _td; // 要传递的参数};
}让每个线程都获取局部锁的地址在每个线程在执行抢票逻辑的时候将锁的地址传给加锁函数就能实现局部加锁了。
#include Thread.hpp
#include vectorint ticketnum 10000;
void Ticket(ThreadModule::ThreadData *td)
{while(true){pthread_mutex_lock(td-_lock_ptr); // 加锁if(ticketnum 0){// 抢票printf(get a new ticket, who get it: %s, id: %d\n, td-_name.c_str(), ticketnum--);pthread_mutex_unlock(td-_lock_ptr);// 解锁}else{pthread_mutex_unlock(td-_lock_ptr);// 解锁break;}}
}
#define NUM 4
int main()
{// 创建局部锁pthread_mutex_t mutex;pthread_mutex_init(mutex, nullptr);// 创建线程对象std::vectorThreadModule::Thread threads;for(int i 0;i NUM; i){std::string name thread- std::to_string(i1);// 把锁的地址给到td对象ThreadModule::ThreadData *td new ThreadModule::ThreadData(name, mutex);// 之后在将td给到Threadthreads.emplace_back(name, Ticket, td);}// 启动线程for(int i 0; i NUM;i)threads[i].Start();// 等待线程for(int i 0; i NUM;i)threads[i].Join();// 释放锁pthread_mutex_destroy(mutex); return 0;
}
锁的相关问题 1. 锁本身是全局的那么锁也是共享资源谁保证锁的安全 pthread_mutex:加锁和解锁被设计成为原子的了 2. 如何看待锁呢二元信号量就是锁 2.1 加锁本质就是对资源展开预订 2.2 整体使用资源 3. 如果申请锁的时候锁被别人已经拿走了怎么办 其他线程要进行阻塞等待 4. 线程在访问临界区代码的时候可以不可以切换可以切换 4.1 我被切走的时候别人能进来吗不能因为我是抱着锁被切换的临界区的代码就是被串行的这也是加锁效率低的原因也体现了原子性(要么不做要么做完) 锁是如何实现的
现在大家已经意识到单纯的 i 或者 i 都不是原子的有可能会有数据一致性问题。 在内核中为了实现互斥锁操作大多数体系结构都提供了swap或exchange指令该指令的作用是把寄存器和内存单元的数据相交换由于只有一条指令保证了原子性即使是多处理器平台访问内存的 总线周期也有先后一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 将%al看成一个寄存器把 0 movb到 %al中xchgb将内存中的变量与寄存器中的做了直接的交换不需要中间变量。我们假定mutex一开始的数据是1表示锁没有被申请0表示锁被申请了。线程执行判断如果%al中的内容0则申请锁成功然后返回否则挂起等待等待完成被唤醒goto lock重新申请锁。 CPU的寄存器只有一套被所有的线程共享。但是寄存器中的数据属于执行流上下文属于执行流私有的数据CUP在执行代码的时候一定要有对应的执行载体 -- 线程进程。数据在内存中是被所有线程所共享的 结论把数据从内存移动到寄存器本质是把数据从共享变成线程私有 重新理解加锁 当线程A执行第一行代码时此时%al寄存器中为0内存mutex中为1(图1)执行第二条代码时内存中mutex中的数据与%al进行交换变为%al中值为1mutex的值为0(图2)我们假设线程A执行第三行代码的时候被切换走线程A会保存自身的上下文带走%al中的数据此时线程A处在第三行。 这时线程B来了并且开始走第一行和第二行代码由于内存中mutex的值为0(还是处于图2的状态)交换之后%al的值还是0。所以当线程B执行到第3行代码的时候只能跳到第6行进行挂起等待。 线程B被挂起线程A被重新切回并恢复上文数据从第三行开始执行进入if调用接口pthread_mutex_lockreturn 0表示加锁成功进入临界区。所以此时线程A被称为申请锁成功。在上面代码中加锁就是执行第二行代码xchgb只有一条汇编代码交换不是拷贝只有一个“1”持有1的就表示持有锁 当线程A执行完临界区的代码后进行解锁执行第八行代码将自身持有的“1”movb到内存中(这样就回到了图1的状态)接着唤醒正在等待mutex的线程B线程B被唤醒后执行第七行代码继续goto lock重新申请锁。