解决线程同步互斥问题(原子访问、关键段、互斥量、事件、信号量)

时间:2022-11-18 14:43:44

        线程同步,同步的意思并不是一同、一起做某些事,同的意思应该是协同,互相配合。也就是说要有一定的调理或者规矩的执行,比如:我先干完,你在干。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。——来自百度百科

   下面就是解决问题的方法

1.原子访问

因为CPU运算时从寄存器中读取数据比从内存中读取数据快得多,所以代码在编译阶段编译器往往会对代码进行优化,使CPU读取你代码中的变量时,从寄存器中读取而不是内存中,从而提高运行速度。但是在多线程中就会出现问题,比如:当有两个线程A、B都要用寄存器的变量,并且它们要对这个变量进行修改,这就有可能导致线程A在还没有对变量修改完成,线程B就又拿到这个变量,所以B拿到的更新之前的变量,就会导致整个运算结果出现问题。

原子访问,用关键字volatile声明变量,volatile可以防止编译优化,使每次的读取操作都从内存中读取,就可以大幅的减少内存一致性错误,让B线程可以看到A线程对变量操作后的结果。

volatile int value;

2.关键段(临界区)

例如:同样有两个线程A、B都执行同一段代码,value的初始值为1

value--;
cout << value;

那么线程A和线程B输出的结果可能是一样的也可能是不同的,如果线程A刚拿到value的值还没有执行value--操作,时间片就到了,这时线程B开始执行,输出结果为0,等线程A重新获得时间片继续执行时,直接执行value--,输出的结果也是0,这并不是我们想得到的结果,我们往往要这个变量每次执行这段代码都-1,我们就可以要使用关键段来控制线程执行这段代码。

关键段的声明

CRITICAL_SECTION m_cs;
关键段的初始化,声明了一个关键段就必须对它进行初始化
InitializeCriticalSection(&m_cs);
进入关键段和离开关键段
EnterCriticalSection(&m_cs); //进入关键段
value--;
cout << value;
LeaveCriticalSection(&m_cs); //离开关键段

这样只要有线程进入了关键段,其他的线程就必须在关键段外等待,直到关键段中的线程离开。但是如果关键段中代码需要执行很长时间,其他的线程就一直在关键段外被阻塞,就会降低整个程序的执行效率,这时我们可以使用旋转锁或者TryEnterCriticalSection函数来解决这个问题。

3.互斥量

互斥量也可以做到和关键段同样的效果

互斥量是内核对象,可以跨进程使用。

(1).声明

HANDLE m_Mutex;

(2).初始化

函数原型

CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全描述符,如果为空则为用默认的安全描述符
    _In_ BOOL bInitialOwner, //如果此值为TRUE并且调用者创建了互斥锁,则调用线程获得互斥锁对象的初始所有权。否则,调用线程不会获得互斥锁的所有权
    _In_opt_ LPCWSTR lpName  //跨进程时的进程名
    );
m_Mutex = CreateMutex(NULL, FALSE, NULL);
(3).使用WaitForSingleObject来等待互斥量的状态
if (WAIT_TIMEOUT == WaitForSingleObject(m_Mutex, 100))
    continue;

意为等待这个互斥量100毫秒,如果这个互斥量还有没变为有信号状态则返回。这样就可以控制只有一个线程进入下面的代码,知道这个线程释放互斥量。

(4).释放互斥量

ReleaseMutex(m_Mutex);

4.事件

和互斥量的用法差不多 声明、初始化、等待、释放

HANDLE m_Event;
m_Event = CreateEvent(NULL, FALSE, TRUE, NULL);
if (WAIT_TIMEOUT == WaitForSingleObject(m_Event, 100))
	continue;
SetEvent(m_Event); //设置事件为有信号

5.信号量

信号量、互斥量、事件都是内核对象可以跨进程使用,但信号量的初始化和释放时稍有不同,它可以控制进入关键区域的线程个数。

HANDLE m_hSemaphore;
m_hSemaphore = CreateSemaphore(NULL, 1, 10, NULL);
if (WAIT_TIMEOUT == WaitForSingleObject(m_hSemaphore, 100))
	continue;
ReleaseSemaphore(m_hSemaphore, 1, NULL);

CreateSemaphore中第三个参数是创建信号的最大数。第二个参数是创建后就释放的信号量数量。

ReleaseSemaphore中第二个参数是要释放的信号量数量。第三个参数是返回释放前释放的信号量数目,NULL则不接受。