(3)并发安全问题起源于同步与争夺
在WinCE多线程共享资源中防止访问冲突是极为重要的。正常来说,被允许执行的线程首先会拥有对资源的排他性访问权。当第一个线程访问资源时,第一个线程会给该访问资源加锁,而这个锁会导致其它也想访问同一资源的线程被阻塞,直至第一个线程释放它加在资源上的锁。也就是说,在WinCE系统中如果一个线程不能得到需要的某个资源,它将挂起执行(阻塞),直到该资源有效为止。所以,在WinCE系统运行过程中,各线程都将会对资源进行锁定或解锁。
从理论上说,对资源的锁定或解锁动作应该是能避免资源竞争情况的出现。但由于各线程运行和指向其资源的相对时序各不相同,就有可能出现由于各个线程正在等待被其它线程保持的资源,从而导致所有线程都无法运行的情况。如果系统中所有进程都进入死锁,即没有一个进程能前进的话,称为系统死锁,这种情况是很少见的,我们在这次项目中经常遇到的是进程死锁。
3.应用同步机制,避免并发安全问题发生
WinCE系统的线程要经常在某些条件下进行状态切换,这可能是自身行为引起的,也可能是外界行为引起。在大多数情况下,这些线程之间是要相互通信、相互协调才能完成任务的。WinCE给我们提供了很多的同步机制,包括临界区、互斥体、信号量、事件、互锁函数和消息队列等。这里只介绍我在这次项目中应用的几种主要方法:
(1)临界区
临界区(Critical Section)是一种防止多个并发线程同时执行一个特定代码节的机制。在这次项目反复的调试中,我得到一个宝贵的经验,就是在跟踪代码中的多线程处理性能时,对 WinCE中的临界区深刻理解是非常有用的。临界区是一种轻量级机制,它在某一时间内只只允许一个线程执行某个给定的代码段。因此,通过对临界区进行有效管理,使得某一时刻最多只能有一个线程进入临界区,就能实现对临界资源的保护。
临界区对象是运行在用户模式的,利用临界区可以实现对临界资源的互斥操作。它能保证在临界区内所有被访问的资源不被其它线程访问,直到当前线程执行完临界区代码。我在这次项目中得到的教训是:WinCE的临界区是只能应用在同一进程内,亦即实现的是同一进程内的线程间同步,不能应用在进程之间,这一点的实践经验可谓是血汗的结晶。例如,当有两个线程,每个线程都有临界区,而且临界区保护的资源有相同的时候,这时就要在编写代码时多加考虑。另外,在使用临界区时一定要注意避免死锁,如果能够更深入地了解临界区的工作原理,则这一情形的出现就不会令人太沮丧。
(2)互斥体
互斥体(Mutex)顾名思义就是实现对共享资源实现互斥的访问。在使用上,互斥体与临界区都能实现的对共享资源的互斥访问,但是临界区是多个线程对同一段程序的执行,这段程序会访问到临界资源,所以它们是同一个进程内的多个线程;而互斥体的应用情景则是在线程之间的独立执行,可以不是程序上的重叠,只是一个线程执行到共享资源的时候,有可能别的线程也要访问该共享资源,所以要用互斥体来保护该共享资源。
一般来说,互斥对象是运行在内核模式的,它的行为特性同临界区非常相似,在一个线程访问某个共享资源时,它能够保证其它线程不能访问这个资源。不同的是互斥对象是运行在内核模式,从时间上比临界区要慢。但由于内核对象具有全局性,不同的进程都能够访问,这样利用互斥对象就可以让不同的进程中的线程互斥访问一个共享资源,而临界区只能在一个进程内有效,这一点在使用上需要特别的注意,否则容易误用导致经常报错。
(3)其它的并发同步机制和方法
WinCE系统中还广泛使用事件(Event)机制来实现线程之间的协调工作。具体方式是用通知来告知一个线程什么时候去执行它的特定的任务,并标识事件的发生。而信号量(Semaphore)同步机制是用一个数值表示当前该信号量的使用情况,当前值的大小处于零和最大值之间。在WinCE中如果把信号量的最大值和初始值均设置为1,那么它就可实现互斥体,即保证对共享资源互斥访问的保护。如果把信号量的初始值设置为0,就是要等待别的线程来唤醒它,那么它就可实现事件机制。信号量机制的最大特点是可以用在同一进程内的线程之间同步,也可以用在进程之间的同步。
消息队列通信(MsgQueue P2P)同步机制是如同建立了一个管道,各方的线程分别在管道的两端建立读/写端口。管道内的消息组成一个先进先出FIFO(First In First Out)的队列,在读端口读取的消息是读取管道内消息组队列的头消息,写入消息时则是在写端口的队列尾部追加一个消息。消息队列既可以作为在线程之间传递数据的工具,也可以作为线程之间同步的工具。
除了上面的同步方法之外,WinCE还提供了一些用于原子操作的互锁函数。这些函数在执行过程中,不会因为线程的调度引起的当前线程被抢占而打断函数内的操作。互锁函数运行在用户模式,它能保证当一个线程访问一个变量时,其它线程无法访问此变量,以确保变量值的唯一性。这种访问方式也被称为原子访问。总的来说,WinCE多线程编程是一个很重要的功能,但必须要正确的使用同步机制来保证其安全性。