06.线程同步

互斥锁(互斥量)

描述

一个进程下的线程是共享资源的,通信便利的同时也造成了许多麻烦,线程程和线程之间如果同时访问一块资源就会出错,所以要引入一个互斥变量给它加锁,让它去协同不同线程程之间的访问(线程同步)

pthread_mutex_init函数

函数描述

初始化一个互斥量

函数原型

int pthread_mutex_init(pthread_mutex_t *mtx,const pthread_mutexattr *attr)
参一:传出参数,互斥量
参二:互斥量属性的指针

pthread_mutex_lock函数

阻塞加锁,如果没有加锁就加锁,有锁的话阻塞等待锁的消失(对于互斥量而言)
int pthread_mutex_lock(pthread_mutex_t *mtx)

pthread_mutex_unlock函数

int pthread_mutex_unlock(pthread_mutex_t *mtx)

pthread_mutex_destroy函数

int pthread_mutex_destroy(pthread_mutex_t *mtx)
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
const int N=1e6;
int a;
pthread_mutex_t mtx;//定义互斥变量
void* thread1(void* arg){for(int i = 1;i <= N; i++){//首先会获取mtx,判断mtx是否加锁,如果加锁了,会阻塞等待//知道持有互斥锁的线程解锁,然后当前进程加锁//如果没有加锁,直接给mtx上锁继续执行下面代码pthread_mutex_lock(&mtx);a++;//给互斥变量解锁pthread_mutex_unlock(&mtx);}
}
int main(int argc, char* argv[])
{pthread_t tids[10];pthread_mutex_init(&mtx,NULL);//初始化互斥变量for(int i = 0;i < 10; i++){pthread_create(tids+i,NULL,thread1,NULL);}for(int i = 0;i < 10; i++){pthread_join(tids[i],NULL);}printf("a = %d\n",a);pthread_mutex_destroy(&mtx);return 0;
}

pthread_mutex_trylock函数

非阻塞加锁(可以解决死锁问题)

练习

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
int seat[11],seat_num=10,thread_num=12;
pthread_t thread[13];
pthread_mutex_t mtx;
int get_id(){//利用随机函数生成随机数,每次生成的随机数与末尾的调换int r_id,res;if(seat_num==0) return 0;r_id=rand()%seat_num+1;res=seat[r_id];seat[r_id]=seat[seat_num];seat_num--;return res;
}
void* thread1(void* arg){pthread_mutex_lock(&mtx);int seat_id=get_id();pthread_mutex_unlock(&mtx);if(seat_id==0){printf("用户 %d 选座失败,座位已售空\n",*(int*)arg);}else{printf("用户 %d 成功选得座位 %d\n",*(int*)arg,seat_id);}return NULL;
}
int main(int argc, char* argv[])
{srand((unsigned)time(0));for(int i=1;i<=10;i++){seat[i]=i;}pthread_mutex_init(&mtx,NULL);for(int i=1;i<=12;i++){int* user_id=(int*)malloc(sizeof(int));*user_id=i;pthread_create(&thread[i],NULL,thread1,user_id);}for(int i=1;i<=12;i++){pthread_join(thread[i],NULL);}pthread_mutex_destroy(&mtx);return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

死锁现象

描述

当多个线程为了保护多个共享资源使用了多个互斥锁,如果使用不恰当,就可能造成多个线程之间一直阻塞等待对方锁的释放,这种现象叫死锁
具体例子

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在又俩个线程,A线程和B线程,现在同时运行,A线程给互斥量1上了锁,B线程给互斥量2上了锁,然后sleep了1s,此时A线程持有互斥量1并等待B线程互斥量2解锁,B线程中持有互斥量2并等待A线程互斥量1解锁,AB线程互相等待,产生死锁
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
pthread_mutex_t mtx1,mtx2;
void* thread1(void* arg){printf("child get mtx1.........\n");pthread_mutex_lock(&mtx1);printf("child get mtx1 success!\n");sleep(1);printf("child get mtx2.........\n");pthread_mutex_lock(&mtx2);printf("child get mtx2 success!\n");pthread_mutex_unlock(&mtx1);pthread_mutex_unlock(&mtx2);
}
int main(int argc, char* argv[])
{pthread_mutex_init(&mtx1,NULL);pthread_mutex_init(&mtx2,NULL);pthread_t tid;pthread_create(&tid,NULL,thread1,NULL);printf("main get mtx2.........\n");pthread_mutex_lock(&mtx2);printf("main get mtx2 success!\n");sleep(1);printf("main get mtx1.........\n");pthread_mutex_lock(&mtx1);printf("main get mtx1 success!\n");pthread_mutex_unlock(&mtx2);pthread_mutex_unlock(&mtx1);return 0;
}

死锁产生的四个必要条件

1.互斥条件:必须得有多个互斥变量

2.持有并等待条件:A持有锁1并等待B锁2解锁,B持有锁2并等待A锁1解锁

3.不可剥夺条件:必须一直阻塞

4.环路等待条件

避免死锁

1.不加锁(不太现实奥)

2.锁的力度控制:尽量减少持有锁的事件,降低死锁发生概率

3.资源有序分配:破坏环路条件1 2 1 2

4.重试机制:破坏不可剥夺条件,一般为非阻塞的trylock

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include <errno.h>
pthread_mutex_t mtx1,mtx2;
void* thread1(void* arg){while(1){printf("child get mtx1.........\n");pthread_mutex_lock(&mtx1);printf("child get mtx1 success!\n");sleep(1);printf("child get mtx2.........\n");int ret = pthread_mutex_trylock(&mtx2);if(ret != 0 && ret == EBUSY){pthread_mutex_unlock(&mtx1);printf("child free mtx1\n");continue;}printf("child get mtx2 success!\n");pthread_mutex_unlock(&mtx1);pthread_mutex_unlock(&mtx2);return NULL;}
}
int main(int argc, char* argv[])
{pthread_mutex_init(&mtx1,NULL);pthread_mutex_init(&mtx2,NULL);pthread_t tid;pthread_create(&tid,NULL,thread1,NULL);printf("main get mtx2.........\n");pthread_mutex_lock(&mtx2);printf("main get mtx2 success!\n");sleep(1);printf("main get mtx1.........\n");pthread_mutex_lock(&mtx1);printf("main get mtx1 success!\n");pthread_mutex_unlock(&mtx2);pthread_mutex_unlock(&mtx1);pthread_exit(NULL);return 0;
}

读写锁

已经有互斥锁了为什么还要有读写锁?

当多个线程读多写少的时候建议使用读写锁

概述

读写锁由读锁和写锁两部分构成,特性为写独占,读共享。
适用于写多读少的情况

特性

写独占,读共享

一个线程写的时候,另一个线程不能写
一个线程读的时候,另一个线程可以读

读写锁函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

场景分析

持有读锁时,申请读锁,加锁成功,不需要等待
持有写锁时,申请写锁,会阻塞等待写锁的解锁,然后再加锁
持有读锁时,申请写锁,会阻塞等待读锁的解锁,然后再加锁
持有写锁时,申请读锁,会阻塞等带写锁的写锁,然后再加锁
持有读锁时,申请写锁和读锁,读锁会加锁成功,写锁会阻塞等待(如果是很多的读锁和少量的写锁的话,写锁会一直无法请求成功,造成饥饿)
持有写锁时,申请写锁和读锁,(如果是读优先)读锁会加锁成功,写锁阻塞等待写锁解锁,然后再加锁
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include <errno.h>
pthread_rwlock_t rwlock;
void* thread1(void* arg){pthread_rwlock_wrlock(&rwlock);printf(" thread1 加锁成功\n");sleep(2);pthread_rwlock_unlock(&rwlock);printf("thread1 解锁\n");}
void* thread2(void* arg){pthread_rwlock_wrlock(&rwlock);printf(" thread2 加锁成功\n");sleep(2);pthread_rwlock_unlock(&rwlock);printf("thread2 解锁\n");
}
void* thread3(void* arg){pthread_rwlock_rdlock(&rwlock);printf(" thread3 加锁成功\n");sleep(2);pthread_rwlock_unlock(&rwlock);printf("thread3 解锁\n");
}
int main(int argc, char* argv[])
{pthread_rwlock_init(&rwlock,NULL);pthread_t tid;pthread_create(&tid,NULL,thread1,NULL);sleep(1);pthread_create(&tid,NULL,thread2,NULL);sleep(1);pthread_create(&tid,NULL,thread3,NULL);pthread_exit(NULL);return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

练习

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include <errno.h>
pthread_rwlock_t rwlock;
int balance;void* save(void* arg){int val = atoi((char*)arg);pthread_rwlock_wrlock(&rwlock);sleep(1);balance+=val;printf("save money:%d , balance:%d\n",val,balance);pthread_rwlock_unlock(&rwlock);}
void* query(void* arg){pthread_rwlock_rdlock(&rwlock);int r = rand()%3+1;sleep(r);printf("query : balance : %d\n",balance);pthread_rwlock_unlock(&rwlock);
}int main(int argc, char* argv[])
{srand((unsigned)time(0));pthread_t threads[15];pthread_rwlock_init(&rwlock,NULL);for(int i=0;i<5;i++){pthread_create(&threads[i],NULL,save,argv[i+1]);}for(int i=5;i<15;i++){pthread_create(&threads[i],NULL,query,NULL);}for(int i=0;i<15;i++){pthread_join(threads[i],NULL);}pthread_rwlock_destroy(&rwlock);return 0;
}

条件变量

条件变量用于进程同步当中。条件变量允许一个线程阻塞等待某个条件的成立,改线程会被挂起,不占用cpu资源。当条件满足的时候,可以唤醒阻塞等待的线程继续执行
条件变量一般与互斥锁一同使用

函数

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
//条件变量的初始化
int pthread_cond_destroy(pthread_cond_t *cond)
//条件变量的销毁
int pthread_cond_wait(pthread_cond_t *t)
//阻塞等待某个条件变量,释放持有的互斥锁,这俩步是原子操作
//直到被唤醒,接触阻塞重新获得互斥锁
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int a;
pthread_mutex_t mtx;
pthread_cond_t cond;
void* thread1(void* arg){for(int i=0;i<10;i++){//在哪访问共享资源就在哪加锁pthread_mutex_lock(&mtx);while (a != 0){//避免轮询操作pthread_cond_wait(&cond,&mtx);//释放锁并阻塞等待,这两步是原子操作不可分割 }printf("a = %d\n", ++a);pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mtx);}  
}
void* thread2(void* arg){for(int i=0;i<10;i++){pthread_mutex_lock(&mtx);while (a != 1){pthread_cond_wait(&cond,&mtx);}printf("a = %d\n", --a);pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mtx);}
}
int main(int argc, char* argv[])
{pthread_mutex_init(&mtx,NULL);pthread_t tid1,tid2,tid3,tid4;pthread_create(&tid1,NULL,thread1,NULL);pthread_create(&tid2,NULL,thread2,NULL);pthread_create(&tid3,NULL,thread1,NULL);pthread_create(&tid4,NULL,thread2,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);pthread_join(tid4,NULL);return 0;
}

生产者消费者问题

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>pthread_mutex_t mtx;
pthread_cond_t cond;struct node* head;struct node{int val;//数据域struct node* next;//指针域
};void add_val(int val){struct node* p=(struct node*)malloc(sizeof(struct node));p->val=val;p->next=head;head=p;
}int get_val(){if(!head){return -1;}int ans=head->val;struct node* tmp=head;head = head->next;free(tmp);return ans;
}void* producer(void* arg){while(1){pthread_mutex_lock(&mtx);add_val(rand()%500+1);//说明肯定有数据了pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mtx);}
}
void* conmuser(void* arg){while(1){pthread_mutex_lock(&mtx);//如果链表中没有数据了,那么就需要阻塞等待数据的产生int num=get_val();if(num == -1){pthread_cond_wait(&cond,&mtx);}printf("num = %d\n",num);sleep(1);pthread_mutex_unlock(&mtx);}
}
int main(int argc, char* argv[])
{srand(time(0));pthread_mutex_init(&mtx,NULL);pthread_cond_init(&cond,NULL);pthread_t tid1,tid2,tid3,tid4;pthread_create(&tid1,NULL,producer,NULL);pthread_create(&tid2,NULL,conmuser,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}

信号量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

两个线程堆a自增1万次

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<semaphore.h>int a;
sem_t sem;
void* thread1(void* arg){for(int i=0;i<100000;i++){sem_wait(&sem);a++;sem_post(&sem);}
}
int main(int argc, char* argv[])
{sem_init(&sem,0,1);pthread_t tid1,tid2;pthread_create(&tid1,NULL,thread1,NULL);    pthread_create(&tid2,NULL,thread1,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("a = %d\n",a);    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/685869.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【软件测试】3.开发模型

目录 1.常见的开发模型 1.1瀑布模型 1.2螺旋模型 1.3增量模型和迭代模型 1.4敏捷模型 1.4.1特点&#xff1a; 1.5Scrum模型&#xff08;三个角色和五个重要会议&#xff09; 1.5.1三个角色&#xff1a; 1.5.2Scrum工作流程&#xff08;五个会议&#xff09; 1.6测试模…

Unreal Engine(虚幻引擎)的版本特点

Unreal Engine&#xff08;虚幻引擎&#xff09;是Epic Games开发的游戏引擎&#xff0c;广泛应用于游戏开发、影视制作、建筑设计、虚拟现实等领域。Unreal Engine版本指的是该引擎的发布版本&#xff0c;不同版本之间在功能、性能和稳定性等方面存在差异。北京木奇移动技术有…

【JavaScript】内置对象 - 数组对象 ① ( 数组简介 | 数组创建 | 数组类型检测 )

文章目录 一、数组对象1、数组简介2、数组创建3、数组检测 - Array.isArray() 方法4、数组检测 - instanceof 运算符 Array 数组对象参考文档 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array 一、数组对象 1、数组简介 在 JavaScr…

【vulhub靶场】Apache 中间件漏洞复现

【vulhub靶场】Apache 中间件漏洞复现 一、Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09;1. 漏洞详情2. 影响版本3. 漏洞复现 二、Apache多后缀解析漏洞&#xff08;apache_parsing_vulnerability&#xff09;1. 漏洞详情2. 漏洞复现 三、Apache HTTP Serv…

JDK不同版本里中国夏令时时间

什么是夏令时&#xff1f; 夏令时&#xff0c;&#xff08;Daylight Saving Time&#xff1a;DST&#xff09;&#xff0c;也叫夏时制&#xff0c;又称“日光节约时制”和“夏令时间”&#xff0c;是一种为节约能源而人为规定地方时间的制度&#xff0c;在这一制度实行期间所采…

day2_greedyIntervalsLRU/LFU

二、贪心算法之区间调度问题 0.计算一个区间集合中无重复的区间的最大数量(模板) public int intervalSchedule(int[][] intvs) {if (intvs.length 0) return 0;// 按 end 升序排序Arrays.sort(intvs, (a, b) -> Integer.compare(a[1], b[1]));// 至少有一个区间不相交in…

摩菲Murphy显示器显示表 总线编程器维修PV780B

Murphy仪器维修包括&#xff1a;摩菲数字显示器&#xff1b;摩菲监视仪表&#xff1b;摩菲CAN总线控制器等维修 维修故障包括&#xff1a;黑屏、指示灯无显示&#xff0c;触摸屏上电无反应&#xff0c; 上电蓝屏、白屏&#xff0c;通电几分钟后屏幕变为蓝屏&#xff0c;主板故…

java中的oop(三)、构造器、javabean、uml类图、this、继承

!! 有get/set方法的情况基本就是说要搞个私有属性&#xff0c;不直接对外开放&#xff1b; 构造器 Person p new Person(); //其中的Person();就是构造器&#xff1b;---造对象&#xff1b;Constructor–建设者&#xff0c;建造者&#xff1b; 作用 搭配new 创建类的&…

DS:顺序表、单链表的相关OJ题训练(2)

欢迎各位来到 Harper.Lee 的学习世界&#xff01; 博主主页传送门&#xff1a;Harper.Lee的博客主页 想要一起进步的uu欢迎来后台找我哦&#xff01; 一、力扣--141. 环形链表 题目描述&#xff1a;给你一个链表的头节点 head &#xff0c;判断链表中是否有环。如果链表中有某个…

java数据结构之数组系统了解

1.数组介绍 数组就是一个存储数据的容器&#xff0c;容器的长度固定、存储元素的数据类型固定。 跟变量加以区分&#xff1a;变量也可以存储数据&#xff0c;但是只能存一个值。当要存的数据比较多的时候&#xff0c;用变量就不方便了。我们就可以使用数组来存储。 1.1数组…

嫁接打印的技术要点

所谓嫁接打印&#xff0c;是一种增减材混合制造的方式。它将已成形的模具零件当作基座&#xff0c;在此基础上“生长”出打印的零件。其中基座通常采用传统加工方式制造&#xff0c;而打印部分则使用专用的金属粉末&#xff0c;通过 3D 打印技术成型。 嫁接打印之所以备受欢迎&…

安全 | 开源入侵防御系统 Snort

目录 Snort 概要 入侵预防系统模式 数据包记录器和嗅探器模式 网络安全学习路线 &#xff08;2024最新整理&#xff09; 学习资料的推荐 1.视频教程 2.SRC技术文档&PDF书籍 3.大厂面试题 特别声明&#xff1a; Snort 概要 Snort 概要 是世界上最重要的开源入…