Linux之线程:同步与互斥

首先我们来说一下同步是什么:    

            其实所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

    也就是针对于同一个需要解决的问题,但是我们需要多个执行流进行同时操作,所以我们就需要对这个任务进行分工计算,最终得到一个最终结果,相当于树的根部求值。唯一不同的就是线程/进程之间进行访问操作,总有访问到相同的数据性,如果当前的计算机的访问问题存在不一致的原子性,就会出现我们资源上的抢占回访错误或者出现死锁问题,这个我们后面提。


说简单了就是:同步就是多个线程按照约定顺序共同完成一个任务。也就是线程控制与创建。


然后我们看一段代码:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000

static int g_count = 0;

void* read_write_mem(void *_val)
{
	int val = 0;
	int i = 0;
	for (;i<NLOOP;++i)
	{
		val = g_count ;
		printf("pthread id is :%x,count is :%d\n",(unsigned long )pthread_self(),g_count);
		g_count = val +1;
	}
	return NULL;
}

int main()
{
	pthread_t tid1;
	pthread_t tid2;
	pthread_create(&tid1,NULL,read_write_mem,NULL);
	pthread_create(&tid2,NULL,read_write_mem,NULL);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	printf("count final val is :%d\n",g_count);
	return 0;
}

就是创建了2个线程,然后共同进行运算,运算次数5000次,首先需要注意的是。

如果没有:val = g_count;

     g_count = val+1;

    而是直接的++g_count;缺少寄存器存储值转换的话,操作系统反应很快。将无法体现出线程的剥夺出错。

然后运行结果是这样:

wKiom1cn_gzwCCsrAAAz3d5FjzI759.png

spacer.gif最终的结果并不是10000.然后为了解决资源获取非原子性的问题。就需要了解线程互斥的概念。


什么是互斥?

  其实互斥从字面意思来看就是2个线程是相互排斥,其实互斥真正指的是针对于临界区对临界资源操作访问的唯一性,就是当一个线程中的加锁代码段(临界区)访问共同的临界资源,出现了唯一访问权限,只有抢占这个的访问完毕后,才能够让其他的线程进入抢占。也就是锁的原理。


关于线程互斥锁的相关函数:

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);//attr一般使用默认值为NULL
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

其中PTHREAD_MUTEX_INITIALIZER必须初始化,代表锁的创建初始化。

加锁,去锁函数

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);//阻塞式
int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞式
int pthread_mutex_unlock(pthread_mutex_t *mutex);

然后我们将上一段代码改成加锁以后看看效果是什么样的:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000

static int g_count = 0;

pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;
void* read_write_mem(void *_val)
{
	int val = 0;
	int i = 0;
	for (;i<NLOOP;++i)
	{
		pthread_mutex_lock(&mutex_lock);
		val = g_count ;
		printf("pthread id is :%x,count is :%d\n",(unsigned long )pthread_self(),g_count);
		g_count = val +1;
		pthread_mutex_unlock(&mutex_lock);
	}
	return NULL;
}

int main()
{
	pthread_t tid1;
	pthread_t tid2;
	pthread_create(&tid1,NULL,read_write_mem,NULL);
	pthread_create(&tid2,NULL,read_write_mem,NULL);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	printf("count final val is :%d\n",g_count);
	return 0;
}


运行结果:

wKioL1coAa7gPxpUAAC4WqInUxQ275.png

很显然,结果变成了10000,资源上面的读取与存储体现了资源互斥的现象,操作的独占性。

了解了线程互斥锁,我们来看一下互斥锁的实现机制:

    Mutex的两个基本操作lock和unlock是如何实现的呢?假设Mutex变量的值为1表示互斥锁空闲,这时某个进程调用lock可以获得锁,而Mutex的值为0表示互斥锁已经被某个线程获得,其它线程再调用lock只能挂起等待。那么lock和unlock的伪代码如下:

wKiom1coJ_qjv1zVAACIBeoCPyg574.png

    unlock操作中唤醒等待线程的步骤可以有不同的实现,可以只唤醒一个等待线程,也可以唤醒所有等待该Mutex的线程,然后让被唤醒的这些线程去竞争获得这个Mutex,竞争失败的线程继续挂起等待。

    细心的读者应该已经看出问题了:对Mutex变量的读取、判断和修改不是原子操作。如果两个线程 同时调用lock,这时Mutex是1,两个线程都判断mutex>0成立,然后其中一个线程置mutex=0,而另一个线程并不知道这一情况,也置mutex=0,于是两个线程都以为自己获得了

锁。

    为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下(以x86的xchg指令为例):

wKiom1coKJbz_6cZAAEfeszhW6s842.png

其实锁的真正实现机制就是利用交换来保证1的可进行令牌有且只有唯一一个,就不会出现访问紊乱了。。


    一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,这叫做死锁(Deadlock)。另一种典型的死锁情形是这样:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都 永远处于挂起状态了。不难想象,如果涉及到更多的线程和更多的锁,有没有可能死锁的问题将会变得复杂和难以判断。

    写程序时应该尽量避免同时获得多个锁,如果一定有必要这么做,则有一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。比如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3,那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确 定一个先后顺序比较困难,则应该尽量使pthread_mutex_trylock调用代替pthread_mutex_lock调用,以免死锁。


本文出自 “剩蛋君” 博客,请务必保留此出处http://memory73.blog.51cto.com/10530560/1769667

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值