专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

WinCE线程同步

作者:佚名   来源:本站原创   点击数:  更新时间:2012年07月31日   【字体:

1、线程概述
     WinCE是有优先级的多任务操作系统,它允许重功能、进程在相同时间的系统中运行,WinCE支持最大的32位同步进程。一个进程包括一个或多个线程,每个线程代表进程的一个独立部分,而一个线程被指定为进程的基本线程。

     WinCE以抢先方式来调度线程。线程以“时间片”为单位来运行,WinCE的“时间片”通常为25毫秒。过来那个时间后,如果线程没有放弃它的时间片,并且线程并不紧急,系统就会挂起线程并调度另一个线程来运行。WinCE将根据优先级方法来决定要运行的线程,高优先级的线程将在低优先级的线程前面调度。

2、线程API函数
2.1 创建线程

     WinCE提供了CreateThread函数来创建线程,其声明如下:

     HANDLE CreateThread(
          LPSECURITY_ATTRIBUTES lpThreadAttributes,     //线程安全指针,不支持
          DWORD dwStackSize,        //为自己所使用堆栈分配的地址空间大小,不支持
          LPTHREAD_START_ROUTINE lpStartAddress, //线程函数地址
          LPVOID lpParameter, //传入线程函数的参数
          DWORD dwCreationFlags, //控制线程创建的附加标志
          LPDWORD lpThreadId//新线程的ID值
          );

     WinCE不支持lpThreadAttributes和dwStackSize参数,将它们设置成NULL和0即可。lpStartAddress指向线程函数的地址;lpParameter被传递到线程中的参数;dwCreationFlags线程创建参数,可以设置成0或CREATE_SUSPENDED,如果为0,表示线程立即执行,如果参数为CREATE_SUSPENDED,则被创建的线程将处于挂起状态,而且必须要调用ResumeThread函数将其唤醒。

 

2.2挂起和恢复线程

     正在运行的线程可以被挂起、暂停执行。同他使用SuspendThead函数即可实现以上功能,该函数的声明如下:

     DWORD SuspendThread( HANDLE hThread );

     参数hThead代表要挂起线程的句柄。由于SuspendThread函数的调用将增加挂起计数,因此在实际调度线程运行之前,对SuspendThread函数的多次调用必须与对ResumeThread函数的多次调用相匹配。ResumeThread函数的定义

     DWORD ResumeThread( HANDLE hThread );

     参数hThead同样代表要恢复线程的句柄。

3、线程同步

     在使用线程时,会经常遇到两个概念,即线程冲突和线程死锁。

     线程冲突:如果线程A读写数据G,线程B也正在读取数据G,那么很显然,该操作将导致数据冲突,引起数据混乱。这里需要使用同步技术,以保证线程A和线程B依次读写数据G,避免数据冲突。

     线程死锁:例如A工人为加工III零件在等待B提供的I零件,而B正好在等待应由A加工提供的II零件来装配I零件。由于他们之间再没有其他的任何人帮助通信或其他通信手段。所以他们一直在等对方的零件而进入死锁状态。死锁属于逻辑错误,无法通过线程同步来解决。

     WinCE实现线程同步的常用方法:事件(Event)、互斥(Mutex)、信号量(Semaphore)、临界区(CriticalSection)。

3.1 利用事件同步

     “事件对象”是实现线程同步最基本的方法之一,一个事件对象可以处于“已标示”和“未标示”两种状态,如果将事件对象设置为“已标示”状态,表示可以执行同步操作,事件对象处于“未标示”状态,则表示需要等待事件对象变为“已标示”状态才可以进行同步操作。下面介绍利用事件同步所需要的API函数。

(1)CreateEvent函数。创建事件对象函数CreateEvent,其声明如下:

      HANDLE CreateEvent(
          LPSECURITY_ATTRIBUTES lpEventAttributes,//CE不支持,设为NULL
          BOOL bManualReset, //设置是否手动设置事件对象状态
          BOOL bInitialState, //事件对象初始状态
          LPTSTR lpName //事件对象名称
          );

     参数bManualReset表示是否手动设置事件对象状态,当其值为TRUE时,在调用完等待函数(WaitForSingObject,WaitForMutipleObject)后,则必须调用ResetEvent函数,以设置事件对象没有被标示,当其值为FALSE时,系统调用完等待函数,会自动将事件对象设置为未标示状态。

     参数bInitialState表示事件对象初始状态,当其值为TRUE是,事件对象初始化状态为已标示,当其值为FALSE时,事件对象初始状态为未标示。

     如果创建事件函数对象CreateEvent执行成功,将返回事件对象句柄。若失败,则返回0,在不用事件句柄时,需要使用CloseHandle()将其关闭,以释放资源。

(2)SetEvent函数和ResetEvent函数。函数SetEvent()的功能是将事件对象设置为已标示状态。该函数的声明如下:

     BOOL SetEvent(HANDLE hEvent);

     参数hEvent表示事件对象句柄。

     函数ResetEvent函数功能将事件对象设置成未标示状态,该函数的声明如下:

     BOOL ResetEvent(HANDLE hEvent);

(3) 使用事件同步的一般使用流程

     通常情况,在主线程中,用户利用CreateEvent函数创建一个事件对象,并且将参数bManualReset设为FALSE,参数bInitialState也设为FALSE,此时事件对象状态未标示。然后在线程里通过WaitForSingleObject函数来等待事件被标示。此时,只要在主线程中调用SetEvent函数,将事件对象设置成已标示。那么线程里的WaitForSingleObject函数便会返回,继续执行,同时将事件对象状态设置成未标示。

3.2 利用互斥同步

     互斥同步类似于事件对象同步。互斥同步也将创建一个互斥对象,该互斥对象也有“被线程拥有”和“不被线程拥有”两种状态;当互斥对象处于“不被线程拥有”状态,表示可以执行相关操作;当互斥对象处于“被线程拥有”状态,表示此时不可以执行相关操作。通过等待函数请求互斥对象实现同步。

(1)CreateMutex函数。通过CreateMutex函数创建互斥对象,该函数定义如下:

     HANDLE CreateMutex(
          LPSECURITY_ATTRIBUTES lpMutexAttributes, //CE不支持
          BOOL bInitialOwner, //初始化拥有状态,TRUE表示拥有,FALSE表示未被拥有
          LPCTSTR lpName //互斥名称
          );

     如果创建互斥函数对象CreateMutex执行成功,将返回互斥对象句柄。若失败,则返回ERROR_INVALID_HANDLE,在不用互斥句柄时,需要使用CloseHandle()将其关闭,以释放资源。

(2)ReleaseMutex函数。在使用等待函数请求互斥对象时,如果请求到互斥对象的拥有权,则等待函数将自动设置互斥对象状态为“未被拥有”。ReleaseMutex函数负责释放某个线程对象互斥对象的拥有权,也就是将互斥对象设置为“未被线程拥有”状态。ReleaseMutex函数定义如下:

     BOOL ReleaseMutex( HANDLE hHandle);hHandle表示互斥对象句柄;

(3)利用互斥同步的一般使用流程

     利用互斥同步的一般使用流程是:首先利用CreateMutex函数创建互斥对象,并将CreateMutex中的参数bInitialOwer设置为FALSE,使互斥对象处于“未被线程拥有”状态。然后利用WaitForObject等待互斥对象,执行相关操作。处理完成后,利用ReleaseMutex函数释放线程对互斥对象的拥有权。当所有线程执行完毕后,需要使用CloseHandle()将其关闭。

3.3 利用临界区同步

     “临界区”是进行线程同步的另一种方法,它能够阻止两个或多个不同的线程在同一时间内访问同一个代码区域。它通过调用 EnterCriticalSection函数来指出已经进入代码的临界区,如果另一线程也调用了EnterCritialSection函数,并且参数指向同一临界区对象,那么另一线程将阻塞,直到第一个线程调用了LeaveCriticalSection函数离开临界区为止。

临界区同步所需要的API函数:

(1) InitializeCriticalSection函数。如果要使用临界区,首先要使用InitializeCriticalSection函数创建临界区,该函数定义如下:

     void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

(2) DeleteCriticalSection 函数,当结束使用临界区对象时,必须调用DeleteCriticalSection 函数释放临界区对象所占有的资源。该函数定义如下:

     void DeleteCriticalSection(   LPCRITICAL_SECTION lpCriticalSection );

(3)EnterCriticalSection函数,在创建了临界区对象后,需要调用EnterCriticalSection函数进入临界区,以保护代码,该函数定义如下:

     void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

如果临界区对象已经属于另一个线程,那么此函数将阻塞直到另一线程离开临界区才返回。

(4)LeaveCriticalSection函数。如果要离开临界区,只需要调用LeaveCriticalSection函数即可。该函数定义如下:

     void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

3.4 利用信号量同步

     信号量是建立在互斥基础上,并增加了资源计数的功能。它允许预定数目的线程同时进入要同步的代码。通过设置信号量计数为1,只允许一个线程同时访问同步代码,而实现线程同步。信号量同步所需要的API函数:

(1) CreateSemaphore函数。在使用信号量实现同步时,需要调用CreateSemaphore函数创建信号量对象。该函数定义如下:

     HANDLE CreateSemaphore(
          LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //CE不支持
          LONG lInitialCount, //信号量初始化计数值
          LONG lMaximumCount, //信号量计数最大值
          LPCTSTR lpName //信号量对象名称
          );

     如创建信号量成功,函数返回信号量对象句柄,否则返回NULL值。

(2)ReleaseSemaphore函数。在使用等待函数请求信号量时,等待函数自动给信号量计数减1,那么当计数减到0时,信号量对象将不能被请求。ReleaseSemaphore函数负责给信号量计数加值,使信号量可以被请求。此函数定义如下:

     BOOL ReleaseSemaphore(
          HANDLE hSemaphore, //信号量句柄
          LONG lReleaseCount, //信号量计数增加的值
          LPLONG lpPreviousCount //输出量,表示上一次信号量计数
          );

3.5 利用互锁函数可对变量和指针进行原子的读/写操作。因为它们不需要额外的同步对象,所以有时这些互锁函数特别有用。Windows ce提供的互锁函数有:

     InterlockedIncrement             //把一个变量的值加1
     InterlockedDecrement
     InterlockedExchange             //交换两个变量的值
     InterlockedTestExchange          //根据条件交换变量的值
     InterlockedCompareExchange       //根据比较原子交换
     InterlockedCompareExchangePointer //根据比较原子交换指针
     InterlockedExchangePointer        //交换两个指针的值
     InterlockedExchangeAdd           //给某个变量啬某个特定值
关闭窗口

相关文章