线程的互斥与同步

article/2025/9/17 16:53:06

线程的互斥与同步

    • 线程的互斥
      • 简单的抢票程序
      • 互斥量
        • 初始化互斥量
        • 销毁互斥量
        • 互斥量加锁和解锁
      • 互斥量实现原理
    • 可重入VS线程安全
      • 概念
        • 常见的线程不安全的情况
        • 常见的线程安全的情况
        • 常见不可重入的情况
        • 常见可重入的情况
        • 可重入与线程安全联系
        • 可重入与线程安全区别
    • 死锁
      • 死锁四个必要条件
      • 避免死锁
    • 进程同步
      • 条件变量
        • 条件变量初始化
        • 销毁
        • 等待条件满足
        • 唤醒等待
      • 资源等待队列
    • 生产者消费者模型
      • 单生产者,单消费者,一个blockingqueue
    • POSIX信号量
      • 初始化信号量
      • 销毁信号量
      • 等待信号量
      • 发布信号量
      • 基于环形队列的生产消费模型
      • 线程池
    • 线程安全的单例模式
      • 饿汉方式实现单例模式
      • 懒汉方式实现单例模式(线程安全版本)
    • STL,智能指针线程安全问题
      • STL中的容器是否是线程安全的
      • 智能指针是否是线程安全的?
    • 其他常见的各种锁
    • 读写者问题
      • 初始化
      • 销毁
      • 加锁和解锁

线程的互斥

相关概念:
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

简单的抢票程序

	//未保护临界区#include<stdio.h>#include<unistd.h>#include<pthread.h>#include<sys/stat.h>#include<sys/types.h>int tickets=1000;void* TicketGrabbing(void* arg){const char* str=(char*) arg;while(1){if(tickets>0){usleep(100);printf("%s:get a ticket:%d\n",str,tickets--);}else {break;}}                                                                                                                                         printf("%s quit!\n",str);pthread_exit((void*)0);}int main(){pthread_t t1,t2,t3,t4;pthread_create(&t1,NULL,TicketGrabbing,"thread 1"); pthread_create(&t2,NULL,TicketGrabbing,"thread 2");        	    pthread_create(&t3,NULL,TicketGrabbing,"thread 3");              	pthread_create(&t4,NULL,TicketGrabbing,"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);return 0;}              

在这里插入图片描述
可以看到,票数竟然被抢到了负数,那这是为什么呢?
在这里插入图片描述

a++为什么不是原子的:
在这里插入图片描述
这就是对临界资源没有进行保护而造成了问题

互斥量

在这里插入图片描述
为解决上面的问题,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量

初始化互斥量

方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:初始属性,NULL默认

销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:
1.使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁

2.不要销毁一个已经加锁的互斥量

3.已经销毁的互斥量,要确保后面不会有线程再尝试加锁

互斥量加锁和解锁

加锁是有损于性能的

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号

对于lock函数:其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁,这样保证临界区只有一个线程访问,保护了临界区

通过互斥量实现线程安全版本的抢票:

    #include<stdio.h>#include<unistd.h>#include<pthread.h>#include<sys/stat.h>#include<sys/types.h>int tickets=1000;pthread_mutex_t mutex;void* TicketGrabbing(void* arg){const char* str=(char*) arg; while(1){pthread_mutex_lock(&mutex);if(tickets>0){usleep(100);printf("%s:get a ticket:%d\n",str,tickets--);pthread_mutex_unlock(&mutex);}                                                                                                                                       else{pthread_mutex_unlock(&mutex);break;}}printf("%s quit!\n",str);pthread_exit((void*)0);}int main(){pthread_mutex_init(&mutex,NULL);pthread_t t1,t2,t3,t4;pthread_create(&t1,NULL,TicketGrabbing,"thread 1"); pthread_create(&t2,NULL,TicketGrabbing,"thread 2");        pthread_create(&t3,NULL,TicketGrabbing,"thread 3");pthread_create(&t4,NULL,TicketGrabbing,"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);pthread_mutex_destroy(&mutex);return 0;}                                

在这里插入图片描述

在这里插入图片描述

互斥量实现原理

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性
即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期
在这里插入图片描述
加锁解锁是如何完成的:
在这里插入图片描述
所以本质上是1只有一份,只有拿到那个1的线程才能return执行临界区的代码

可重入VS线程安全

概念

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数

线程安全与重入的关系:
在这里插入图片描述

常见的线程不安全的情况

1.不保护共享变量的函数
2.函数状态随着被调用,状态发生变化的函数
3.返回指向静态变量指针的函数
4.调用线程不安全函数的函数

常见的线程安全的情况

1.每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
2.类或者接口对于线程来说都是原子操作
3.多个线程之间的切换不会导致该接口的执行结果存在二义性

常见不可重入的情况

1.调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
2.调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
3.可重入函数体内使用了静态的数据结构

常见可重入的情况

1.不使用全局变量或静态变量
2.不使用用malloc或者new开辟出的空间
3.不调用不可重入函数
4.不返回静态或全局数据,所有数据都有函数的调用者提供
5.使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全联系

1.函数是可重入的,那就是线程安全的
2.函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
3.如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

可重入与线程安全区别

1.可重入函数是线程安全函数的一种
2.线程安全不一定是可重入的,而可重入函数则一定是线程安全的
3.如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态

死锁四个必要条件

①互斥条件:一个资源每次只能被一个执行流使用
②请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
③不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
④循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

1.破坏死锁的四个必要条件
2.加锁顺序一致
3.避免锁未释放的场景
4.资源一次性分配

进程同步

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

为什么要有进程同步?
单纯的互斥会让竞争力强的线程获得锁,竞争力弱的一直等待,效率十分低

条件变量

进程同步通过条件变量来实现,类似于进程互斥的互斥量

条件变量:用来描述某种临界资源是否就绪的一种数据化描述
条件变量通常要与互斥搭配使用

条件变量初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量

线程挂起等待时还拿着互斥锁,别的线程无法进入临界区,这时需要释放锁mutex

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个

示例:

  #include<iostream>                                                                                                                            #include<pthread.h>#include<cstdio>using namespace std;pthread_mutex_t mutex;pthread_cond_t cond;void* run(void* arg){pthread_detach(pthread_self());cout<<(char*)arg<<"run....."<<endl;while(true){pthread_cond_wait(&cond,&mutex);//初始条件不满足,等待条件满足cout<<"thread"<<pthread_self()<<"runing.."<<endl; }}int main(){pthread_mutex_init(&mutex,NULL);pthread_cond_init(&cond,NULL);pthread_t t1,t2,t3;pthread_create(&t1,NULL,run,(void*)"thread t1");pthread_create(&t2,NULL,run,(void*)"thread t2");pthread_create(&t3,NULL,run,(void*)"thread t3");while(true){getchar();pthread_cond_signal(&cond);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;}               

在这里插入图片描述
可以看到使用同步后,打印结果出现了相当强的顺序性

因为每个线程等待时都在资源的等待队列中,唤醒队头后再等待插入到队尾,这样就出现每个线程顺序打印

资源等待队列

在这里插入图片描述

生产者消费者模型

本质上是一个线程安全的队列,和两种角色的线程(生产者和消费者)

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的

三种关系:
生产者与生产者(竞争关系,互斥关系)
消费者和消费者(竞争关系,互斥关系)
生产者和消费者(互斥关系(保证数据的正确性),同步关系(多线程协同))
两种角色:
生产者和消费者
一个交易场所:
通常是内存中的缓冲区,自己通过某种方式实现的

生产者消费者模型优点:
1.解耦
2.支持并发
3.支持忙闲不均

为什么要有生产者与消费者模型?
通过缓冲区,实现了生产者与消费者的解耦

单生产者,单消费者,一个blockingqueue

在这里插入图片描述
在这里插入图片描述
代码实现:
blockingqueue.c

  #pragma once#include<iostream>using namespace std;#include<pthread.h>#include<queue>#include<cstdlib>#include<ctime>#define NUM 10template<class T>class blockingqueue{                                                                                                                                             bool isFull(){return q.size()==cap;}bool isEmpty(){return q.size()==0;}public:blockingqueue(int _cap=NUM):cap(_cap){pthread_mutex_init(&lock,NULL);pthread_cond_init(&full,NULL);pthread_cond_init(&empty,NULL);}void Push(T& in){pthread_mutex_lock(&lock);while(isFull()){//wait是函数可能失败,造成伪唤醒,//所以通过while循环再次进行判定//队列为空不能push等待消费者Poppthread_cond_wait(&full,&lock);}q.push(in);pthread_mutex_unlock(&lock);if(q.size()>=cap/2){cout<<"数据太多了,消费者来消费吧"<<endl;pthread_cond_signal(&empty);}}void Pop(T& out){pthread_mutex_lock(&lock);while(isEmpty()){pthread_cond_wait(&empty,&lock);}out=q.front();q.pop();pthread_mutex_unlock(&lock);if(q.size()<=cap/2){cout<<"数据已经快不够了,生产者来生产吧"<<endl;pthread_cond_signal(&full);}}~blockingqueue(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&full);pthread_cond_destroy(&empty);}public:queue<T> q;private:int cap;pthread_mutex_t lock;pthread_cond_t full;pthread_cond_t empty;};      

main函数

  #include"blockingqueue.c"#include<unistd.h>void* Consumer(void* arg){auto bq=(blockingqueue<int>*) arg;while(true){int data=0;//输出型参数bq->Pop(data);cout<<"consumer:"<<data<<"当前个数:"<<bq->blockingqueue::q.size()<<endl;sleep(3);                                                                                                                                 }}void* Producer(void* arg){auto bq=(blockingqueue<int>*) arg;while(true){int data=rand()%100+1;bq->Push(data);cout<<"Producer:"<<data<<"当前个数:"<<bq->blockingqueue::q.size()<<endl;sleep(1);}}int main(){srand((unsigned long)time(NULL));blockingqueue<int>* bq=new blockingqueue<int>();pthread_t c,p;pthread_create(&c,NULL,Consumer,bq);pthread_create(&p,NULL,Producer,bq);pthread_join(c,NULL);pthread_join(p,NULL);return 0;}               

POSIX信号量

信号量本身是一个计数器,描述临界资源的计数器
拥有更细粒度的临界资源的管理

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步和互斥
在这里插入图片描述
P操作:
①信号量S减1;
②若S减1后仍大于或等于0,则进程继续执行;
③若S减1后小于0,则该进程被阻塞后放入等待该信号量的等待队列中
V操作:
①S加1;
②若相加后结果大于0,则进程继续执行;
③若相加后结果小于或等于0,则从该信号的等待队列中释放一个等待进程,然后再返回原进程继续执行或转进程调度

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值,大小表示可以同时访问临界资源的线程数

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1
int sem_post(sem_t *sem);//V()

信号量版本实现线程安全的抢票程序

  #include<iostream>                                                                                                                                                                                                                                                                                                      #include<string>#include<pthread.h>#include<unistd.h>#include<semaphore.h>using namespace std;class Sem{private:sem_t sem;public:Sem(int num){sem_init(&sem,0,num);}void P(){sem_wait(&sem);}void V(){sem_post(&sem);}~Sem(){sem_destroy(&sem);}};Sem sem(1);//只能有一个人在抢int tickets=100;void* Gettickets(void* arg){string s=(char*)arg;while(true){sleep(1);sem.P();if(tickets>0){usleep(10000);cout<<s<<"get tickets:"<<tickets--<<endl;sem.V();}else{sem.V();break;}}pthread_exit((void*)0);}int main(){pthread_t t1,t2,t3;pthread_create(&t1,NULL,Gettickets,(void*)"thread t1");pthread_create(&t2,NULL,Gettickets,(void*)"thread t2");pthread_create(&t3,NULL,Gettickets,(void*)"thread t3");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);return 0;}

在这里插入图片描述

基于环形队列的生产消费模型

在这里插入图片描述
通过信号量实现:

  #include<iostream>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  #include<unistd.h>#include<pthread.h>#include<stdlib.h>#include<semaphore.h>#include<vector>using namespace std;#define NUM 5template<class T>class ring{private:sem_t sem_blank;sem_t sem_data;vector<T> v;int _cap;int c_pos;int p_pos;void P(sem_t& t){sem_wait(&t);}void V(sem_t& t){sem_post(&t);}public:ring(int num=NUM):_cap(num),c_pos(0),p_pos(0){v.resize(_cap);sem_init(&sem_data,0,0);sem_init(&sem_blank,0,_cap);}void Push(T& in){P(sem_blank);v[p_pos]=in;V(sem_data);p_pos++;p_pos%=_cap;}void Pop(T& out){P(sem_data);out=v[c_pos];V(sem_blank);c_pos++;c_pos%=_cap;}~ring(){sem_destroy(&sem_data);sem_destroy(&sem_blank);}};void* produce(void* arg){ring<int>* rq=(ring<int>*) arg;while(true){sleep(1);int a=rand()%100+1;rq->Push(a);cout<<"produce done:"<<a<<endl;}}void* consume(void* arg){ring<int>* rq=(ring<int>*) arg;while(true){sleep(2);int a=0;rq->Pop(a);cout<<"consume done:"<<a<<endl;}}int main(){pthread_t p,c;ring<int>* rp=new ring<int>();srand((unsigned long)time(NULL));pthread_create(&p,NULL,produce,rp);pthread_create(&c,NULL,consume,rp);pthread_join(p,NULL);pthread_join(c,NULL);return 0;}

在这里插入图片描述
该队列不可能出现数据不一致的情况

只有两种情况生产者和消费者指向同一空间,访问同一临界资源
1.队列为空时,消费者不能消费,只有生产者生成,不存在竞争
2.队列为满时,生产者不能生产,等待消费者消费,也不存在竞争

线程池

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

  • 线程池的应用场景:
  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。 *
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

在这里插入图片描述

Task.hpp

  #pragma once                                                                                               #include<iostream>using namespace std;#include<pthread.h>class Task{private:int _x;int _y;char _op;public:Task(){}Task(int x,int y,char op):_x(x),_y(y),_op(op){}void run(){int z=0;switch(_op){case '+':z=_x+_y;break;case '-':z=_x-_y;break;case '*':z=_x*_y;case '/':if(_y==0){cerr<<"div zero"<<endl;}else z=_x/_y;break;case '%':if(_y==0){cerr<<"mod zero"<<endl;}else z=_x%_y;break;default:cerr<<"default"<<endl;}cout<<_x<<_op<<_y<<"="<<z<<endl;}};           

Threadpool.hpp

  #include<iostream>using namespace std;#include<pthread.h>#include<queue>#define  NUM 5                                                                                                                                                                                                                                                                                                                                                                                                                                                                        template<class T>class ThreadPool{private:queue<T> task_queue;int thread_num;pthread_mutex_t lock;pthread_cond_t cond;public:ThreadPool(int num=NUM):thread_num(num){pthread_mutex_init(&lock,NULL);pthread_cond_init(&cond,NULL);}void LockQueue(){pthread_mutex_lock(&lock);}void UnlockQueue(){pthread_mutex_unlock(&lock);}void waitcond(){pthread_cond_wait(&cond,&lock);}void wakeup(){pthread_cond_signal(&cond);}bool isEmptyThreadPool(){return task_queue.size()==0;}static void* Routine(void* arg){pthread_detach(pthread_self());ThreadPool* This=(ThreadPool*) arg;while(true){This->LockQueue();if(This->isEmptyThreadPool()){This->waitcond();}T t;This->Pop(t);This->UnlockQueue();cout<<"thread:"<<pthread_self()<<"run:";t.run();}}void Push(T& in){LockQueue();task_queue.push(in);UnlockQueue();wakeup();}void Pop(T& out){out=task_queue.front();task_queue.pop();}void InitThreadPool(){pthread_t tid;for(int i=0;i<thread_num;i++){pthread_create(&tid,NULL,Routine,this);}}~ThreadPool(){pthread_cond_destroy(&cond);pthread_mutex_destroy(&lock);}};

对于Routine函数
在这里插入图片描述
main函数

  #include"Task.hpp"                                                                                         #include"Threadpool.hpp"#include<unistd.h>int main(){ThreadPool<Task>* tp=new ThreadPool<Task>();tp->InitThreadPool();srand((unsigned int)time(NULL));const char* op="+-*/%";while(true){sleep(1);int x=rand()%100+1;int y=rand()%100+1;Task t(x,y,op[x%5]);tp->Push(t);}return 0;}

在这里插入图片描述

线程安全的单例模式

某些类, 只应该具有一个对象(实例), 就称之为单例

单例模式通常分为饿汉模式和懒汉模式

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

懒汉方式最核心的思想是 “延时加载”,“需要时再加载” 从而能够优化服务器的启动速度以及有效利用内存

饿汉方式实现单例模式

template <typename T> 
class Singleton 
{  static T data; public:  static T* GetInstance() {  return &data;  } 
}; 

懒汉方式实现单例模式(线程安全版本)

template <typename T> 
class Singleton {  volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.  static std::mutex lock; public:  static T* GetInstance() {  if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.  lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.  if (inst == NULL) {  inst = new T();  }  lock.unlock(); }  return inst;  } }; 

注意事项:

  1. 加锁解锁的位置
  2. 双重 if 判定, 避免不必要的锁竞争
  3. volatile关键字防止过度优化

STL,智能指针线程安全问题

STL中的容器是否是线程安全的

不是

原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).

因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

智能指针是否是线程安全的?

对于 unique_ptr:
由于只是在当前代码块范围内生效, 因此不涉及线程安全问题

对于 shared_ptr,:
多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

自旋锁:
在这里插入图片描述

读写者问题

在这里插入图片描述

初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

多线程下:
如果读写写者同时到来,如果有人还在读,则不让读者进入临界区,让写者进入并写完后再读,称之为写者优先
反之,为读者优先

示例:
在这里插入图片描述

在这里插入图片描述


http://chatgpt.dhexx.cn/article/AN8NZR1A.shtml

相关文章

FreeRTOS个人笔记-互斥量

根据个人的学习方向&#xff0c;学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄&#xff0c;打算在本专栏尽量细化一点。作为个人笔记&#xff0c;仅供参考或查阅。 配套资料&#xff1a;FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视…

C++ 互斥锁原理以及实际使用介绍

兄弟姐妹们&#xff0c;我又回来了&#xff0c;今天带来实际开发中都需要使用的互斥锁的内容&#xff0c;主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁。实现互斥&#xff0c;可以有以下几种方式&#xff1a;互斥量&#xff08;Mutex&#xff09;、递归互斥量&#x…

MySQL mutex互斥锁

在事务机制中&#xff0c;锁机制是为了保证高并发&#xff0c;数据一致性的重要实现方式。MySQL除了Innodb引擎层面的行锁&#xff0c;还有latch锁。latch锁的作用资源协调处理。包含表句柄&#xff0c;线程&#xff0c;cpu线程&#xff0c;内存池等。其保证非常短时间内快速处…

uCOSii中的互斥信号量

uCOSii中的互斥信号量 一、互斥型信号量项管理 (MUTUAL EXCLUSION SEMAPHORE MANAGEMENT) OSMutexAccept() 无条件等待地获取互斥型信号量 OSMutexCreate() 建立并初始化一个互斥型信号量 OSMutexDel() 删除互斥型信号量 OSMutexPend() 等待一个互斥型信号量 OSMutexPost…

互斥信号量

目录 1、Creat 2、Delete 3、Wait 4、Post 5、Statu 互斥信号量 在介绍二进制信号量时&#xff0c;曾讨论到如果二进制信号量创建时设置参数 bInitValue 为TRUE&#xff0c;则可以用于互斥访问共享资源。实际上&#xff0c;SylixOS 的二进制信号量实现的互斥性是将一个变量…

UCOS-III 互斥量

互斥量 一、互斥量基本概念二、互斥量优先级继承机制三、互斥量应用场景四、互斥量运作机制五、互斥量创建流程1、定义互斥量2、创建互斥量 六、互斥量接口函数1、创建互斥量函数OSMutexCreate()2、删除互斥量函数 OSMutexDel()3、获取互斥量函数 OSMutexPend()4、释放互斥量函…

互斥量知识

文章目录 互斥量1、基本概念2、互斥量的优先级继承机制3、互斥量应用场景4、互斥量运行机制5、互斥量控制块6、互斥量函数接口&#xff08;1&#xff09;互斥量创建函数 xSemaphoreCreateMutex()&#xff08;2&#xff09;递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()…

同步和互斥

同步和互斥 竞争与协作 在单核 CPU 系统里&#xff0c;为了实现多个程序同时运行的假象&#xff0c;操作系统通常以时间片调度的方式&#xff0c;让每个进程执行每次执行一个时间片&#xff0c;时间片用完了&#xff0c;就切换下一个进程运行&#xff0c;由于这个时间片的时间很…

多线程的同步与互斥(互斥锁、条件变量、读写锁、自旋锁、信号量)

文章目录 一、同步与互斥的概念二、互斥锁&#xff08;同步&#xff09;三、条件变量&#xff08;同步&#xff09;1、线程的条件变量实例12、线程的条件变量实例23、虚假唤醒(spurious wakeup) 四、读写锁&#xff08;同步&#xff09;五、自旋锁&#xff08;同步&#xff09;…

同步和互斥区别

互斥的概念 由于多线程执行操作共享变量的这段代码可能会导致竞争状态&#xff0c;因此我们将此段代码称为临界区&#xff08;critical section&#xff09;&#xff0c;它是访问共享资源的代码片段&#xff0c;一定不能给多线程同时执行。 我们希望这段代码是互斥&#xff0…

操作系统——互斥的定义及实现

一、进程互斥的定义 所谓进程互斥,指的是对某个系统资源,一个进程正在使用它,另外一个想用它的进程就必须等待,而不能同时使用 。进程互斥是多道程序系统中进程间存在的一种源于资源共享的制约关系,也称间接制约关系,主要是由被共享资源的使用性质所决定的。 二、互斥…

Fisher判别分析详解

Fisher判别分析 将高维度空间的样本投影到低维空间上&#xff0c;使得投影后的样本数据在新的子空间上有最小的类内距离以及最大的类间距离&#xff0c;使得在该子空间上有最佳的可分离性 可以看出右侧投影后具有更好的可分离性。 Fisher判别分析和PCA差别 刚学完感觉两个很…

基于spss的多元统计分析 之 聚类分析+判别分析(2/8)

实验目的&#xff1a; 1&#xff0e;掌握聚类分析及判别分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件进行聚类分析及判别分析的基本操作&#xff1b; 3&#xff0e;利用实验指导的实例数据&#xff0c;上机熟悉聚类分析及判别分析方法。 实验前预习&#xff1a;…

机器学习——线性判别分析

目录 线性判别分析 LDA的降维过程 案例&#xff1a;鸢尾花(Iris) 代码演示 数据集 局部线性嵌入 线性判别分析 线性判别分析&#xff08;LDA&#xff09;是一种有监督的线性降维算法。与PCA不同&#xff0c;LDA是为了使降维后的数据点尽可能地容易被区分。 线性判别分析…

MATLAB判别分析例题,判别分析的matlab实现案例.doc

判别分析的matlab实现案例.doc 读取EXAMP10_01XLS中数据&#xff0c;进行距离判别读取数据读取文件EXAMP10_01XLS的第1个工作表中C2F51范围的数据&#xff0c;即全部样本数据&#xff0c;包括未判企业SAMPLEXLSREAD EXAMP10_01XLS , , C2F51 读取文件EXAMP10_01XLS的第1个工作…

SAS数据分析之判别分析

判别分析与聚类分析有非常类似的特性&#xff0c;因此&#xff0c;在多数数据分析的教材中&#xff0c;这两章是一前一后出现的&#xff0c;简而言之&#xff0c;聚类分析&#xff0c;其实是判别分析的基础&#xff0c;即在聚类分析的基础上&#xff0c;总结出各类的权值&#…

线性判别分析

线性判别分析&#xff08;LDA&#xff09;是一种经典的线性学习方法。其思想是&#xff1a;给定训练样例集&#xff0c;设法将样例投影到一条直线上&#xff0c;使得同类样例的投影点尽可能接近、异类样例的投影点尽可能远离。如图所示的二分类示意图&#xff1a; 损失函数的…

sas判别分析

#判别分析两大类&#xff1a;Fisher&Bayes #neighbbor&#xff1a;马氏距离和欧氏距离&#xff1b; #典型判别分析&#xff0c;联系典型相关分析#组变量相关&#xff0c;最后得到的每组内变量的线性组合作为典型变量 #协方差矩阵&#xff0c;举个栗子,设一组特征x(a1,a…

【数模】判别分析

文章目录 判别分析简介SPSS操作步骤输出结果分析 判别分析简介 判别分析又称“分辨法”&#xff0c;是在分类确定的条件下&#xff0c;根据某一研究对象的各种特征值判别其类型归属问题的一种多变量统计分析方法。 其基本原理是按照一定的判别准则&#xff0c;建立一个或多个判…

fisher线性判别分析matlab,线性判别分析LDA

首先搞清楚什么叫判别分析&#xff1f;Discriminant Analysis就是根据研究对象的 各种特征值判别其类型归属问题的一种多变量统计分析方法。 根据判别标准不同&#xff0c;可以分为距离判别、Fisher判别、Bayes判别法等。比如在KNN中用的就是距离判别&#xff0c;当然这里的“距…