线程同步(1)-- 互斥量与读写锁

目录:

10.1、线程为什么要同步

10.2、互斥量

10.3、死锁

10.4、读写锁




10.1 线程为什么要同步

做个小实验吧


#include<pthread.h>
#include<unistd.h>
#include<stdio.h>

int count = 0;//声明全局变量,等下就看看它了

void *run(void *arg)
{
	int i = 0;
	for(i = 0;i < 5000; i++)
	{
		count++;
	}
	
	return (void*)0;
}

int main(int argc,char **argv)
{
	pthread_t tid1,tid2;
	int err1,err2;
	err1 = pthread_create(&tid1,NULL,run,NULL);
	err2 = pthread_create(&tid2,NULL,run,NULL);

	if(err1==0 && err2==0)//俩线程都成功创建出来
	{
		sleep(1000);
		printf("count:%d\n",count); //这个换行呐,不能漏掉
		
		pthread_join(tid1,NULL);
		pthread_join(tid2,NULL);
	}
	return 0;
}

我临时写的,如果结果是10000那就不用往下看了。

好,为什么要线程同步,那就心照不宣了

算了,官方话还是要说一说的

1、共享资源,多个线程都可以对共享资源进行操作
2、线程操作共享资源的先后顺序不一定
3、处理器对存储器的操作一般不是原子操作

10.2互斥量

鉴于上面的实验,我们知道了线程需要同步
那怎么同步?互斥量就是一种办法。

什么叫互斥量,顾名思义就是咱这么多人,只能有一个使用这个资源,就像共享小单车,一次只能给一个人用,一个人下车锁车了,另一个人才能去扫码开锁。

10.2.1 临界区

临界区用来保证在同一时刻只有一个线程可以访问到资源,对于临界区进行操作的函数有两个:


EnterCriticalSection();
LeaveCriticalSection();

临界区最大的特色是其同步速度很快,但是其只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

10.2.2 互斥量 (注(1))

互斥量和临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,互斥量比临界区负责,并且互斥量是可以命名的,因此互斥量不仅仅可以用于同一应用程序不同线程中资源的同步,也可以用于不同应用程序的线程之间实现对资源的同步。

因此互斥量可以在整个系统中被任意进程的任意线程访问到,但它严格限定只有获取了互斥量的线程才能释放该互斥量。

10.2.3 互斥量原语
pthread_mutex_t mutex = PTHREAD_MUREX_INITALIZER //用于初始化互斥锁,后面简称锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); //初始化锁,和上面那个一个意思。
//初始化一个互斥锁(互斥量)–>初值可看做1
int pthread_mutex_destroy(pthread_mutex_t mutex); //销毁锁
int pthread_mutex_lock(pthread_mutex_t mutex); //上锁
int pthread_mutex_unlok(pthread_mutex_t mutex); //解锁
int pthread_mutex_trylock(pthread_mutex_t mutex); //尝试上锁

参数释义:

一般没啥事儿别用那个init,用都麻烦
pthread_mutex_t 类型,其本质是一个结构体,为简化理解,应用时可忽略其实现细节,简单当成整数看待。

<这里只释义那个init>

参数1:传出参数,调用时应传&mutex
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。
参数2:互斥属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享).
静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化。pthread_mutex_init(&mutex, NULL);

好,接下来我们给开头那段代码上锁
看看在哪里上锁啊

#include<pthread.h>
#include<unistd.h>
#include<stdio.h>

pthread_mutex_t mutex = PTHREAD_MUREX_INITALIZER; //这样多好
int count = 0;//声明全局变量,等下就看看它了

void *run(void *arg)
{
	int i = 0;
	pthread_mutex_lock(&mutex);  //好,搁这儿上锁
	for(i = 0;i < 5000; i++)
	{
		count++;
	}
	pthread_mutex_unlock(&mutex); //好,记得上锁和解锁要配在一起写,就算忘记在中间写东西也得把这俩黏一块儿
	
	return (void*)0;
}

int main(int argc,char **argv)
{
	pthread_t tid1,tid2;
	int err1,err2;
	err1 = pthread_create(&tid1,NULL,run,NULL);
	err2 = pthread_create(&tid2,NULL,run,NULL);

	if(err1==0 && err2==0)//俩线程都成功创建出来
	{
		sleep(1000);
		printf("count:%d\n",count); //这个换行呐,不能漏掉
		
		pthread_join(tid1,NULL);
		pthread_join(tid2,NULL);
	}
	return 0;
}

好,再拿去执行以下,如果不是10000也不用往下看了

10.3死锁

为什么我要强调那俩一定要放在一起写,就是防止出现人为失误导致死锁
死锁嘛,解不开了。
要么是你忘了解开,别人也就没得用了
要么就是几个线程互相掐着关键数据导致谁也没办法完成任务,结果谁也没办法解锁。
这种情况下只有销毁掉代价最小的那个锁,让任务执行下去,不过后面要记得把那个被销毁的任务重新运作。

10.4 读写锁

读时共享,写时复制

在这里插入图片描述

读写锁是效率更高的互斥量(在大多数条件下)。

读写锁工作机理:
读写锁分为三种状态

读锁、写锁、不加锁
读写锁特性(12字):写锁优先级高,写独占,读共享

读锁:当某文件添加读锁时,如果要对此文件再申请读操作,可以。如果要对文件执行写操作,阻塞。如果同时要对文件添加读写操作,也阻塞,等这个读操作结束之后先执行写操作,不能让写操作阻塞太久。

写锁:阻塞外部请求

读写锁非常适合对数据读的次数比写的次数多。

读写锁原语:

初始化读写锁


// pthread_rwlock_t类型 , 用于定义一个读写锁变量

//pthread_rwlock_init 初始化一把读写锁
pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER; //这个方法好

int pthread_rwolck_init(pthread_rwlock_t*restrict rwlock,const pthread_rwlockattr_t*restrict attr); //还是用上面那个吧

参数释义:restrict关键字:只用于限制指针,所有修改该指针指向内存中内容的操作,只能通过本指针来完成,不能通过除本指针之外的其它变量或指针修改。

参数2:attr表读写属性,通常使用NULL,表示默认属性

销毁读写锁


//销毁一把读写锁:pthread_rwlock_destroy

int pthread_rwlock_destroy(pthread_rwlock_t * rwlock);  

尝试加锁


//以读方式请求加锁:pthread_rwlock_rdlock
int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock);   

//以写方式请求加锁:pthread_rwlock_wrlock
int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock);    

解锁


int pthread_rwlock_unlock(pthread_rwlock_t * rwlock);   

尝试加锁


//非阻塞请求读锁:pthread_rwlock_tryrdlock
int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock); 

//非阻塞请求写锁:pthread_rwlock_trywrlock
int pthread_rwlock_trywrock(pthread_rwlock_t * rwlock); 

栗子就不加了,把上面那个代码里面的锁可以改成写锁。
一个线程可以同时拥有n个读锁,不过解锁也需要使用n个解锁。

发布了61 篇原创文章 · 获赞 3 · 访问量 1631

猜你喜欢

转载自blog.csdn.net/qq_43762191/article/details/103956989