windows多线程同步

概述


  任何单个应用程序都不能完全使该处理器达到满负荷。当一个线程遇到较长等待时间事件时,同步多线程还允许另一线程中的指令使用所有执行单元。例如,当一个线程发生高速缓存不命中,另一个线程可以继续执行。同步多线程是
POWER5? 和 POWER6? 处理器的功能,可与共享处理器配合使用。

  SMT 对于商业事务处理负载的性能优化可达30%。在更加注重系统的整体吞吐量而非单独线程的吞吐量时,SMT 是一个很好地选择。

  但是并非所有的应用都能通过SMT
取得性能优化。那些性能受到执行单元限制的应用,或者那些耗尽所有处理器的内存带宽的应用,其性能都不会通过在同一个处理器上执行两个线程而得到提高。

  尽管SMT 可以使系统识别到双倍于物理CPU数量的逻辑CPU(lcpu),但是这并不意味着系统拥有了两倍的CPU能力。

  SMT技术允许内核在同一时间运行两个不同的进程,以此来压缩多任务处理时所需要的总时间。这么做有两个好处,其一是提高处理器的计算性能,减少用户得到结果所需的时间;其二就是更好的能效表现,利用更短的时间来完成任务,这就意味着在剩下的时间里节约更多的电能消耗。当然这么做有一个总前提——保证SMT不会重复HT所犯的错误,而提供这个担保的则是在酷睿微架构中表现非常出色的分支预测设计。[1]

编辑本段同步多线程的同步机制

1、 Event

  用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为CEvent.。CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。共有三个函数来改变事件的状态:SetEvent,ResetEvent和PulseEvent。用事件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重置事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。

  多线程同步-event

  在所有的内核对象中,事件内核对象是个最基本的。它包含一个使用计数(与所有内核对象一样),一个BOOL值(用于指明该事件是个自动重置的事件还是一个人工重置的事件),还有一个BOOL值(用于指明该事件处于已通知状态还是未通知状态)。事件能够通知一个线程的操作已经完成。有两种类型的事件对象。一种是人工重置事件,另一种是自动重置事件。他们不同的地方在于:当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

  当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最频繁。在这种情况下,事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态,而一直在等待该事件的另一个线程在事件已经被通知后,就变成可调度线程。

  当这个进程启动时,它创建一个人工重置的未通知状态的事件,并且将句柄保存在一个全局变量中。这使得该进程中的其他线程能够非常容易地访问同一个事件对象。程序一开始创建了三个线程,这些线程在初始化后就被挂起,等待事件。这些线程要等待文件的内容读入内存,然后每个线程都会访问这段文件内容。一个线程进行单词计数,另一个线程运行拼写检查,第三个线程运行语法检查。这3个线程函数的代码的开始部分都相同,每个函数都调用WaitForSingleObject.,这将使线程暂停运行,直到文件的内容由主线程读入内存为止。一旦主线程将数据准备好,它就调用SetEvent,给事件发出通知信号。这时,系统就使所有这3个辅助线程进入可调度状态,它们都获得了C P
U时间,并且可以访问内存块。这3个线程都必须以只读方式访问内存,否则会出现内存错误。这就是所有3个线程能够同时运行的唯一原因。如果计算机上配有三个以上CPU,理论上这个3个线程能够真正地同时运行,从而可以在很短的时间内完成大量的操作

  如果你使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大的差别。当主线程调用S e t E v e n
t之后,系统只允许一个辅助线程变成可调度状态。同样,也无法保证系统将使哪个线程变为可调度状态。其余两个辅助线程将继续等待。已经变为可调度状态的线程拥有对内存块的独占访问权。

  让我们重新编写线程的函数,使得每个函数在返回前调用S e t E v e n t函数(就像Wi n M a i n函数所做的那样)。

  当主线程将文件内容读入内存后,它就调用SetEvent函数,这样操作系统就会使这三个在等待的线程中的一个成为可调度线程。我们不知道系统将首先选择哪个线程作为可调度线程。当该线程完成操作时,它也将调用S
e t E v e n t函数,使下一个被调度。这样,三个线程会以先后顺序执行,至于什么顺序,那是操作系统决定的。所以,就算每个辅助线程均以读/写方式访问内存块,也不会产生任何问题,这些线程将不再被要求将数据视为只读数据。

  这个例子清楚地展示出使用人工重置事件与自动重置事件之间的差别。

  P u l s e E v e n t函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用S e t E v e n t后又立即调用R
e s e t E v e n t函数一样。如果在人工重置的事件上调用P u l s e E v e n
t函数,那么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用P u l s e E v e n
t函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将不起任何作用[2]

2、 Critical Section

  使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical
Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。Critical
Section在MFC中的相应实现类是CcriticalSection。CcriticalSection::Lock()进入临界区,CcriticalSection::UnLock()离开临界区。

只能同步同一个进程的线程之间的同步,因为临界区不能跨越进程的边界工作。也是因为临界区没有name,所以不能跨进程使用。访问临界区之前进行锁定,访问后进行解锁。如果线程B访问线程A锁定的临界区,那么线程B会被阻塞,直到线程A释放临界区,线程B才可以运行。在线程B进行阻塞期间,不占用CPU时间.

3、 Mutex

  互斥量与临界区的区别,如果一个线程锁定了临界区,而终止时没有解除临界区的锁定,那么等待临界区空闲的其他线程将无限期的阻塞下去。然而,如果锁定互斥量的线程不能在其终止前解除互斥量的锁定,那么系统将认为互斥量被“放弃”了并自动释放该互斥量,这样等待进程就可以继续执行了。

互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比Critical
Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定TIMEOUT,不会像Critical
Section那样无法得知临界区域的情况,而一直死等。MFC中的对应类为CMutex。Win32函数有:创建互斥体CreateMutex()
,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入Critical
Section一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。

4、 Semaphore

信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。对应的MFC类是Csemaphore。Win32函数CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当然不会超过初始设定的资源总数。

编辑本段有关多线程的一些技术问题

  1、 何时使用多线程?

  2、 线程如何同步?

  3、 线程之间如何通讯?

  4、 进程之间如何通讯?

  先来回答第一个问题,线程实际主要应用于四个主要领域,当然各个领域之间不是绝对孤立的,他们有可能是重叠的,但是每个程序应该都可以归于某个领域:

  1、 offloading time-consuming
task。由辅助线程来执行耗时计算,而使GUI有更好的反应。我想这应该是我们考虑使用线程最多的一种情况吧。

  2、 Scalability。服务器软件最常考虑的问题,在程序中产生多个线程,每个线程做一份小的工作,使每个CPU都忙碌,使CPU(一般是多个)有最佳的使用率,达到负载的均衡,这比较复杂,我想以后再讨论这个问题。

  3、 Fair-share resource
allocation。当你向一个负荷沉重的服务器发出请求,多少时间才能获得服务。一个服务器不能同时为太多的请求服务,必须有一个请求的最大个数,而且有时候对某些请求要优先处理,这是线程优先级干的活了。

  4、 Simulations。线程用于仿真测试。

编辑本段线程之间的通讯

  线程常常要将数据传递给另外一个线程。Worker线程可能需要告诉别人说它的工作完成了,GUI线程则可能需要交给Worker线程一件新的工作。

  通过PostThreadMessage(),可以将消息传递给目标线程,当然目标线程必须有消息队列。以消息当作通讯方式,比起标准技术如使用全局变量等,有很大的好处。如果对象是同一进程中的线程,可以发送自定义消息,传递数据给目标线程,如果是线程在不同的进程中,就涉及进程之间的通讯了。下面将会讲到。

  进程之间的通讯:

  当线程分属于不同进程,也就是分驻在不同的地址空间时,它们之间的通讯需要跨越地址空间的边界,便得采取一些与同一进程中不同线程间通讯不同的方法。

  1、
Windows专门定义了一个消息:WM_COPYDATA,用来在线程之间搬移数据,――不管两个线程是否同属于一个进程。同时接受这个消息的线程必须有一个窗口,即必须是UI线程。WM_COPYDATA必须由SendMessage()来发送,不能由PostMessage()等来发送,这是由待发送数据缓冲区的生命期决定的,出于安全的需要。

  2、 WM_COPYDATA效率上面不是太高,如果要求高效率,可以考虑使用共享内存(Shared
Memory)。使用共享内存要做的是:设定一块内存共享区域;使用共享内存;同步处理共享内存。

  第一步:设定一块内存共享区域。首先,CreateFileMapping()产生一个file-mapping核心对象,并指定共享区域的大小。MapViewOfFile()获得一个指针指向可用的内存。如果是C/S模式,由Server端来产生file-mapping,那么Client端使用OpenFileMapping(),然后调用MapViewOfFile()。

  第二步:使用共享内存。共享内存指针的使用是一件比较麻烦的事,我们需要借助_based属性,允许指针被定义为从某一点开始起算的32位偏移值。

  第三步:清理。UnmapViewOfFile()交出由MapViewOfFile()获得的指针,CloseHandle()交出file-mapping核心对象的handle。

  第四步:同步处理。可以借助Mutex来进行同步处理。

  3、 IPC

  1)Anonymous Pipes。Anonymous Pipes只被使用于点对点通讯。当一个进程产生另一个进程时,这是最有用的一种通讯方式。

  2)Named Pipes。Named Pipes可以是单向,也可以是双向,并且可以跨越网络,步局限于单机。

  3)Mailslots。Mailslots为广播式通讯。Server进程可以产生Mailslots,任何Client进程可以写数据进去,但是只有Server进程可以取数据。

  4)OLE Automation。OLE Automation和UDP都是更高阶的机制,允许通讯发生于不同进程间,甚至不同机器间。

  5)DDE。DDE动态数据交换,使用于16位Windows,目前这一方式应尽量避免使用。[3]

内容来自网络资源:http://baike.baidu.com/view/2808915.htm

windows多线程同步,布布扣,bubuko.com

时间: 2024-10-15 09:19:11

windows多线程同步的相关文章

windows多线程同步总结

1.多线程同步与多线程互斥的关系 其实这也是我一直困扰的问题,在这里我只是说说我的理解. 我的理解是多线程互斥是针对于多线程资源而言的. 而多线程同步是针对于多线程时序问题. 由于线程的并发性导致其运行时间的不确定性,所以我们需要 控制多个线程的协同工作. 还是举卖票的例子来区别互斥和同步的区别吧: 比如我们有两个窗口进行卖票,当然这里就需要一个全局变量 来计数当前卖了第几张票了,最重要的是一张票不能卖出去两次, 所以当窗口1在卖第N张票的时候,窗口2应该就不能卖第N张票, 否则当不进行互斥的时

@windows 多线程同步技术

使线程同步 临界区 管理事件内核对象  信号量内核对象 互斥内核对象  小结 正文 使线程同步 在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作.更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解.正常情况下对这种处理结果的了解应当在其处理任务完成后进行. 如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解.例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题.如果一个

windows平台多线程同步实现之Mutex对象的使用

windows平台多线程同步实现之MutexMutex对象的使用 前言 线程组成: 线程的内核对象,操作系统用来管理该线程的数据结构. 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量. ??操作系统为每一个运行线程安排一定的CPU时间 -- 时间片.系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,多个线程不断地切换运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样. ??单cpu计算机一个时间只能运行一个线程,如果计算机拥有多个CPU,线程就能真正

一个经典的多线程同步问题

上一篇<秒杀多线程第三篇原子操作 Interlocked系列函数>中介绍了原子操作在多进程中的作用,现在来个复杂点的.这个问题涉及到线程的同步和互斥,是一道非常有代表性的多线程同步问题,如果能将这个问题搞清楚,那么对多线程同步也就打下了良好的基础. 程序描述: 主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程.子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量. 要求: 1.子线程输出的线

秒杀多线程第四篇 一个经典的多线程同步问题

上一篇<秒杀多线程第三篇原子操作 Interlocked系列函数>中介绍了原子操作在多进程中的作用,如今来个复杂点的.这个问题涉及到线程的同步和相互排斥,是一道很有代表性的多线程同步问题,假设能将这个问题搞清楚,那么对多线程同步也就打下了良好的基础. 程序描写叙述: 主线程启动10个子线程并将表示子线程序号的变量地址作为參数传递给子线程.子线程接收參数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出參数和全局变量. 要求: 1.子线程输

Windows多线程编程总结

Windows 多线程编程总结 keyword:多线程 线程同步 线程池 内核对象 1 内核对象 1 .1 内核对象的概念 内核对象是内核分配的一个内存块,这样的内存块是一个数据结构,表示内核对象的各种特征.而且仅仅能由内核来訪问.应用程序若须要訪问内核对象,须要通过操作系统提供的函数来进行,不能直接訪问内核对象( Windows 从安全性方面来考虑的). 内核对象通过 Create* 来创建,返回一个用于标识内核对象的句柄,这些句柄 (而不是内核对象)可在创建进程范围内使用,不可以被传递到其它

多线程同步精要

单机并发编程有两种基本模型:"消息传递"和"共享内存":分布式系统运行在多台机器上,只有一种实用模型:"消息传递". 单机上多进程并发可以照搬"消息传递",多线程编程用"消息传递"更容易保证程序的正确性. 多线程同步有很多种方式:互斥量.条件变量.信号量.读写锁等.尽量不要用信号量和读写锁 Don't use a semaphore where a mutex would suffice. A semaph

多线程同步之互斥对象

多线程同步之互斥对象 作者:vpoet mail:[email protected] 在http://blog.csdn.net/u013018721/article/details/46637215一文中介绍了使用临界区 对卖票问题进行线程间同步,本文将在上文的基础上,使用互斥对象对线程进行同步. 首先看看windows API 该函数创建一个命名或者不命名的互斥对象 lpMutexAttributes [in] Pointer to a SECURITY_ATTRIBUTES structu

多进程间通信方式和多线程同步机制总结

多进程之间通信方式: 文件映射:本地之间 共享内存:本地之间 匿名管道:本地之间 命名管道:跨服务器 邮件槽:一对多的传输数据,通常通过网络向一台Windows机器传输 剪切板:本地之间 socket:跨服务器 多线程之间通信方式: 全局变量 自定义消息响应 多线程之间同步机制:           临界区:不可以跨进程,忘记解锁会无限等待,要么存在要么没有,多线程访问独占性共享资源 互斥量:可以跨进程,忘记解锁会自动释放,要么存在要么没有 事件:又叫线程触发器,不可以跨进程,要么存在要么没有,