解决多线程编程中的同步互斥问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34276797/article/details/53443613
一、解决多线程编程中的同步互斥问题,
1、可以使用关键段CRITICAL_SECTIONI来解决。
2、关键段CRITICAL_SECTION一共有四个函数,分为初始化,销毁,进入关键区域、离开关键区域。(关键段一般用CS代替)
 (1)、初始化关键段函数:
        void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
        函数说明:定义关键段变量后必须先初始化
 
  (2)、 关键段销毁函数:
        void DeleteCriticalSection(LPCRITICAL lpCriticalSection);
        函数说明:关键段用完之后记得要销毁
  (3)、进入关键段区域函数:
        void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
        函数说明:系统保证各线程互斥的进入关键区域
  (4)、离开关键段区域函数:
         void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
3、关键段区域示例代码:
   #include<stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//关键段变量声明
CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
int main()
{
 printf("     经典线程同步 关键段\n");

 //关键段初始化
 InitializeCriticalSection(&g_csThreadParameter);
 InitializeCriticalSection(&g_csThreadCode);
 HANDLE handle[THREAD_NUM];//线程句柄
 g_nNum = 0;
 int i = 0;
 while (i<THREAD_NUM)
 {
  EnterCriticalSection(&g_csThreadParameter);
  //进入子线程序号关键区域
  handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
  ++i;
 }
 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
 DeleteCriticalSection(&g_csThreadCode);
 DeleteCriticalSection(&g_csThreadParameter);
 system("pause");
 return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
 
 int nThreadNum = *(int *)pPM;
 LeaveCriticalSection(&g_csThreadParameter);  //离开子线程序号关键区域
 Sleep(50);
 EnterCriticalSection(&g_csThreadCode);    //进入各子线程互斥区域
 g_nNum++;
 Sleep(0);
 printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);
 LeaveCriticalSection(&g_csThreadCode);
 return 0;
}
/*
该段程序运行两次的结果如下:
1、
经典线程同步 关键段
线程编号为2  全局资源值为1
线程编号为7  全局资源值为2
线程编号为10  全局资源值为3
线程编号为10  全局资源值为4
线程编号为10  全局资源值为5
线程编号为9  全局资源值为6
线程编号为6  全局资源值为7
线程编号为3  全局资源值为8
线程编号为9  全局资源值为9
线程编号为10  全局资源值为10
2、
经典线程同步 关键段
线程编号为10  全局资源值为1
线程编号为10  全局资源值为2
线程编号为10  全局资源值为3
线程编号为10  全局资源值为4
线程编号为8  全局资源值为5
线程编号为9  全局资源值为6
线程编号为10  全局资源值为7
线程编号为10  全局资源值为8
线程编号为7  全局资源值为9
线程编号为3  全局资源值为10
总结:使用关键段可以解决当多个线程需要访问同一段代码片段的情况,但是无法解决当多个线程同步运行的情况。
通过分析上面两次情况:
 第一种情况:在主进程中先创建了线程1但是并没有进入Fun函数,再创建线程2,此时进入了Fun函数,随后,主进程创建线程3,4,5,6都没有进入
 Fun函数,当创建线程7的时候,此时线程7进入Fun函数。所以该种方法不能解决多个线程访问同一段代码片段的情况.
 缺点:当创建好一个线程后,这个线程可以访问多次Fun函数而不只是一次。(即不能解决同步
*/
二、解决多线程问题使用事件Event
1、由于使用关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步。可以用事件Event来解决线程同步的问题。
2、事件Event实际上是个内核对象。在多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理个子线程间的互斥。
   (1)、创建事件函数
       HANDLE CreateEvent(LPSECRITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
       第一个参数表示安全控制,一般直接传入NULL。
       第二个参数确定事件是手动置位还是自动置位。如果为自动置位,则对该事件调用
       WaitForSingleObject()后会自动使事件变成未触发状态。
       手动置位相当于教室门,教室门一旦打开(被触发),所有人都可以进入直到老师去关上教室门(事件变成未触发).
       自动置位事件就相当于医院拍X光的房间门,门打开后只能进入一个人,这个人进去后就把门关上,其他人不能进入除非门重新被打开(事件重新被触发)
       总结:手动置位相当于人进房间后,可能不关门,这是其他人都可以进入房间,直到门被关上。自动置位相当于一个人进去房间之后马上就把门关上了,只有它一个人在房间里面,其他人想要进房间只有等房间里面的人出来,把门打开后才能进去。
       第三个参数表示事件的初始状态,传入TRUE表示已触发。
       第四个参数表示事件的名称,传入NULL表示匿名事件。
   (2)、打开事件函数:
       函数功能:根据名称获得一个事件句柄。
       函数原型:
       HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
       第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。
       第二个参数表示事件句柄继承性,一般传入TRUE。
       第三个参数表示名称,不同进程中的各线程可以通过名称来确保他们访问同一个事件。
   (3)、触发事件函数:
       函数原型:BOOL SetEvent(HANDLE hEvent);
       函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
   (4)、将事件设置为未触发函数
       函数原型:BOOL ResetEvent(HANDLE hEvent);  
#include<stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//事件与关键段
HANDLE g_hThreadEvent;
CRITICAL_SECTION  g_csThreadCode;
int main()
{
 printf("     经典线程同步 事件Event\n");

 //初始化事件和关键段 自动置位,初始化无触发的匿名事件
 g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 InitializeCriticalSection(&g_csThreadCode);
 HANDLE handle[THREAD_NUM];
 g_nNum = 0;
 int i = 0;
  while (i < THREAD_NUM)
 {
  //进入子线程序号关键区域
     handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
  WaitForSingleObject(g_hThreadEvent,INFINITE);
  ++i;
 }
 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
 CloseHandle(g_hThreadEvent);
 DeleteCriticalSection(&g_csThreadCode);
 
 system("pause");
 return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
 int nThreadNum = *(int *)pPM;
 SetEvent(g_hThreadEvent);  //触发事件
 Sleep(50);
 EnterCriticalSection(&g_csThreadCode);    //进入各子线程互斥区域
 g_nNum++;
 Sleep(0);
 printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);
 LeaveCriticalSection(&g_csThreadCode);
 return 0;
}
/*
以下是我执行三次的结果:
1、
经典线程同步 事件Event
线程编号为9  全局资源值为1
线程编号为7  全局资源值为2
线程编号为8  全局资源值为3
线程编号为6  全局资源值为4
线程编号为5  全局资源值为5
线程编号为3  全局资源值为6
线程编号为2  全局资源值为7
线程编号为4  全局资源值为8
线程编号为1  全局资源值为9
线程编号为0  全局资源值为10
2、
经典线程同步 事件Event
线程编号为9  全局资源值为1
线程编号为7  全局资源值为2
线程编号为8  全局资源值为3
线程编号为6  全局资源值为4
线程编号为5  全局资源值为5
线程编号为3  全局资源值为6
线程编号为2  全局资源值为7
线程编号为4  全局资源值为8
线程编号为1  全局资源值为9
线程编号为0  全局资源值为10
3、
经典线程同步 事件Event
线程编号为9  全局资源值为1
线程编号为7  全局资源值为2
线程编号为8  全局资源值为3
线程编号为6  全局资源值为4
线程编号为5  全局资源值为5
线程编号为4  全局资源值为6
线程编号为3  全局资源值为7
线程编号为2  全局资源值为8
线程编号为1  全局资源值为9
线程编号为0  全局资源值为10
总结:从这三次结果中发现,每个线程他都只访问了Fun一次,但是每个线程访问的顺序不是固定的
这说明事件很好的解决了多个线程访问同一段代码片段会出现重复访问的问题。
*/
3、解决多线程问题使用信号量Semaphore
   (1)、信号量Semaphore常用的三个函数:
    CreateSemaphore:创建信号量
    OpenSemaphore:打开信号量
    ReleaseSemaphore:释放信号量
   (2)、当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽,资源已经耗尽,故信号量处于未触发。
   (3)、在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,如果大于0(即信号量处于触发状态),减1后返回让调用线程继续执行。
   (4)、一个线程可以多次调用等待函数来减少信号量。
创建信号量函数:
函数原型:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount,LpCTSTR lpName);
参数说明:
第一个参数表示安全控制,一般直接传入NULL。
第二个参数表示初始资源数量。
第三个参数表示最大并发数量。
第四个参数表示信号量的名称,传入NULL表示匿名信号量。

打开信号量函数:
函数原型:
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
参数说明:
第一个参数表示访问权限,一般传入SEMAPHORE_ALL_ACCESS。
第二个参数表示信号量句柄继承性,一般传入TRUE。
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。

ReleaseSemaphore
函数功能:递增信号量的当前资源计数
函数原型:
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
参数说明:
第一个参数是信号量的句柄。
第二个参数表示增加个数,必须大于0且不超过最大资源数量。
第三个参数可以用来传出先前的资源计数,设为NULL表示不需要传出

#include<stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//信号量与关键段
HANDLE g_hThreadParameter;
CRITICAL_SECTION  g_csThreadCode;
int main()
{
 printf("     经典线程同步 关键段\n");

 //初始化信号量与关键段
 g_hThreadParameter = CreateSemaphore(NULL, 0, 1, NULL);
 InitializeCriticalSection(&g_csThreadCode);
 HANDLE handle[THREAD_NUM];
 g_nNum = 0;
 int i = 0;
 while (i<THREAD_NUM)
 {
  //进入子线程序号关键区域
  handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
  WaitForSingleObject(g_hThreadParameter, INFINITE);  // 等待信号量大于1,如果档期那信号量大于1,会将信号量-1,其实这就是操作系统中所学的二元信号量
  ++i;
 }
 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
 //销毁信号量与关键段
 DeleteCriticalSection(&g_csThreadCode);
 CloseHandle(g_hThreadParameter);
 for (i = 0; i < THREAD_NUM;i++)
 {
  CloseHandle(handle[i]);
 }
 DeleteCriticalSection(&g_csThreadCode);
 system("pause");
 return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
 int nThreadNum = *(int *)pPM;
 ReleaseSemaphore(g_hThreadParameter, 1, NULL); // 信号量++
 Sleep(50);
 EnterCriticalSection(&g_csThreadCode);    //进入各子线程互斥区域
 g_nNum++;
 Sleep(0);
 printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);
 LeaveCriticalSection(&g_csThreadCode);
 return 0;
}
/*
执行效果与事件处理的类似,不过是采用信号量来解决的.
信号量:当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽,故信号量处于未出发
通过打断点发现编译器是先产生了所有的线程,产生完后,向后面执行的,我觉的这个地方可能是采用了队列,从而解决多个线程同步的问题
在等待单对象函数时,它一直处于监听状态,当发现信号量大于1了,他就会立即执行。
*/
展开阅读全文

没有更多推荐了,返回首页