线程的同步和互斥

线程的同步和互斥

今天笔者和大家一起学习一下线程的同步和互斥,最简单的理解,同步就是按照某种次序去访问资源,互斥就是某种资源同时只能有一个访问者去访问,因此对于资源的访问就是原子性的。即也就是当前资源已经被占用或者没有被占用两个情况,不存在第三种情况。我们先来看一段代码:

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

int count = 0;

void* thread_count(void* arg)
{
	int i = 0;
	while(i<50000000)
	{
		count++;
		i++;
	}
}


int main()
{
	pthread_t id1,id2;
	pthread_create(&id1,NULL,thread_count,NULL);
	pthread_create(&id2,NULL,thread_count,NULL);

	pthread_join(id1,NULL);
	pthread_join(id2,NULL);

	printf("count:%d\n",count);
	return 0;
}
我们将刚才的程序生成可执行文件之后,多运行几次,还会看见如下:

    此时发现有几次的运行结果不对,不是100000000,造成这个的原因就是在在thread_count函数中对于coun++不是原子性的,在两个线程都在同时运行这个功能的时候,如果循环的次数比较小,几乎不会出现该错误,是因为,在线程2还没有创建完成的时候,线程1就已经将count累加完成了,但是现在,每将功能执行一次,count就要累加50000000次,在一个线程还没有将count累加完成的时候,线程2就也开始对count进行的累加,问题就出现在这里,当两个线程同时对count进行累加,而计算机中的能够进行累加的部件就只有CPU,所以当线程1把count从内存中拿出来,放到寄存器的时候,此时会有一个上下文保护,记录count的值为0,然后放到CPU中进行累加,但是就在CPU累加count的时候,线程2,将count的值取出来,也进行累加,此时count的是值已经成为1了,但是当线程1从CPU中将累加完成之后count的值拿出来的时候,根据自己的上下文数据,认为count还是0,然后把count赋值为1,这个时候,就把线程2的累加结果覆盖掉了,这样累加的结果就小于100000000了,这样的情况对发生几次,也就造成了刚才的结果。

    也正是因为累加要把数据从内存拿到CPU执行,有了一个从用户态和内核态之间转换,才造成了这样子的结果,而当累加的结果比较小的时候就不会,是因为以当前计算机的计算速度,小一点的数字,线程2还没有创建完成,此时线程1的累加工作就已经完成了,那么我们怎么在累加次数比较小的时候,让他出错呢?就是让他增加用户态和内核态的一个转化。代码如下:

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

int count = 0;

void* thread_count(void* arg)
{
	int i = 0;
	int tmp = 0;
	while(i<5000)
	{
		tmp = count;
		//printf是往stdout上输出,既然应用软件要控制硬件,必定要调用系统接口,所以printf底层一定封装了系统调用
		//既然要调用系统接口,即一定得完成用户态和内核态的转换
		printf("%d\n",tmp);
		count = tmp+1;
		i++;
	}
}


int main()
{
	pthread_t id1,id2;
	pthread_create(&id1,NULL,thread_count,NULL);
	pthread_create(&id2,NULL,thread_count,NULL);

	pthread_join(id1,NULL);
	pthread_join(id2,NULL);

	printf("count:%d\n",count);
	return 0;
}
当多次运行这段代码的时候,会发现也会出错,产生错误的原因就是临界资源的访问不是原子的,解决这个问题的方法就是:保证资源的原子性。保证资源原子性的第一种方法,就是:互斥锁,

互斥锁

怎么定一个锁呢?Linux中man手册可以查到:

现在我们在之前累加5000的代码中加入互斥锁,使得错误消失,代码如下:

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

int count = 0;
//创建变量,使用对应的初始化方法来进行初始化
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* thread_count(void* arg)
{
	int i = 0;
	int tmp = 0;
	while(i<5000)
	{
		//加锁
		pthread_mutex_lock(&lock);
		tmp = count;
		printf("%d\n",tmp);
		count = tmp+1;
		i++;
		//解锁
		pthread_mutex_unlock(&lock);
	}
}


int main()
{
	pthread_t id1,id2;
	pthread_create(&id1,NULL,thread_count,NULL);
	pthread_create(&id2,NULL,thread_count,NULL);

	pthread_join(id1,NULL);
	pthread_join(id2,NULL);

	pthread_mutex_destroy(&lock);
	printf("count:%d\n",count);
	return 0;
}
此时,当一个线程访问临界资源的时候,其他线程因为互斥锁的原因是不能访问的,所以此时,当线程申请锁资源的时候,该线程就会被挂起,此时的状况和进程的信号量申请失败有些类似。但是还有一种情况,就是当一个优先级比较高的线程,循环的访问一块资源,也就是释放资源之后,又开始访问,这个时候,其他线程还是没有办法访问临界资源,此时解决的办法如下:

    互斥锁中,每一个mutex都有一个等待队列,一个线程要在mutex上挂起等待,首先要把自己加入到等待队列中,然后状态置为睡眠,然后调用调度器函数切换别的线程,一个线程要唤起等待队列的其他线程,只需从等待队列中取出一项,把其状态从睡眠改成就绪,然后加入到就绪队列,那么下一次调度器就有可能切换到被唤起的线程,以上的动作也就是我们的“挂起等待”和“唤醒等待线程”。

死锁:

①一般情况下,如果一个线程先后两次调用lock,那么第二次调用lock的时候,由于锁已经被占用了,那么自己就会被挂起,进入到等待队列,但是锁是被自己使用的,被挂起之后也就没有机会来释放锁,此时就会永远的被挂起,造成死锁。

②当两个线程1和线程2,线程1申请使用锁1,线程2申请使用锁 2,此时线程1申请锁2,但是锁2被线程2使用,此时线程1被挂起,而同时,锁2也申请锁1 ,但是锁1 被线程1占用,且线程1已经被挂起,无法释放锁1,此时线程2也被挂起,这样锁2也无法释放,这样也就造成了死锁。

死锁产生有四种必要情况:

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源
如何避免死锁?
如果程序中有用到lock1,lock2,lock3,且3个锁的地址:lock1 < lock2 < lock3,那么在线程需要获得2个或者3个锁的时候,都应该按照
lock1 -> lock2 -> lock3的申请顺序获得。如果确定顺序比较困难,那么在加锁的时候尽量使用pthread_mutex_trylock代替pthread_mutex_lock。



限于编者水平,文章难免有缺漏之处,欢迎指正。
 如需转载,请注明出处~!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值