中断、可重入、线程安全、数据竞争、volatile

1、中断

中断指CPU收到来自硬件或软件的信号,提示发生了某个事件要处理。

中断是计算机中的一个十分重要的概念,在现代计算机中毫无例外地都要采用中断技术。可以举一个日常生活中的例子来说明,假如你正在给朋友写信,电话铃响了。这时,你放下手中的笔,去接电话,通话完毕,再继续写信。在这个例子中,电话铃声称为“中断请求”,你暂停写信去接电话叫作“中断响应”,接电话的过程就是“中断处理”。

计算机为什么要采用中断?为了说明这个问题,再举一例子。假设你有一个朋友来拜访你,但是由于不知道何时到达,你只能在大门等待,于是什么事情也干不了。如果在门口装一个门铃,你就不必在门口等待而去干其它的工作,朋友来了按门铃通知你,你这时才中断你的工作去开门,这样就避免等待和浪费时间。计算机也是一样,例如打印输出,CPU传送数据的速度高,而打印机打印的速度低,如果不采用中断技术,CPU将经常处于等待状态,效率极低。而采用了中断方式,CPU可以进行其它的工作,只在打印机缓冲区中的当前内容打印完毕发出中断请求之后,才予以响应,暂时中断当前工作转去执行向缓冲区传送数据,传送完成后又返回执行原来的程序。这样就大大地提高了计算机系统的效率。

中断可以有优先级,决定在同时发生几个中断的时候,先执行哪个中断响应。如果在响应一个中断,执行中断处理的过程中,又有新的中断事件发生而发出了中断请求,应该如何处理也取决于中断事件的优先级。当新发生的中断事件的优先级高于正在处理的中断事件时,又将中止当前的中断处理程序,转去处理新发生的中断事件,处理完毕才返回原来的中断处理。

2、可重入

  可重入的概念是在单线程操作系统的时代提出来的,单线程操作系统下,一个函数的重入可能是由于UNIX系统的Single处理(中断)或者函数的递归调用。 可重入函数简单来说就是可以被中断的函数,也就是说可以在这个函数执行的任何时刻中断它,转入OS调度去执行另外一段代码,且返回函数时执行结果符合设计时的预期。

若一个函数是可重入的,那么该函数:

  • ①不能含有静态或全局变量数据,即它除了使用自己栈上的变量以外不依赖于任何环境。
  • ②不能依赖于单实例模式资源的锁。
  • ③不能调用不可重入的函数。
 
如以下的sum()函数就是不可重入的,如果函数运行期间被中断执行另一段代码,而另一段代码中也包含对sum()的调用,则可能会出现非预期的结果:
 
int sum(int count)
{
	static int sum = 0;

	for (int i = 1; i <= count; i++)
		sum += i;

	return sum;
}


  由于一些函数是不可重入的,所以当这些函数被中断的时候,比如在其signal信号处理函数中应该尽量简单的去处理,避免在signal处理函数中又调用某些函数,产生可重入问题。

  后来又出现了多线程操作系统,所以一个函数的可重入也有可能是当前线程正在调用该方法,而另一个线程同时也可以调用该方法。

3、可重入与线程安全
由此可见,可重入与线程安全是两个不同性质的概念,可重入一开始是 在单线程操作系统背景下所考虑的数据安全问题,而线程安全是在多线程的环境中考虑的线程共享数据的安全问题。一个方法是可重入的话那么我们就可以在中断处理中再次调用该方法,或者在递归中调用该方法,或者多线程同时调用该方法(但不保证线程安全,仅保证static、全局变量这些共享数据安全)。而在windows下,因为没有中断,所以一个方法是可重入的就表明该方法能够被多个线程同时来调用(但线程安全是不保证的),一个线程安全的函数总是可重入的(保证static等所有共享数据安全),但一个可重入的函数不一定是线程安全的。
 
如以下的increment_counter()函数是线程安全的,因为该函数使用了线程同步保护措施,在多线程环境下执行该代码不会引起数据安全问题。
而该函数是不可重入的,因为函数使用了静态变量。比如在运行到pthread_mutex_lock(&mutex)和pthread_mutex_unlock(&mutex)之间的时候又产生了另一个调用increment_counter函数的中断,则会第二次执行此函数,此时由于mutex已被lock,函数会在pthread_mutex_lock(&mutex)处阻塞,并且由于mutex没有机会被unlock,阻塞会永远持续下去。
int increment_counter ()
{
	static int counter = 0;
	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

	pthread_mutex_lock(&mutex);

	++counter;
	int result = counter;	

	pthread_mutex_unlock(&mutex);

	return result;
}
又比如说malloc函数是线程安全的但不是可重入的,当在malloc的中断处理中再次调用malloc的话会产生不可预料的结果。如果一个函数中调用了malloc那么就可以认为它不是可重入的。
 
4、竞争、竞态条件
  

  竞争:多个线程对于共享数据进行访问,最少有一个线程是进行的写入,并且没有使用同步访问手段,这种情况就会产生数据竞争。避免数据竞争的方法是使用同步锁,在Java中还可以将变量声明为volatile类型来解决竞争问题,因为volatile会保证变量的写操作完成后后面的读操作再执行。如下代码所示,如果去掉锁的话,两个线程执行该方法的话就会发生数据竞争:

void setValue()
{
	lock.lock();
	g_value++;
	lock.unlock();
}

  竞态条件:计算的正确性取决于多个线程的交替执行的时序,最常见的竞态条件就是先检测后执行,比如下面的代码,在A线程取得状态为ture后可能另一个线程立即修改了状态为false,而A线程继续以状态为true的前提来做一些事情,这就可能导致出现问题。如果将下面代码中同步锁去掉的话就会既有数据竞争又存在竞态条件:

void func()
{
	lock.lock();
	bool bFlag = getState();
	lock.unlock();

	if (bFlag)
	{
		//ToDo
	}
}

  解决竞态条件的方法也是使用同步手段:

void func()
{
	lock.lock();
	bool bFlag = getState();
	if (bFlag)
	{
		//ToDo
	}
	lock.unlock();
}

5、volatile

  volatile其实是C中的关键字,它与多线程也没有关系,不能用来作为线程同步。它用来指示编译器不要对访问该变量的代码进行优化,比如在汇编中存取变量的时候直接从内存访问该变量,而不是寄存器。比如我们在中断(信号处理方法)中修改了一个全局变量,在中断执行完毕后获得该变量的值仍然是原来的值,而不是中断修改后的值,这是因为变量在中断中的修改编译器并没有察觉到,所以在进行编译的时候就会进行编译优化,变量会从寄存器中读取。
  C++中的volatile只在下面三种情况下使用是合适的: ①、和信号处理相关的场合。 ②、和内存映射硬件相关的场合。③、和非本地跳转(goto只能跳转到本函数方法内,所以为本地跳转,使用setjmp、longjmp能跳转到其它方法内,即非本地跳转)相关的场合。
 
参考出处:http://zh.wikipedia.org/wiki/%E5%8F%AF%E9%87%8D%E5%85%A5
                    http://zh.wikipedia.org/wiki/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8
                    http://baike.baidu.com/link?url=BfeuNAQKvioI5vBmZMQ4KCor5qe32zgYM0lIY52WRuWKVLL6M0j5MxZgoAy-Ut_phSooxzUKtP4OiGIdCHfWlWWgGVBj58EB0N2ZeEr3dVbjtHWCN1TSXLnZf-rCTI3B
                     http://baike.baidu.com/subview/121718/5143456.htm#viewPageContent
                     http://zh.wikipedia.org/wiki/%E4%B8%AD%E6%96%B7
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值