WinCE多线程事半功倍 但须避免资源争夺

       WinCE系统是一个多任务的操作系统。一般来说,实现多任务的方法是采用多线程和多进程机制。因此掌握多进程和多线程的技巧和方法,对于编写复杂的WinCE嵌入式程序是必须的。因为多线程编程方法能有效地解决各种并行的复杂工作任务,使一些特别复杂的总体设计和解决方案变得更简洁和更清晰。

  近期,我在一个嵌入式开发项目中就体验到轻视多线程的惨痛教训。由于在开发过程中对多线程没有处理好,结果是经常会出现线程争夺资源,使系统变得缓慢或直接瘫痪,这些难以察觉的多线程处理错误使到项目组成员被迫花了许多额外的时间来处理。不但项目开发进度严重滞后,而且还影响到产品的上市时间。在WinCE编程中,其实多线程处理并不象表面看起来的那么简单。本文分享我在此项目得到的一些关于多线程的经验和教训。

  一. 多线程中最常见的两类资源争夺问题

  一个嵌入式系统开发一般来说有两种编程方式:一是内核态编程,另一种是用户态编程。当一个进程执行系统调用而陷入内核执行时,称为进程处于内核态,此时CPU处于特权级最高的内核执行过程。当进程在执行用户自己的代码时,则称为处于用户态,此时CPU在特权级最低的用户代码中运行。在内核态下的CPU可执行任何指令,在用户态下的CPU只能执行非特权指令,用户态的程序不能随意操作内核地址空间。所以,多线程资源冲突在内核编程中远比用户态程序的编程要常见。

  在这次嵌入式项目中,由于依仗WinCE内核是由我们项目组自已定制和剪裁的,许多项目成员为了让程序的执行效率更高、代码更短,不但经常把进程陷入到内核运行态中执行,而且还过份的强调利用多线程来提高执行效率。结果是不但没有提高程序执行效率,反而使到WinCE系统经常出现资源争夺情况。轻则运行缓慢,重则死锁和死机。我们总结这次开发失败的经验教训是:多线程编程能让开发事半功倍,但一定要注意多线程资源争夺的安全性。这是一个开发人员如何做到“鱼与熊掌”两者平衡兼得的考验问题。

  (1)多线程资源冲突问题

  WinCE系统支持多线程,这无疑是一件很好的事情。但这同时也带来了另一个比较棘手的设计问题,那就是如何让多线程的资源争夺是安全的,这是非常重要的。一般的说,多线程的资源安全性是指一个线程在被调用过程中,还未返回时又再次被其它线程调用的情况下执行结果的可靠性。如果结果是可靠的,则称这个多线程安全的;如果结果是不可靠的,则称这个多线程是不安全的,原因是发生了多线程资源冲突问题。因此,多线程资源安全性如果处理不当,轻则导致程序运行时出错,严重的话将导致系统崩溃。

  2)多线程死锁问题

  从以上第一点我们可知,在WinCE多线程编程中防止线程资源冲突是极为重要的。因此,这个项目开发过程中我们大量通过锁定一个资源来防止任何其它线程访问这个资源,以避免资源竞争。但没有想到的是,这又导致了多线程资源争夺的另一个常见问题:死锁。结果在项目测试时,我们遇到的最常见的现象就是多线程因资源竞争不当而产生死锁的现象,后来需要逐一分析资源死锁的过程,这让我们开发组的各成员都备受折磨和感到痛苦不堪。

  所谓死锁(Dead Lock)是指在WinCE编程中两个或两个以上的并发进程在执行过程中,如果每个进程持有某种资源而又都在等待别的进程释放它们现在保持着的资源,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法进行下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

 

二. 为什么多线程会产生资源争夺?

  进程、线程是WinCE内核最基本的服务,也是内核最主要的组成部分。这两方面的知识是一个WinCE嵌入式软件开发人员必须掌握的基础知识,只有掌握了这些知识,才能够充分利用WinCE系统内核提供的服务。

  (1)什么是多线程?

  在谈到多线程资源安全性的时候,我们首先必须了解什么是线程。WinCE是一个抢占式多任务操作系统,抢占多任务又称为调度。多线程是这样一种机制,它是指允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。每一个线程都有五种状态,分别为运行、挂起、睡眠、阻塞、终止。多线程是多任务的特殊形式,多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。

  除了一些在运行中必不可少的资源(比如寄存器和栈等)外,线程本身并不占有系统的资源。但是线程却可以和同属于一个进程的另一个线程共享进程所拥有的全部资源。同进程一样,一个线程可以创建或者撤销另一个线程,同一个进程内的线程也可以并发执行。从并发性的角度来看,不仅可以在不同的进程之间并发执行,在同一个进程中的多个线程之间也是可以并发执行的。因此,这种方式可以使WinCE系统具有更好的并发执行能力,可以更为有效的利用系统资源,同时在一定程度上也极大的提高了系统的运行效率。

  (2)多线程资源争夺起源于同步与竞争

  在WinCE多线程共享资源中,防止访问冲突是极为重要的。正常来说,被允许执行的线程首先会拥有对变量或对象的排他性访问权。当第一个线程访问对象时,第一个线程会给访问对象加锁,而这个锁会导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。在WinCE系统中,如果一个线程不能得到需要的某个资源,它将挂起执行(阻塞),直到该资源有效为止。所以,在WinCE系统运行过程中,各线程都将会对资源进行锁定或解锁。

  因此,从上可知在线程之间要相互通信、相互协调才能完成任务。例如,当有多个线程共同访问同一个资源时,就必须保证第一个线程在正读取这个资源数据的时候,其它线程不能够修改它,这就需要线程之间相互通信。再有当一个线程要准备执行下一个任务之前,它必须等待另一个线程终止才能运行,这也需要彼此相互通信。一般在用户态模式下的同步方法有:互锁函数Interlocked(属原子访问),临界区Critical Section(保证临界区内的资源不被其它线程访问)。在内核模式下的同步方法有:事件对象Event(线程睡眠,而内核执行等待),互斥对象Mutex(类似于临界区,但相对较慢),信标对象Semaphore(用于限制资源访问数量),消息队列MsgQueue(利用很小的内存传递消息)等。

 

三. 如何避免多线程出现争夺资源问题?

  多线程执行是WinCE系统的特点之一,我们在编程时应该要大力的利用,以提高程序的执行效率。但是也必须要小心处理多线程问题,避免出现多线程争夺资源。一般来说,针对不同的情况有不同的处理方式,主要分为以下两种情况:

  (1)建立同步机制,避免进程互斥竞争

  在正常情况下,一个进程的运行一般是不会影响到其它正在运行的进程的。但是对于某些有特殊要求的,如需要以独占资源的进程(或线程)就要求在其进程运行期间不允许其它进程试图使用此资源,这就引出了进程互斥竞争的问题。要实现进程互斥的核心思想比较简单:进程在启动时首先检查当前系统是否已经存在有此进程的实例,如果没有,进程将成功创建并设置标识实例已经存在的标记。此后再创建进程时将会通过该标记而知晓其实例已经存在,从而保证进程在系统中只能存在一个实例。具体可以采取内存映像文件、有名事件量、有名互斥量以及全局共享变量等多种方法来实现。

  一般来说,使用临界区、互斥、信号量和事件可解决线程同步的问题。这里介绍两种最常用的方法:①临界区:它是一种最直接的线程同步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。②互斥机制:互斥非常类似于临界区,但有两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且能通过引用此名字创建现有互斥对象的附加句柄。

  另外,还需要提醒的是临界区与事件对象的最大的区别是在性能上。临界区在没有线程冲突时,一般要用到10-15个时间片,而事件对象由于涉及到系统内核则要到用400-600时间片。所以,临界区是一个进程里的所有线程同步的最好办法,因为它不是系统级的,只是进程级的。

  (2)建立资源分配图,避免进程死锁

  一般来说,多线程产生死锁是与进程对资源的需求、进程的执行速度、资源的分配策略有关。因此,如果无法通过设计来避免死锁,则应该建立资源分配图。通过仔细跟踪系统中的所有线程和它们锁定的共享资源,周期性地进行检查和维护资源分配图,及时发现循环等待的特征,从而事先识别潜在死锁。

  建立资源分配图需要识别每个受保护的共享资源,以及指向其中某一资源的所有线程。一般可以采用这几个步骤来实现:①识别所有可能阻塞的系统调用,因为每个受保护的共享资源总是有一些与访问它有关的阻塞调用。②识别出获取共享资源的阻塞调用之后,在源代码中查找它们的各次调用情况。③对于每次调用,记录下指向资源的线程名称和该资源的名称。通常调用本身将受保护的资源作为一个参数来传递,通过这种方式可以识别出所有受保护的资源以及分配资源的线程。④建立资源分配图,并检查是否有任何资源存在循环路径。

  当然,如果预先确定每一个共享资源并建立分配图是不实际或不可能的,此时可以增加一些额外的代码,以便在系统运行时检测出潜在的死锁。例如目前有许多不同的算法都致力于优化这个检测过程,但本质上它们还是动态地建立某种资源分配图。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值