同步就是协同步调,按预约的前后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。线程同步是指多线程经过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也能够说是在线程之间经过同步创建起执行顺序的关系,若是没有同步,那线程之间是各自运行各自的!ios
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任什么时候刻最多只容许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥能够当作是一种特殊的线程同步(下文统称为同步)。编程
文末收录了一篇关于并发 并行 同步 异步 多线程的区别(这是原文地址,尊重原创)windows
临界区(Critical Section)、互斥对象(Mutex):主要用于互斥控制;都具备拥有权的控制方法,只有拥有该对象的线程才能执行任务,因此拥有,执行完任务后必定要释放该对象。安全
信号量(Semaphore)、事件对象(Event):事件对象是以通知的方式进行控制,主要用于同步控制!多线程
一、临界区:经过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只容许一个线程对共享资源进行访问,若是有多个线程试图访问公共资源,那么在有一个线程进入后,其余试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其余线程才能够抢占。它并非核心对象,不是属于操做系统维护的,而是属于进程维护的。
并发
总结下关键段:
1)关键段共初始化化、销毁、进入和离开关键区域四个函数。
2)关键段能够解决线程的互斥问题,但由于具备“线程全部权”,因此没法解决同步问题。
3)推荐关键段与旋转锁配合使用。
异步
二、互斥对象:互斥对象和临界区很像,采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。由于互斥对象只有一个,因此能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其余线程访问该资源。ide
总结下互斥量Mutex:
1)互斥量是内核对象,它与关键段都有“线程全部权”因此不能用于线程的同步。
2)互斥量可以用于多个进程之间线程互斥问题,而且能完美的解决某进程意外终止所形成的“遗弃”问题。函数
三、信号量:信号量也是内核对象。它容许多个线程在同一时刻访问同一资源,可是须要限制在同一时刻访问此资源的最大线程数目学习
在用CreateSemaphore()建立信号量时即要同时指出容许的最大资源计数和当前可用资源计数。通常是将当前可用资源计数设置为最 大资源计数,每增长一个线程对共享资源的访问,当前可用资源计数就会减1 ,只要当前可用资源计数是大于0 的,就能够发出信号量信号。可是当前可用计数减少 到0 时则说明当前占用资源的线程数已经达到了所容许的最大数目,不能在容许其余线程的进入,此时的信号量信号将没法发出。线程在处理完共享资源后,应在离 开的同时经过ReleaseSemaphore ()函数将当前可用资源计数加1 。在任什么时候候当前可用资源计数决不可能大于最大资源计数。
四、事件对象: 经过通知操做的方式来保持线程的同步,还能够方便实现对多个线程的优先级比较的操做
总结下事件Event
1)事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(全部内核对象都有),一个布尔值表示是手动置位事件仍是自动置位事件,另外一个布尔值用来表示事件有无触发。
2)事件能够由SetEvent()来触发,由ResetEvent()来设成未触发。还能够由PulseEvent()来发出一个事件脉冲。
3)事件能够解决线程间同步问题,所以也能解决互斥问题。
第一个CreateMutex
函数功能:建立互斥量(注意与事件Event的建立函数对比)
函数原型:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
函数说明:
第一个参数表示安全控制,通常直接传入NULL。
第二个参数用来肯定互斥量的初始拥有者。若是传入TRUE表示互斥量对象内部会记录建立它的线程的线程ID号并将递归计数设置为1,因为该线程ID非零,因此互斥量处于未触发状态。若是传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。
第三个参数用来设置互斥量的名称,在多个进程中的线程就是经过名称来确保它们访问的是同一个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
第二个打开互斥量
函数原型:
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名称
);
函数说明:
第一个参数表示访问权限,对互斥量通常传入MUTEX_ALL_ACCESS。详细解释能够查看MSDN文档。
第二个参数表示互斥量句柄继承性,通常传入TRUE便可。
第三个参数表示名称。某一个进程中的线程建立互斥量后,其它进程中的线程就能够经过这个函数来找到这个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
第三个触发互斥量
函数原型:
BOOL ReleaseMutex (HANDLE hMutex)
函数说明:
访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示本身已经结束访问,其它线程能够开始访问了。
最后一个清理互斥量
因为互斥量是内核对象,所以使用CloseHandle()就能够(这一点全部内核对象都同样)。
首先咱们须要建立CreateMutex一把互斥对象,咱们能够指明当前线程是否拥有它,互斥对象彻底就像一把钥匙同样,咱们用WaitForSignalObject来等待这把钥匙,可是这把钥匙被等到而且使用后必须释放-----ReleaseMutex ,否则别人永远没法等到。这样从等待到释放中间的代码段永远都是只有一个线程在执行,也就造成了互斥控制。固然互斥对象的句柄是要关闭的CloseHandle。
结合下面的程序理解简直轻松。
(对共同资源的互斥控制)
注意:虽然改程序运行结果是某一条线程执行完而后第二条线程执行,如此往复,可是这不是同步,由于咱们无法控制到底一开始是谁先执行,(咱们只是控制了轮流次序)。
第一个 CreateEvent
函数功能:建立事件
函数原型:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
函数说明:
第一个参数表示安全控制,通常直接传入NULL。
第二个参数肯定事件是手动置位仍是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。若是为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
第三个参数表示事件的初始状态,传入TRUR表示已触发。
第四个参数表示事件的名称,传入NULL表示匿名事件。
第二个 OpenEvent
函数功能:根据名称得到一个事件句柄。
函数原型:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名称
);
函数说明:
第一个参数表示访问权限,对事件通常传入EVENT_ALL_ACCESS。详细解释能够查看MSDN文档。
第二个参数表示事件句柄继承性,通常传入TRUE便可。
第三个参数表示名称,不一样进程中的各线程能够经过名称来确保它们访问同一个事件。
第三个SetEvent
函数功能:触发事件
函数原型:BOOL SetEvent(HANDLE hEvent);
函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
第四个ResetEvent
函数功能:将事件设为末触发
函数原型:BOOLResetEvent(HANDLEhEvent);
最后一个事件的清理与销毁
因为事件是内核对象,所以使用CloseHandle()就能够完成清理与销毁了。
首先咱们须要建立CreateEvent一个事件对象,它的使用方式是触发方式,要想被WaitForSingleObject等待到该事件对象必须是有信号的,事件要想有信号能够用SetEvent手动置为有信号,要想事件对象无信号可使用ResetEvent(或者在建立事件对象时就声明该事件对象WaitForSingleObject后自动置为无信号,见上面CreateEvent第二个参数),打个小小比方,手动置位事件至关于教室门,教室门一旦打开(被触发),因此有人均可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就至关于医院里拍X光的房间门,门打开后只能进入一我的,这我的进去后会将门关上,其它人不能进入除非门从新被打开(事件从新被触发)。固然事件对象的句柄是要关闭的CloseHandle。
结合下面的程序理解简直轻松。
(利用通知的方式对共同资源的互斥控制)
g_hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);的意义(顺序解释):默认安全性,自动重置事件,初始时该事件对象就有信号。执行顺序:开始的时候事件对象具备信号,当第一个线程申请得到事件对象后,进入if语句线程1会暂停1毫秒,因而第二根线程运行,由于此时g_hEvent已经无信号故没法申请并执行下面的程序,此时第一个线程睡醒开始执行本身的任务而后设置对象为有信号(能够被其余线程申请),因而第二个线程申请获得事件对象................与此往复!直到退出循环。
注意:该程序实现的不是的同步控制!可是他能够实现同步,见下面!
下面这种事件通知方式就是严格的同步方式(先让线程1执行,再.....),两个事件对象进行同步控制:
函数功能:初始化
函数原型:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:定义关键段变量后必须先初始化。
函数功能:销毁
函数原型:
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:用完以后记得销毁。
函数功能:进入关键区域
函数原型:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。
函数功能:离开关关键区域
函数原型:
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
添加代码以下
信号量Semaphore经常使用有三个函数,使用很方便。下面是这几个函数的原型和使用说明。
HANDLE CreateSemaphore(
LPSECURITY ATTRIBUTES lpSemaphoreAttributes, //安全属性
LONG lInitialCount, //信号量对象的初始值
LONG lMaximumCount, //信号量的最大值
LPCTSTR lpName //信号量名
);
参数说明:
(1)lpSemaphoreAttributes:指定安全属性,为NULL时,信号量获得一个
默认的安全描述符。
(2) lInitialCount:指定信号量对象的初始值。该值必须大于等于0,小于等于lMaximumCount。当其值大于0时,信号量被唤醒。当该函数释放了一个等待该信号量的线程时,lInitialCount值减1,当调用函数ReleaseSemaphore()时,按其指定的数量加一个值。
(3) lMaximumCount:指出该信号量的最大值,该值必须大于0。
(4) lpName:给出信号量的名字。
返回值:
信号量建立成功,将返回该信号量的句柄。若是给出的信号量名是系统已经存在的信号量,将返回这个已存在信号量的句柄。若是失败,系统返回NULL,能够调用函数GetLastError()查询失败的缘由
第二个 OpenSemaphore
函数功能:打开信号量
函数原型:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
函数说明:
第一个参数表示访问权限,对通常传入SEMAPHORE_ALL_ACCESS。详细解释能够查看MSDN文档。
第二个参数表示信号量句柄继承性,通常传入TRUE便可。
第三个参数表示名称,不一样进程中的各线程能够经过名称来确保它们访问同一个信号量。
第三个 ReleaseSemaphore
函数功能:递增信号量的当前资源计数
函数原型:
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
函数说明:
第一个参数是信号量的句柄。
第二个参数表示增长个数,必须大于0且不超过最大资源数量。
第三个参数能够用来传出先前的资源计数,设为NULL表示不须要传出。
注意:当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,若是大于0(即信号量处于触发状态),减1后返回让调用线程继续执行。一个线程能够屡次调用等待函数来减少信号量。
最后一个 信号量的清理与销毁
因为信号量是内核对象,所以使用CloseHandle()就能够完成清理与销毁了。
以一个停车场的运做为例。简单起见,假设停车场只有三个车位(共有资源),一开始三个车位都是空的。这时若是同时来了五辆车(线程),看门人(信号量)容许其中三辆(线程)直接进入,而后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车(线程)离开停车场,看门人(信号量)得知后,打开车拦,放入外面的一辆进去,若是又离开两辆,则又能够放入两辆,如此往复。
抽象的来说,信号量的特性以下:信号量是一个非负整数(车位数),全部经过它的线程/进程(车辆)都会将该整数减一(经过它使得资源被使用了1个),当该整数值为零时,全部试图经过它的线程(车辆)都将处于等待状态。在信号量上咱们定义两种操做: Wait(等待函数) 和 Release(释放函数)。当一个线程调用Wait操做时,它要么获得资源而后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。Release(释放)对应于车辆离开停车场,该操做之因此叫作“释放”是由于释放了由信号量守护的资源(车位)。
参考学习例子:
收录博文:
1. 并发:在操做系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥
2. 互斥:进程间相互排斥的使用临界资源的现象,就叫互斥。
3. 同步:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出做为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具备同步关系的一组并发进程相互发送的信息称为消息或事件。其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。
4. 并行:在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不只能够交替执行,并且能够重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具备并发的含义,但并发不必定并行,也亦是说并发事件之间不必定要同一时刻发生。
5. 多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程能够实现线程间的切换执行。
6. 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,须要等待、协调运行。异步就是彼此独立,在等待某事件的过程当中继续作本身的事,不须要等待这一事件完成后再工做。线程就是实现异步的一个方式。异步是让调用方法的主线程不须要同步等待另外一线程的完成,从而可让主线程干其它的事情。
异步和多线程并非一个同等关系,异步是最终目的,多线程只是咱们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而能够作其它的事情。实现异步能够采用多线程技术或则交给另外的进程来处理。
为了对以上概念的更好理解举一个简单例子,
假设我要作 烧开水,举杠铃100下, 洗衣服 3件事情。
烧开水 这件事情, 我要作的事情为, 准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟
举杠铃100下 我要作的事情为, 举杠铃100下 10分钟
洗衣服 我要作的事情为, 准备洗衣服 1分钟, 等开水烧开 5 分钟 , 关掉洗衣机 1分钟
单核状况下
同步的完成,我须要作的时间为 1+ 8 +1 + 10 + 1+ 5 +1 = 27 分
若是异步,就是在等的时候,我能够切换去作别的事情
准备烧开水(1) + 准备洗衣服(1) + 举50下杠铃 (5)分钟+ 关洗衣机 1分钟 + 举杠铃20下 (2)分钟+ 关烧水机 1分钟 + 举30下杠铃(3)分钟
1+1+5+1+2+1+3 =14 分钟
双核 异步 并行
核1 准备烧开水 1分钟+ 举杠铃50下(5)分钟+ 等待3分钟 + 关掉烧水机 1分钟
核2 准备洗衣服 1分钟+ 举杠铃50下(5)分钟+ 关掉洗衣机 1分钟 + 等待3分钟
其实只花了 1+5+3+1 = 10分钟
其中还有双核都等待了3分钟
双核 异步 非并行
核1 举杠铃100下(10)分钟
核2 准备烧开水 1分钟+ 准备洗衣服 1分钟+ 等待5 分钟+ + 关掉烧水机 1分钟 + 等待 1 分钟 + 关掉洗衣机 1分钟
其实只花了 1+5+3+1 = 10分钟
多线程的作法
单核下
线程1 准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟
线程2 举杠铃100下 10分钟
线程3 准备洗衣服 1分钟, 等开水烧开 5 分钟 , 关掉洗衣机 1分钟
cpu 可能这么切换 最理想的切换方式
线程1 准备烧开水1 sleep 1 sleep 5 sleep 1 sleep 2 关开水 1分钟 exit
线程2 sleep 1 sleep 1 举杠铃50 5分钟 sleep 1 举杠铃20 2分钟 sleep1 举杠铃30下 3分钟
线程3 sleep 1 准备洗衣服1 分钟 sleep 5 关洗衣机1分钟 exit
最后使用了 14分钟 和异步是同样的。
可是其实是不同的,由于线程不会按照咱们设想的去跑, 若是线程2 举杠铃先跑,整个流程的速度就下来了。
异步和同步的区别, 在io等待的时候,同步不会切走,浪费了时间。
若是都是独占cpu 的业务, 好比举杠铃的业务, 在单核状况下 多线和单线 没有区别。
多线程的好处,比较容易的实现了 异步切换的思想, 由于异步的程序很难写的。多线程自己程仍是以同步完成,可是应该说
比效率是比不上异步的。 并且多线很容易写, 相对效率也高。
多核的好处,就是能够同时作事情, 这个和单核彻底不同的。
参考资源:
【1】MoreWindows的秒杀多线程系列,博客地址,http://blog.csdn.net/morewindows?viewmode=contents
【2】孙鑫《VC++深刻详解》
【3】http://blog.csdn.net/cqkxboy168/article/details/9026205/