[多线程]windows线程小结

线程同步是一个非常大的话题,包括方方面面的内容。从大的方面讲,线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。
  内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。     

编程简便的线程进程控制手段。      

  • 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 
  • 互斥量:为协调共同对一个共享资源的单独访问而设计的。 
  • 信号量:为控制一个具有有限数量用户资源而设计。 
  • 事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。 

临界区(Critical Section) 

    保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。 
    临界区包含两个操作原语: 

  • EnterCriticalSection() 进入临界区 
  • LeaveCriticalSection() 离开临界区 

    EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。 

互斥量(Mutex) 

    互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。 
    互斥量包含的几个操作原语: 
  •     CreateMutex() 创建一个互斥量 
  •     OpenMutex() 打开一个互斥量 
  •     ReleaseMutex() 释放互斥量 
  •     WaitForSingelObjects() 等待互斥量对象 

信号量(Semaphores) 

    信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。 
    PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。 
    P操作申请资源: 
    (1S1; 
    (2)若S1后仍大于等于零,则进程继续执行; 
    (3)若S1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。 
   
    V操作 释放资源: 
    (1S1; 
    (2)若相加结果大于零,则进程继续执行; 
    (3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。 

    信号量包含的几个操作原语: 
  •     CreateSemaphore() 创建一个信号量 
  •     OpenSemaphore() 打开一个信号量 
  •     ReleaseSemaphore() 释放信号量 
  •     WaitForSingleObject() 等待信号量

事件(Event) 

    事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。 
    信号量包含的几个操作原语: 
  •     CreateEvent() 创建一个信号量 
  •     OpenEvent() 打开一个事件 
  •     SetEvent() 回置事件 
  •     WaitForSingleObject() 等待一个事件 
  •     WaitForMultipleObjects() 等待多个事件 

总结: 

    1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。 
    2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和线程退出。 

windows平台下,用于对多线程(包括进程)之间的同步保护机制,基本上有这么几种:
1Critical Section对象 2Event对象 3Mutex对象 4) Semaphore对象。 

以下我们所讨论的这些行为特征,是对并发的进/线程之间的同步保护机制的一般描述,本文用windows平台作为一个典型的例子。 基于这一些行为特征,对本文提及的这四种同步对象做一个分类。另外,在这里,我们把这四种同步对象,统统称为,以便于接下来的讨论。

第一、保护与同步。

在这里要强调的是:保护与同步是两个不同的概念。而我们经常会混合这两个概念。保护是指在多线程的环境下对共享资源的保护。这样的共享资源大多数情况下是一段内存块,它会被很多线程试图访问和修改。而同步更多的强调的是线程之间的协作,协同工作是需要同步支持的。
基于这一性质,我们可以看出: Critical Section 对象其本质更多的强调的是保护,而 Event 对象、 Mutex 对象与 Semaphore 对象更多的强调的是同步。不过,这样的区别,只是概念上的区别,其本身不会对程序本身产生影响。

第二、锁的等待超时

在开发并发的多进 / 线程程序时,为了避免死锁之类的问题,引入了 等超时 的概念,即当一个线程需要获得一个锁来执行某些代码的时候,它可以在所等待的锁上设置超时值。如果在确定的时间(超时值)内无法获得该锁,它可以选择放弃执行该段代码的权利,这样可以在一定程度上避免出现死锁的问题。这就是锁的等待超时的基本含义。基于这一行为特征,我们来对上面四种同步对象做一个划分: Critical Section 对象是无法设置等待超时的,而其他三个对象则可以设置等待超时。从这一点来讲,在使用 Critical Section 对象时,由于在等待进入关键代码段时无法设置等待超时,很容易造成死锁。

第三、线程锁与进程锁

这里所说的线程锁指的是该锁只在一个进程的所有线程中可见,而进程锁指的是该锁可以被不同的进程所访问,可用于进程间的同步与互斥。当然进程锁仍然可以被用于同一个进程的不同线程之间的同步与互斥。进程锁的概念是大于线程锁的。基于这一特点划分的话, Critical Section 对象是线程锁,而其他三个对象是进程锁。这一点从本质上来分析, Critical Section 对象是用户态模式下面实现线程同步的方法,而其他三个对象均是内核对象。内核对象机制的适应性远远优于用户方式机制。实际上,内核对象机制的唯一不足之处在于它的速度比较慢,这是因为当调用内核机制对象时,必须从用户方式转到内核方式。这样的转换需要付出很大的代价,是一件很费时的操作。在 X86 平台上 , 这样往返一次需要占用 1000 CPU 周期 ( 这并不包括执行内核方式的代码 ) 。当然需要注意的是:使用 Critical Section 对象并不意味着线程不会陷入核心态执行。当一个线程试图进入另一个线程拥有的关键代码段时 , 该线程就会进入等待状态。这意味着:该线程必须从用户态转为核心态。 ( 为了提高这一方面的性能 ,Microsoft 将循环锁的概念纳入到了 Critical Section 对象中 , 该线程可以有选择地不进入核心态等待 . 具体请参阅 MSDN)

第四、锁的递归特质

所谓递归锁指的是当一个线程拥有一个同步锁时 , 而递归地想再次取得该锁 . 如果这次获得操作不会阻塞当前线程的执行 , 则称该锁为递归锁 . 递归锁主要是在 " 保护 " 的概念上提出的 , " 保护 " 概念下的锁包括 Critical Section 对象和 Mutext  对象 . 这两种锁在 Windows 平台上都是递归锁。需要注意的是:调用线程获得几次递归锁必须释放几次递归锁。

第五、读写锁

读写锁允许高效的并发的访问多线程环境下的共享资源。对于一种共享资源 , 多个线程可以获得读锁 , 共享地读该共享资源。而在同一时刻 , 只允许一个线程拥有写锁改变该共享资源 . 这就是读写锁的概念。很遗憾的是在 Windows 平台上没有这样的读写锁 , 你需要自己去实现。
对以上总结如图:


个人认为,如果你想深入研究多线程的同步机制,ACE是一个绝佳的教材,在这里,你会看到什么是Scoped Lock, 读写锁如何实现等等。 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页