多线程相关问题:临界区、事件、互斥量Mutex(秒杀多线程总结)

程序描述: 多线程

主线程启动10个子线程并将表示子线程序号的变量地址做为参数传递给子线程。子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量。 函数

要求: spa

1.子线程输出的线程序号不能重复。 线程

2.全局变量的输出必须递增。 指针


//经典线程同步互斥问题
#include <stdio.h>
#include <process.h>
#include <Windows.h>

long g_nNum;//全局资源
unsigned int __stdcall Fun(void *pPM);//线程函数
const int THREAD_NUM = 10;//子线程个数

int main()
{
	g_nNum = 0;
	HANDLE handle[THREAD_NUM];

	int i = 0;
	while(i < THREAD_NUM)
	{
		handle[i] =  (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
		i++;//等子线程接收到参数时主线程可能改变了这个i值
	}
	//保证子线程已所有运行结束
	WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);
	return 0;
}

unsigned int _stdcall Fun(void *pPM)
{
	//因为建立线程是要必定开销的,因此新线程并不能第一时间执行到这
	int nThreadNum = *(int *)pPM;//子线程获取参数
	Sleep(50);
	g_nNum++;//处理全局资源
	Sleep(0);
	printf("线程编号为%d 全局资源值为%d\n",nThreadNum,g_nNum);
	return 0;
}
执行结果:



//经典线程同步互斥问题(关键段=临界区=critical section)
#include <stdio.h>//C标准输入输出
#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("经典线程同步 关键段");
	//临界区初始化
	InitializeCriticalSection(&g_csThreadParameter);
	InitializeCriticalSection(&g_csThreadCode);

	g_nNum = 0;
	HANDLE handle[THREAD_NUM];
	int i = 0;

	while(i < THREAD_NUM)
	{
		EnterCriticalSection(&g_csThreadParameter);//进入子线程序号临界区
		handle[i] =  (HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
		i++;//等子线程接收到参数时主线程可能改变了这个i值
	}
	//保证子线程已所有运行结束
	WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);
	DeleteCriticalSection(&g_csThreadCode);
	DeleteCriticalSection(&g_csThreadParameter);
	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;
}
运行结果


各子线程已经能够互斥的访问与输出全局资源了,但主线程与子线程之间的同步仍是有点问题。 调试

EnterCriticalSection(&g_csThreadParameter);//进入子线程序号临界区


主线程和子线程没能同步:由于主线程能屡次进入这个关键区域! code

临界区会记录拥有该临界区的线程句柄(指针),临界区是有“线程全部权”概念的 对象

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;//调试用
    LONGLockCount;//n表示有n个线程在等待,初始化为-1
    LONGRecursionCount;//该关键段拥有线程对此资源获取关键段的次数
    HANDLEOwningThread; //即拥有该关键段的线程句柄 from the thread's ClientId->UniqueThread
    HANDLELockSemaphore;//自复位事件
    DWORDSpinCount;//旋转锁的设置,单CPU下忽略
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

所以能够将临界区比做旅馆的房卡,调用EnterCriticalSection()即申请房卡,获得房卡后本身固然是能够屡次进出房间的,在你调用LeaveCriticalSection()交出房卡以前,别人天然是没法进入该房间。 进程

回到这个经典线程同步问题上,主线程正是因为拥有“线程全部权”即房卡,因此它能够重复进入关键代码区域从而致使子线程在接收参数以前主线程就已经修改了这个参数。因此关键段能够用于线程间的互斥,但不能够用于同步。 事件

在经典多线程问题中设置一个事件和一个临界区。用事件处理主线程与子线程的同步,用临界区来处理各子线程间的互斥。

//经典线程同步互斥问题 #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("经典线程同步 事件关键段"); //事件和关键段初始化 自动置位,初始无触发的匿名事件 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++;//等子线程接收到参数时主线程可能改变了这个i值 } WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); CloseHandle(g_hThreadEvent); DeleteCriticalSection(&g_csThreadCode); 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; } 


说明主线程与子线程达到了同步。

最后总结下事件Event

1.事件是内核对象,事件分为手动置位事件自动置位事件。事件Event内部它包含一个使用计数(全部内核对象都有),一个布尔值表示是手动置位事件仍是自动置位事件,另外一个布尔值用来表示事件有无触发。

2.事件能够由SetEvent()来触发,由ResetEvent()来设成未触发。还能够由PulseEvent()来发出一个事件脉冲。

3.事件能够解决线程间同步问题,所以也能解决互斥问题。

互斥量

//经典线程同步互斥问题 #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("经典线程同步 事件关键段"); //互斥两和关键段初始化 第二个参数为true表示互斥量为建立线程全部 g_hThreadParameter= CreateMutex(NULL,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_hThreadParameter,INFINITE);//等待互斥量被触发 i++;//等子线程接收到参数时主线程可能改变了这个i值 } WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); CloseHandle(g_hThreadParameter); DeleteCriticalSection(&g_csThreadCode); for(i = 0;i<THREAD_NUM;i++) CloseHandle(handle[i]); return 0; } unsigned int _stdcall Fun(void *pPM) { //因为建立线程是要必定开销的,因此新线程并不能第一时间执行到这 int nThreadNum = *(int *)pPM;//子线程获取参数 ReleaseMutex(g_hThreadParameter);//触发互斥量 Sleep(50); EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域 g_nNum++;//处理全局资源 Sleep(0); printf("线程编号为%d 全局资源值为%d\n",nThreadNum,g_nNum); LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区 return 0; }
与关键段相似,互斥量也是不能解决线程间的同步问题

最后总结下互斥量Mutex

1.互斥量是内核对象,它与关键段都有“线程全部权”因此不能用于线程的同步。

2.互斥量可以用于多个进程之间线程互斥问题,而且能完美的解决某进程意外终止所形成的“遗弃”问题。

信号量Semaphore

因为信号量是内核对象,所以使用CloseHandle()就能够完成清理与销毁了。

能够看出来,信号量也能够解决线程之间的同步问题。
相关文章
相关标签/搜索