C++线程同步(互斥&协同)

参考博客:http://www.cnblogs.com/kennyMc/archive/2012/12/15/2818887.html   互斥量进行线程同步,与关键段和事件的区别

参考博客:http://www.cnblogs.com/xilentz/archive/2012/11/13/2767317.html       四种进程或线程同步互斥的控制方法

线程同步解决多线程并发问题;

几种内核对象中,除了互斥量,没有任何一个会记住自己是哪个线程等待成功的。

 

互斥量和关键段的比较:

(1)关键段只适用于同一个进程,互斥量可适用与不同的进程

(2)关键段的效率更高

(3)关键段不安全,而互斥量更安全;

 四种进程或线程同步互斥的控制方法

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

 

/************** 线程同步--互斥方式******************/

1、原子访问---变量为Volatile关键字修饰,避免编译器优化,始终从变量在内存中的位置读取变量的值。

原子访问:指线程在访问某一个资源的同时保证没有其他线程会在同一时刻访问同一资源。

即:需要保证递增或者递减操作是原子操作(不可被中断的一个或一系列操作),不会被打断.

    处理器自动保证基本的内存操作的原子性---当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。复杂的内存操作处理器不能自动保证其原子性,处理器提供总线锁定缓存锁定两个机制保证内存操作的原子性.

X86系列CPU,Interlocked 函数会在总线上维持一个硬件信号LOCK#,这个信号会组织其他CPU访问同一个内存地址; 所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效,

Interlocked 函数执行得极快,不需要在用户模式和内核模式之间进行切换,(这个切换需要1000个周期以上)

LONG  m_nTicket;

InterlockedDecrement(&pthis->m_nTicket);    //pthis->m_nTicket--;

InterlockedIncrement(&pthis->m_nTicket);    //pthis->m_nTicket++;

InterlockedExchangeAdd(PLONG volatile plAddend, LONG LIncrement);//*plAddend+=LIncrement

InterlockedExchangeInterlockedExchangePointer会吧第一个参数所执行的内存地址的当前值,以原子访问方式替换为第二个参数指定的值。在实现旋转锁的时候InterlockedExchange及其有用。

2、关建段--是个数据结构,将自旋锁合并到关键段中,用户模式->内核模式耗费cpu(1000)

    2.1、使用CRITICAL_SECTION两个必要条件①所有想要访问资源的的线程必须直到用来保护资源的CRITICAL_SECTION结构的地址;②任何线程视图访问被保护的资源之前,必须对CRITICAL SECTION结构的内部成员进行初始化

      2.2、EnterCriticalSection会检查结构中的成员变量,这些参变量表示是否有线程正在访问资源,以及哪个线程正在访问资源。总的来说会执行以下测试。

①如果没有线程正在访问资源,那么EnterCriticalSection 会更新成员变量,以表示调用线程已经获准对资源的访问,并立即返回。线程继续执行。

②如果表示调用线程已经获准访问资源,那么EnterCriticalSection会更新变量,以表示调用该线程被获准访问的次数,并立即返回。只有当调用LeaveCriticalSection之前连续调用EnterCriticalSection才会发生。

如果成员变量表示有另一个线程已经或得访问资源,那么EnterCriticaSection会使用一个时间内核时间把调用状态切换成等待状态。

(1)定义关建段

CRITICAL_SECTION  m_cs;    //会分配内存,如果分配内存失败,初始化时,会出错。

(2)关键段初始化

InitializeCriticalSection(&m_cs);

(3)进入关键段     //

EnterCriticalSection(&m_cs);  (同步,等待)//异步处理,TryEnterCriticalSection(&m_cs);

(4)离开关键段

LeaveCriticalSection(&m_cs);

LeaveCriticalSection会检查结构内部的成员变量并将计数器-1,该计数器用来表示调用线程获准访问共享资源的次数。如果计数器>0则直接返回,如果计数器变成了0,LeaveCriticalSection会更新成员变量,以表示没有任何线程正在访问被保护的资源。

(5)结束时释放关键段;

DeleteCriticalSection(&m_cs);

//DeleteCriticalSection会重置结构体的成员变量,若继续使用Platform SDK说明会导致不可预料的结果,此时才会释放事件内核对象。

 

3、互斥量(跨进程加锁)是一个内核对象;

互斥量内核对象用来确保一个线程独占一个资源的访问

互斥量对象包含了一个使用计数线程ID以及一个递归计数。互斥量与关键段的行为完全相同,但是互斥量是内核对象,而关键段是用户模式下的同步对象,因此互斥量会比关键段运行的慢。

互斥量的规则:

1.如果线程ID为0(无效线程ID),那么互斥量不为任何线程所占用,它处于触发状态。

2.如果线程ID为非零值,那么有一个线程已经占用了该互斥量,它处于非触发状态。

3.与其他的内核对象有所不同。

(1)定义互斥量句柄

HANDLE  m_hMutex;

(2)创建互斥量

 m_hMutex = CreateMutex(NULL, //安全属性

   FALSE, //同等竞争信号量,TRUE:初始就拥有信号;

    NULL); //名字,跨进程时,需要通过名字打开;

(3)等待互斥量

WaitForSingleObject(pthis->m_hMutex,INFINITE);

(4)释放互斥量的拥有权

ReleaseMutex(pthis->m_hMutex);

/************** 线程同步--协同方式******************/

4、事件;(内核对象); 没有个数限制则用事件;点击打开链接

在所有的内核对象中,事件内核对象是个最基本的对象。事件能够通知一个操作已经完成。

事件内核对象的组成

一个使用计数(与所有内核对象一样),

一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,

一个用于指明该事件有没有被触发的布尔值。

有两种不同类型的事件对象

一种人工重置的事件另一种自动重置的事件

当人工重置的事件得到通知时等待该事件的所有线程均变为可调度线程。

当自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。这第二个线程知道第一个线程已经完成了它的操作。

 用CreateEvent函数创建事件内核对象

HANDLE CreateEvent(

PSECURITY_ATTRIBUTES psa, //如何继承他们的句柄

BOOL fManualReset,//创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE)

BOOL fInitialState,//初始化为已通知状态(TRUE)还是未通知状态(FALSE)

PCTSTR pszName

);

return:createEvent就将与进程相关的句柄返回给事件对象。

其他进程中的线程可以获得对该对象的访问权,方法是使用在pszName参数中传递的相同值(见:命名对象),使用继承性使用DuplicateHandle函数等来调用CreateEvent,或者调用OpenEvent ,在pszName参数中设定一个与调用CreateEvent时设定的名字相匹配的名字:

HANDLE OpenEvent(

    DWORD fdwAccess, //获得指定权限,

  BOOL fInherit,

  PCTSTR pszName  //待打开事件名字。

);

与所有情况中一样,当不再需要事件内核对象时,应该调用CloseHandle函数。

一旦事件已经创建,就可以直接控制它的状态

当调用SetEvent时,可以将事件改为已通知状态

BOOL SetEvent(HANDLE hEvent);

当调用ResetEvent函数时,可以将该事件改为未通知状态

BOOL ResetEvent(HANDLE hEvent);

事件成功等待的副作用

自动重置的事件自动重置到未通知状态(所以通常没有必要为自动重置的事件调用ResetEvent函数)。所以他的名字才有自动两字。

人工重置的事件: Microsoft没有人工重置的事件定义成功等待的副作用。所以它才叫人工。

5、信号量;(内核对象);有个数限制使用信号量;点击打开链接

信号量内核对象用来对资源进行计数。它包含了:①一个使用计数即线程使用信号量内核对象的个数;②一个最大资源数即信号量可以控制的最大资源数量;③当前资源计数表示信号量当前可用资源的数量。

信号量的规则:

①如果当前资源计数大于0,那么信号量处于触发状态;

②如果当前资源计数等于0,那么信号量处于未触发状态;

③当前资源计数不会小于0;

④当前资源计数绝对不会大于最大资源计数;

(1)定义信号量句柄;

HANDLE  m_hSemphore;

(2)创建信号量;

m_hSemphore=CreateSemaphore(NULL, //安全属性;

     0,    //初始化信号量的个数;

                             3,    //信号量最大的个数;

                            NULL  //信号量名称;);

//打开一个已经存在的信号量对象

HANDLE OpenSemphore(

DWORD dwDesiredAccess,  //指定访问权限

BOOL bInheritHandle,  //  

PCTSTR pszName);   //要打开信号量名称

(3)释放信号量;

RealeaseSemaphore(m_hSemphore,//信号量句柄

2, //释放的信号量个数;

NULL); //用来接收之前释放过多少个信号量;

(4)等待信号量

WaitForSingleObject(m_hSemphore,INFINITE);   

如果等待函数发现信号量的当前资源计数为0(信号量处于未触发状态),那么系统会让调用线程进入等待状态。当另一线程将信号量的当前资源计数递增时(触发状态),系统会使等待状态的线程变成可调度状态。

信号量的最大优势在于它们会以原子方式来执行,当我们向信号量请求一个资源的时候,操作系统会检查资源是否可用,并将可用资源的数量递减,整个过程不会被其他线程打断,只有当资源计数递减完成之后,系统才会允许另一个线程请求对资源的访问。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值