高端网站设计哪家公司好,网络营销好不好,做跨境电商需要哪些条件,一级做爰片a视频网站1.线程互斥相关概念 临界资源#xff1a;多线程执行流共享的资源就叫做临界资源 。 临界区#xff1a;每个线程内部#xff0c;访问临界自娱的代码#xff0c;就叫做临界区。 互斥#xff1a;任何时刻#xff0c;互斥保证有且只有一个执行流进入临界区#xff0c;访问临…1.线程互斥相关概念 临界资源多线程执行流共享的资源就叫做临界资源 。 临界区每个线程内部访问临界自娱的代码就叫做临界区。 互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用。 原子性不会被任何调度机制打断的操作该操作只有两态要么完成要么未完成。 2.互斥量
多线程抢票的例子
#include iostream
#include thread
#include cerrno
#include cstring
#include unistd.h
#include pthread.h
#include cstdiousing namespace std;
int tickets 10000; // 在并发访问的时候导致了我们数据不一致的问题void *getTickets(void *args)
{(void)args;while(true){if(tickets 0){usleep(1000);printf(%p: %d\n, pthread_self(), tickets);tickets--;}else{break;}}return nullptr;
}int main()
{pthread_t t1,t2,t3;// 多线程抢票的逻辑pthread_create(t1, nullptr, getTickets, nullptr);pthread_create(t2, nullptr, getTickets, nullptr);pthread_create(t3, nullptr, getTickets, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);
} 输出结果 我们发现票居然被抢到了 -1甚至有的票没被抢到有的票被抢了多次为什么呢----原因就在于ticket是全局的共享的在被多线程并发访问时由于各个线程对ticket变量进行了修改出现了数据不一致问题
其实根本原因是因为对ticket进行 -- 操作并不是原子的对应了3条汇编指令 load 将共享变量 ticket 从内存加载到寄存器中 update : 更新寄存器里面的值执行 -1 操作 store 将新值从寄存器写回共享变量 ticket 的内存地址 要解决以上问题需要做到三点 代码必须要有互斥行为当代码进入临界区执行时不允许其他线程进入该临界区。 如果多个线程同时要求执行临界区的代码并且临界区没有线程在执行那么只能允许一个线程进入该临界区。 如果线程不在临界区中执行那么该线程不能阻止其他线程进入临界区。 要做到这三点本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。 3.对锁的理解 swap或enchange指令 以一条汇编的方式将内存和CPU寄存器数据进行交换如果我们在汇编的角度只有一条汇编语句我们就认为该汇编语句的执行就是原子的。 在执行流视角是如何看待CPU上面的寄存器的 CPU内部的寄存器本质叫做执行流的上下文寄存器们的空间是被所有的执行流所共享的但是寄存器的内容是被每一个执行流私有的。上下文 下边通过例子以图深入理解锁
分以下若干步骤 【线程A先执行 movb $0%al 指令,执行完该指令后被OS切换】
【线程B执行 movb $0%al 指令然后再执行 xchgb %al,mutex 指令然后立刻被OS切换】
【线程A执行 xchgb %al,mutex 指令由于此时 al 寄存器 的内容0因此线程A被挂起等待】
【线程B由于此时 al 寄存器 的内容1因此线程B申请到了锁】
。。。。。。。
【线程B释放锁此时线程A执行goto lock, 才可以重新申请锁】 4.互斥量的接口 初始化互斥量 初始化互斥量有两种方法 方法一静态分配 pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER 方法二动态分配 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参数 mutex要初始化的互斥量 attrNULL 销毁互斥量 销毁互斥量需要注意 使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁 不要销毁一个已经加锁的互斥量 已经销毁的互斥量要确保后面不会有线程再尝试加锁 int pthread_mutex_destroy(pthread_mutex_t *mutex) 互斥量加锁和解锁 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值:成功返回0,失败返回-1错误码errno被设置 调用 pthread_ lock 时可能会遇到以下情况 : 互斥量处于未锁状态该函数会将互斥量锁定同时返回成功 发起函数调用时其他线程已经锁定互斥量或者存在其他线程同时申请互斥量但没有竞争到互斥量那么pthread_ lock 调用会陷入阻塞 ( 执行流被挂起 ) 等待互斥量解锁。 改进上边的售票系统
#include iostream
#include thread
#include cerrno
#include cstring
#include unistd.h
#include pthread.h
#include cstdiousing namespace std;
int tickets 10000; // 在并发访问的时候导致了我们数据不一致的问题//pthread_mutex_t mtxPTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtx;void *getTickets(void *args)
{(void)args;while(true){pthread_mutex_lock(mtx);if(tickets 0){usleep(1000);printf(%p: %d\n, pthread_self(), tickets);tickets--;pthread_mutex_unlock(mtx);}else{pthread_mutex_unlock(mtx);break;}}return nullptr;
}int main()
{pthread_t t1,t2,t3;pthread_mutex_init(mtx, NULL);// 多线程抢票的逻辑pthread_create(t1, nullptr, getTickets, nullptr);pthread_create(t2, nullptr, getTickets, nullptr);pthread_create(t3, nullptr, getTickets, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_mutex_destroy(mtx);
} 输出结果部分 当我们运行代码后发现运行好多次也不会出现之前的有的票没有被卖有的票被卖了多次甚至票数到了 -1这些现象同时也会发现此时打印的速度比之前的打印速度慢了不少。原因就在于当使用了锁以后线程执行时会串行化
因此加锁的粒度一定要越小越好
形象化 5.可重入VS线程安全 线程安全多个线程并发同一段代码时不会出现不同的结果。常见对全局变量或者静态变量进行操作并且没有锁保护的情况下会出现该问题。 重入同一个函数被不同的执行流调用当前一个流程还没有执行完就有其他的执行流再次进入我们称之为重入。一个函数在重入的情况下运行结果不会出现任何不同或者任何问题则该函数被称为可重入函数否则是不可重入函数。 常见的线程不安全的情况 不保护共享变量的函数 函数状态随着被调用状态发生变化的函数 返回指向静态变量指针的函数 调用线程不安全函数的函数 常见的线程安全的情况 每个线程对全局变量或者静态变量只有读取的权限而没有写入的权限一般来说这些线程是安全的 类或者接口对于线程来说都是原子操作 多个线程之间的切换不会导致该接口的执行结果存在二义性 常见不可重入的情况 调用了 malloc/free 函数因为 malloc 函数是用全局链表来管理堆的 调用了标准 I/O 库函数标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构 可重入函数体内使用了静态的数据结构 常见可重入的情况 不使用全局变量或静态变量 不使用用 malloc 或者 new 开辟出的空间 不调用不可重入函数 不返回静态或全局数据所有数据都有函数的调用者提供 使用本地数据或者通过制作全局数据的本地拷贝来保护全局数据 可重入与线程安全的联系 函数是可重入的那就是线程安全的 函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题 如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的 可重入与线程安全的区别 可重入函数是线程安全函数的一种。 线程安全不一定是可重入的而可重入函数则一定是线程安全的。 如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入函数若锁还未释放则会产生死锁因此是不可重入的。 6.死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。 死锁四个必要条件 互斥条件一个资源每次只能被一个执行流使用 请求与保持条件一个执行流因请求资源而阻塞时对已获得的资源保持不放 不剥夺条件:一个执行流已获得的资源在末使用完之前不能强行剥夺 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系 如何避免死锁 破坏死锁的四个必要条件 加锁顺序一致 避免锁未释放的场景 资源一次性分配 产生死锁以2个线程为例
由于线程A已经申请到了锁1线程B已经申请到了锁2但是双方都不让步这就导致了“死锁”。 其实一把锁也能产生死锁比如申请锁后不释放还继续申请锁也产生了“死锁”如下
将释放锁的代码改为申请锁的代码 7.线程同步 条件变量 当一个线程互斥地访问某个变量时它可能发现在其它线程改变状态之前它什么也做不了。 例如一个线程访问队列时发现队列为空它只能等待只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。 同步概念与竞态条件 同步在保证数据安全的前提下让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问题叫做同步。 竞态条件因为时序问题而导致程序异常我们称之为竞态条件。在线程场景下这种问题也不难理解。 条件变量函数 初始化 int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 参数 cond要初始化的条件变量 attrNULL 销毁 int pthread_cond_destroy(pthread_cond_t *cond 等待条件满足 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 参数 cond要在这个条件变量上等待 mutex互斥量后面详细解释 唤醒等待 int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);