您现在的位置是:主页 > news > 西安网站的设计说明/seo怎么推广
西安网站的设计说明/seo怎么推广
admin2025/6/2 20:43:30【news】
简介西安网站的设计说明,seo怎么推广,国外手机网站设计,东莞网站建设 包装材料线程安全 多个线程同时运行,访问临界资源,不会导致程序结果产生二义性,这样的情况是线程安全的。 临界资源:多线程执行流共享的资源叫临界资源。 临界区:每个线程内部访问临界资源的代码 访问:在临界区…
线程安全
多个线程同时运行,访问临界资源,不会导致程序结果产生二义性,这样的情况是线程安全的。
临界资源:多线程执行流共享的资源叫临界资源。
临界区:每个线程内部访问临界资源的代码
访问:在临界区当中对临界资源进行非原子操作
原子操作:不会为任何调度机制打断的操作,该操作只有两种状态,要么完成,要么未完成。
线程不安全的原理
举个栗子:我们规定一个场景
- 假设现在在同一个程序当中有两个线程,线程A和线程B,并且有一个int类型全局变量,值为10;线程A和线程B在各自的入口函数当中对这样一个变量进行++操作。
- 线程A拥有CPU之后,对全局变量进行++操作,并非是原子操作,也就是意味着线程A,在执行++过程中有可能会被打断。假设线程A刚将全局变量的数值10读到CPU的寄存器当中来,就被切换出去了(程序计数器保存下一条执行的指令,上下文信息当中保存寄存器的值,这两个是用来当线程A再次拥有CPU的时候恢复现场使用的)
- 这时有可能线程B拥有了CPU资源,对全局变量进行++操作,并将10加成11放回到了内存当中
- 线程A再次拥有CPU之后,根据程序计数器和上下文信息恢复现场,继续往下执行,从寄存器当中读到的值仍然是10,++操作之后还是11
线程A和线程B各自对全局变量进行了++操作,理论上全局变量应为12,但现在程序计算的结果有是11,这样的线程就是不安全的,访问临界资源对程序的结果造成了二义性。
线程不安全怎么解决
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源。
同步:为了在互斥的基础上追求资源的合理分配,在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源。
互斥锁
使用互斥锁来保证互斥属性,互斥锁的底层是互质量,互斥量本质是一个计数器,该计数器只有两个取值0或者1,当线程获取互斥锁的时候,如果计数器当中的值为0,表示当前线程获取不到互斥锁,反之则表示当前线程可以获取到互斥锁。
原理
互斥锁如何保证操作的原子性?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQ9yGOIk-1613641108316)(image/互斥锁.png)]
- 加锁的时候
- 寄存器当中的值直接赋值为1
- 将寄存器当中的值和计数器当中的值交换(互换指令xchgb)
- 判断当前寄存器中的值,得出加锁结果
- 当寄存器的值为1时,表示可以加锁。
- 当寄存器的值为0时,表示不可以加锁。
- 解锁的时候
- 将寄存器的值赋为1
- 交换寄存器和计数器当中的值手机果然很奇妙。
使用
定义互斥锁
pthread_mutex_t;//互斥锁变量类型
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t
*restrict mutex,const pthread_mutexattr_t *restrict attr);
- mutex:传入互斥锁变量的地址
- attr:属性,一般传递NULL,采用默认属性
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
-
pthread_mutex_t本身是一个结构体类型,PTHREAD_MUTEX_INITIALIZER宏定义是一个结构体的值
-
#define PTHREAD_MUTEX_INITIALIZER {{0,0,0,0,0,_PTHREAD_SPINS,{0,0}}};
加锁
-
int pthread_mutex_lock(pthread_mutex_t *mutex); //阻塞加锁
- mutex:传入互斥锁变量的地址
- 如果mutex当中计数器的值为1,则pthread_mutex_lock接口就返回了,说明加锁成功,同时计数器中的值会被更改为0
- 如果mutex当中计数器的值为0,则pthread_mutex_lock接口阻塞,pthread_mutex_lock接口没有返回,阻塞等待在该函数的内部,直到加锁成功
-
int pthread_mutex_trylock(pthread_mutex_t *mutex); //非阻塞加锁
- 当互斥锁变量当中的计数器为1,则表示可加锁,加锁之后对计数器中的1改为0
- 当互斥锁变量当当中的计数器为0,表示不可以加锁,该接口直接返回(返回为EBUSY),但是此时并没有加锁成功,也就是不会去访问临界资源
- 一般非阻塞接口搭配循环来使用
-
#include <pthread.h> #include <time.h> int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout); //带有超时时间的接口
- abs_timeout:加锁超时时间,当加锁的时候超过abs_timeout之后,还没有获取到互斥锁,则报错返回,不会进行阻塞等待,返回ETIMEOUT
- struct timespes有两个变量,第一个代表秒,第二个代表纳秒
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 不管使用哪一个接口加锁,都可使用该接口进行解锁操作
- 解锁的时候,会将互斥锁变量当中的计数器的值从0变为1
销毁互斥锁
int pthread_mutex_destory(pthread_mutex_t *mutex);
- 针对的是动态初始化的互斥锁,不调用销毁接口,就会造成内存泄露
互斥锁的使用:
定义:对于C语言,需要在不同的函数中使用,所以通常定义为全局变量;对于C++,因为有类或者结构体的存在,通常定义为类的成员变量
初始化:必须在创建线程之初就先初始化互斥锁变量
加锁:开始访问临界资源的时候就需要加锁
解锁:在所有可能导致线程退出的时候解锁
销毁:在所有线程都结束运行之后,销毁互斥锁;一定不要销毁正在被执行流使用的互斥锁变量,否则可能导致死锁。
死锁
-
什么是死锁?
当多个执行流使用同一个互斥锁的时候,有一个执行流获取到了互斥锁之后,但是没有释放互斥锁,导致其他想要获取互斥锁的执行流陷入阻塞等待,我们称这种现象为死锁。
-
死锁的四个必要条件
- 互斥条件:每个互斥锁只能被一个执行流所占用
- 请求与保持条件:当前执行流已经占用了互斥锁,还想去申请新的互斥锁
- 循环等待:若干个执行流在请求锁资源的情况下,形成了一个闭环
- 不可剥夺条件:只有当前拿着互斥锁的线程可以释放该互斥锁
-
避免死锁的方法
- 破坏必要条件
- 加锁顺序一致
- 避免锁未释放
- 一次性分配资源
同步(条件变量)
同步是为了保证各个线程对临界资源访问的合理性
- 条件变量的本质:PCB等待队列+两个接口(等待接口+唤醒接口)
- 同步实现的事情:当有资源的时候,可以直接获取资源,没有资源的时候,线程进行等待,等待另外的线程生产一个资源,当生产完成的时候,通知等待的线程
使用
-
定义条件变量
- pthread_cond_t 条件变量类型
-
初始化条件变量
-
动态
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //cond:传入条件变量的地址 //attr:条件变量的属性,一般设置为NULL,采用默认属性
-
静态——不用调用销毁接口
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
-
-
等待——将调用该等待接口的执行流放入PCB等待队列当中
-
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); //cond:传入条件变量的地址 //restrict mutex:传入互斥锁变量的地址
-
为什么会有互斥锁?
- 同步并没有保证互斥,意味着不同的执行流可以在同一时刻去访问临界资源,所以需要条件变量中的互斥锁来保证互斥,各执行流在访问临界资源的时候,只有一个执行流可以访问。
-
接口内部实现逻辑
- 将调用者的PCB放到PCB等待队列当中去
- 对互斥锁进行解锁操作
- 等待其他执行流通知PCB等待队列,被唤醒之后,移出PCB等待队列当中,进而进行抢锁操作
- 没有抢到互斥锁,卡在抢互斥锁的逻辑当中(卡在pthread_cond_wait接口中);抢到互斥锁,拥有了可以访问临界资源的资格(pthread_cond_wait返回了)
-
-
唤醒
-
int pthread_cond_signal(pthread_cond_t *cond); //cond:传入条件变量的地址 //唤醒至少一个PCB等待队列当中的线程
-
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有PCB等待队列当中的线程
-
通知PCB等待当中的执行流来访问临界资源
-
-
销毁——释放动态初始化的条件变量所占用的内存
-
int pthread_cond_destroy(pthread_cond_t *cond);
-
信号量
本质
资源计数器+PCB等待队列+提供等待和唤醒接口,对比条件变量多了资源计数器,用来对临界资源进行计数。信号量通过判断自身的资源计数器,来进行条件判断。
判断当前资源是否可用,可用则获取资源进行访问;不可用则进行阻塞等待,直到被唤醒。
接口
包含头文件#include <semaphore.h>
-
定义
- sem_t 条件变量类型
-
初始化
-
int sem_init(sem_t *sem, int pshared, unsigned int value);
-
sem:传入信号量的地址
-
pshared:表示当前信号量是使用在进程间还是线程间;0——线程之间;1——进程之间
-
当使用sem_init初始化信号量为进程间的时候,会在内核当中创建一块共享内存,来保存信号量的数据结构,其中资源计数器,PCB等待队列都是在共享内存当中维护的。所以我们调用唤醒或者等待接口的时候,就通过操作共享内存实现了不同进程之间的通信,进而实现不同进程之间的同步与互斥。
-
value:实际资源数量,用于初始化信号量当中资源计数器
-
-
等待
-
int sem_wait(sem_t *sem); //阻塞方式等待 int sem_trywait(sem_t *sem); //非阻塞方式等待 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //带有超时时间的等待
-
如果调用等待接口进行获取信号量,会对资源计数器进行减1操作
-
-
唤醒
-
int sem_post(sem_t *sem);
-
发布信号量,表示资源使用完成了,需要归还资源或者生产这重新生产了一个资源,对信号量当中的资源计数器进行加1操作,唤醒PCB等待队列当中的PCB
-
-
销毁
-
int sem_destory(sem_t* sem);
-
释放信号量所占用的内存
-
-
如何保证同步和互斥
- 同步
- 初始化的时候,根据资源的数量来进行初始化信号量当中的资源计数器
- 互斥
- 初始化的时候,必须初始化信号量当中的资源计数器为1
- 同步
读写锁
- 使用的场景
- 少量写+大量读
- 特点:允许不同用于读的线程,可以获取读模式下的读写锁而并行的运行
- 读写锁的三种状态
- 读模式下的加锁状态
- 写模式下的加锁状态
- 不加锁的状态
- 加锁规则
- 一次只有一个线程可以占用写模式的读写锁,一个执行流进行写的时候,其他的执行流既不能写,也不能读;
- 多个线程可以同时占用读模式下的读写锁,在读写锁的内部有一个引用计数器(标识有多少以读模式打开的读写锁,判断释放读模式打开的读写锁,是否能够完全解锁)
接口
-
定义
pthread_rwlock_t //读写锁类型
-
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
-
加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //以读模式打开(加锁) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //以写模式打开(加锁)
-
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
-
销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);