当前位置: 首页 > 科技观察

C语言花絮2:用纯软件替代Mutex互斥锁

时间:2023-03-12 05:02:30 科技观察

1.前言在Linux系统中,多个线程并行执行时,如果需要访问同一个资源,需要在访问资源时使用操作系统提供给我们保护的同步原语。同步原语包括:互斥量、条件变量、信号量等,被保护的代码称为“临界区”。这是一个非常正式的流程,这基本上就是我们所有人所做的。你有没有想过这些同步原语会对代码的执行效率产生多大的影响?是否可以使用其他纯软件的方法来达到保护临界区的目的,而不是使用操作系统提供的这些机制?在这篇文章中,我们介绍了彼得森(Petersen)算法,它可能不是很实用,但是可以给我们带来一些思考,提高我们的编程元技能。2.Peterson算法简介该算法主要用于解决临界区的保护问题。我们知道临界区必须保证三个条件:互斥访问:在任何时候,最多有一个线程可以进入临界区;在申请进入临界区的线程中,选择其中一个,让它进入临界区;有限等待:线程申请进入临界区时,不能无限等待,必须在限定时间内获得进入临界区的许可。也就是说,不管它的优先级有多低,都不应该饿死在临界区的入口处。Peterson算法是一种实现互斥锁的并发编程算法,可以控制两个线程访问共享的用户资源而不会发生访问冲突。Peterson算法基于两个线程互斥访问的LockOne和LockTwo算法。LockOne算法使用标志布尔数组来实现互斥;LockTwo使用一个turninteger来实现互斥;这两种算法都实现了互斥,但都有死锁的可能。Peterson算法结合了这两种算法,完美的实现了软件中的双线程互斥问题。该算法描述了两个重要的全局变量如下:1.标志数组:有2个布尔型元素,分别代表一个线程是否申请进入临界区;2.turn:如果两个线程都申请进入临界区,这个变量将决定让哪个线程进入临界区;3.测试代码//2个线程同时访问的全局资源staticintnum=0;BOOLflag[2]={0};intturn=0;void*thread0_routine(void*arg){for(inti=0;i<1000000;++i){flag[0]=TRUE;turn=1;while(TRUE==flag[1]&&1==turn);//临时区号num++;flag[0]=FALSE;}returnNULL;}void*thread1_routine(void*arg){for(inti=0;i<1000000;++i){flag[1]=TRUE;turn=0;while(TRUE==flag[0]&&0==turn);//临时区码num++;flag[1]=FALSE;}returnNULL;}全局资源num初值为0,两个程序分别递增100万次,所以最后的结果应该是200万,和实际测试结果确实一样。四、Mutex对代码执行效率的影响1、单线程中:Mutex对代码执行效率的影响for(inti=0;i<1000000;++i){num++;}以上代码,耗时约:1.8ms--3.5毫秒。for(inti=0;i<1000000;++i){pthread_mutex_lock(&mutex);num++;pthread_mutex_unlock(&mutex);}上面的代码大约耗时23.9ms——38.9ms。可以看出加锁和解锁对代码执行效率的影响还是很明显的。2.在多线程中:Mutex对代码执行效率的影响;}returnNULL;}void*thread1_routine(void*arg){for(inti=0;i<1000000;++i){pthread_mutex_lock(&mutex);num++;pthread_mutex_unlock(&mutex);}returnNULL;}耗时:thread0:差异=125.8毫秒线程1:差异=129.1毫秒3。在两个线程中,使用Peterson算法保护临界区耗时:thread1:diff=1.89msthread0:diff=1.94ms5.总结Peterson算法使用纯软件保护临界区,比使用提供的互斥锁表现出更好的性能由操作系统。但是它也有一个缺点:只能在2线程中使用,但是因为和平台无关,所以在一些特殊的场合,或许可以为我们所用!