多线程同步互斥对象、事件对象、关键代码段

网友投稿 601 2022-05-29

互斥对象、事件对象、关键代码段

一、互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但 可以实现在多个进程中各线程间进行同步。

二、关键代码段式工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在进入关键代码时无法设定超时值。

MFC下InitializeCriticalSection()和DeleteCriticalSection()可以放在类的构造函数和析构函数中

在编写程序时首选关键代码段,但需要非常注意死锁问题!

多线程编程推荐书籍《Windows核心编程》机械工业出版社

三、线程、进程、程序的区别

程序

计算机指令的集合,它以文件的形式存储在磁盘上

进程

通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动

区别:进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立的运行的单位,因此,他不占用系统的运行资源。

进程由两个部分组成:

1、操作系统用来管理进程的内核对象。内核对象是操作系统内部分配的一个内存块,内核对象也是系统用来存放关于进程的统计信息的地方。

2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。他还包含动态内存分配的空间。如线程堆栈和堆分配空间。

内核对象:是操作系统内部分配的一个内存块,它是一种只能被内核访问的数据结构, 其成员负责维护该对象的各种信息,应用程序无法找到并直接改变它们的内容,只能通过Windows提供的函数对内核对象进行操作。

进程

进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。

若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。

单个进程可能包含若干个线程,这些线程都“同时”执行进程地址空间中的代码。

每个进程至少拥有一个线程,来执行进程的地址空间中的代码。

当创建一个进程时,操作系统会自动创建这个进程的一个线程,称为主线程。此后,该线程可以创建其他的线程

线程

线程有两个部分组成:

1。线程的内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地方。

2。线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。

当创建线程时,系统创建一个线程内核对象。

该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。

可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。

线程总是在某个进程环境中创建。

系统从进程的地址空间中分配内存,供线程的堆栈使用。

新线程运行的进程环境与创建线程的环境相同。

因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易的互相通信。

线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。

因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。

线程运行

对于单个CPU

操作系统为每一个运行线程安排一定的CPU时间——时间片。

系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时进行的一样。

如果计算机拥有多个CPU,线程就能真正意义上运行了

注意

我们可以用多进程代替多线程,但是这样不是明智的,因为

1.每新建一个进程,系统要为之分配4GB的虚拟内存,浪费资源;而多线程共享同一个地址空间,占用资源较少

2.在进程之间发生切换时,要交换整个地址空间;而线程之间的切换只是执行环境的改变,效率较高。

四、多线程具体实现

一、互斥对象

这种办法不适合在多核CPU的电脑上 。自动重置(只有一个线程可以得到事件对象)

#include

#include

DWORD WINAPI Fun1Proc (LPVOID lpParameter);

DWORD WINAPI Fun2Proc (LPVOID lpParameter);

int index=0;

int tickets=100;

HANDLE hMutex;

void main()

{

HANDLE hThread1,hThread2;

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

CloseHandle(hThread1); //关闭句柄没有终止线程,标示主线程对新线程的引用不感兴趣,引用计数减1

CloseHandle(hThread2);

//创建互斥对象

hMutex=CreateMutex(NULL,FALSE,NULL); //FALSE标示该线程(main)不获得互斥对象的所有权

Sleep(4000);

}

DWORD WINAPI Fun1Proc(LPVOID lpParameter) //当引用计数为0时,终止线程

{

while (TRUE)

{

WaitForSingleObject(hMutex,INFINITE); //INFINITE表示一直等待hMutex有效,不超时退出

if (tickets>0)

{

Sleep(2);

cout<<"thread1 sell tickes :"<

}

else

{

break;

}

ReleaseMutex(hMutex);

}

return 0;

}

DWORD WINAPI Fun2Proc (LPVOID lpParameter)

{

while (TRUE)

{

WaitForSingleObject(hMutex,INFINITE);

if (tickets>0)

{

cout<<"thread2 sell tickes :"<

}

else

{

break;

}

ReleaseMutex(hMutex);

}

return 0;

}

二、事件对象

这种办法不适合在多核CPU的电脑上 。自动重置(只有一个线程可以得到事件对象)

#include

#include

DWORD WINAPI Fun1Proc (LPVOID lpParameter);

DWORD WINAPI Fun2Proc (LPVOID lpParameter);

int tickets=100;

HANDLE g_hEvent;

void main()

{

HANDLE hThread1,hThread2;

HANDLE g_hEvent1;

g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets"); //让应用程序只能有一个实例

if(g_hEvent1)

{

if(ERROR_ALREADY_EXISTS==GetLastError())

{

cout<<"only one instance can run !"<

return;

}

}

g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); //创建自动重置事件内核对象,非信号状态

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

CloseHandle(hThread1); //关闭句柄没有终止线程,标示主线程对新线程的引用不感兴趣,引用计数减1

CloseHandle(hThread2);

SetEvent(g_hEvent);

Sleep(4000);

CloseHandle(g_hEvent);

}

DWORD WINAPI Fun1Proc(LPVOID lpParameter) //当引用计数为0时,终止线程

{

while (TRUE)

{

WaitForSingleObject(g_hEvent,INFINITE);

if (tickets>0)

{

Sleep(2);

cout<<"thread1 sell tickes :"<

}

else

{

break;

}

SetEvent(g_hEvent);

}

return 0;

}

DWORD WINAPI Fun2Proc (LPVOID lpParameter)

{

while (TRUE)

{

WaitForSingleObject(g_hEvent,INFINITE);

if (tickets>0)

{

cout<<"thread2 sell tickes :"<

}

else

{

break;

}

SetEvent(g_hEvent);

}

return 0;

}

三、关键代码段

又称为临界区:对资源的独占权。

初始化临界区对象函数:InitializeCriticalSection(EnterCriticalSection(LPCRITICAL_SECTION))参数为临界区对象;

申请临界区函数:EnterCriticalSection(LPCRITICAL_SECTION)参数为临界区对象;

释放临界区函数:LeaveCriticalSection(LPCRITICAL_SECTION)参数为临界区对象;

释放临界区资源:DeleteCriticalSection(&g_cs);

使用关键代码段的线程同步代码:

#include

#include

#include

DWORD WINAPI Fun1Proc (LPVOID lpParameter);

DWORD WINAPI Fun2Proc (LPVOID lpParameter);

int tickets=100;

CRITICAL_SECTION g_cs;

void main()

{

HANDLE hThread1,hThread2;

HANDLE g_hEvent1;

g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets"); //让应用程序只能有一个实例

if(g_hEvent1)

{

if(ERROR_ALREADY_EXISTS==GetLastError())

{

cout<<"only one instance can run !"<

return;

}

}

InitializeCriticalSection(&g_cs); //必须在创建线程之前初始化临界区对象,否则会出错

// g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); //创建自动重置事件内核对象,非信号状态

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

CloseHandle(hThread1); //关闭句柄没有终止线程,标示主线程对新线程的引用不感兴趣,引用计数减1

CloseHandle(hThread2);

// SetEvent(g_hEvent);

// getchar();

Sleep(4000);

DeleteCriticalSection(&g_cs);

getchar();

// CloseHandle(g_hEvent);

}

DWORD WINAPI Fun1Proc(LPVOID lpParameter) //当引用计数为0时,终止线程

{

while (TRUE)

{

EnterCriticalSection(&g_cs);

if (tickets>0)

{

Sleep(2);

cout<<"thread1 sell tickes :"<

LeaveCriticalSection(&g_cs);

}

else

{

LeaveCriticalSection(&g_cs);

break;

}

}

return 0;

}

DWORD WINAPI Fun2Proc (LPVOID lpParameter)

{

while (TRUE)

{

EnterCriticalSection(&g_cs);

if (tickets>0)

{

cout<<"thread2 sell tickes :"<

LeaveCriticalSection(&g_cs);

}

else

{

LeaveCriticalSection(&g_cs);

break;

}

}

return 0;

}

三、线程死锁问题:

#include

#include

#include

DWORD WINAPI Fun1Proc (LPVOID lpParameter);

DWORD WINAPI Fun2Proc (LPVOID lpParameter);

int tickets=100;

CRITICAL_SECTION g_csA;

CRITICAL_SECTION g_csB;

void main()

{

HANDLE hThread1,hThread2;

HANDLE g_hEvent1;

g_hEvent1=CreateEvent(NULL,FALSE,FALSE,"tickets"); //让应用程序只能有一个实例

if(g_hEvent1)

{

if(ERROR_ALREADY_EXISTS==GetLastError())

{

cout<<"only one instance can run !"<

return;

}

}

InitializeCriticalSection(&g_csA);

InitializeCriticalSection(&g_csB);

// g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); //创建自动重置事件内核对象,非信号状态

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

CloseHandle(hThread1); //关闭句柄没有终止线程,标示主线程对新线程的引用不感兴趣,引用计数减1

CloseHandle(hThread2);

// SetEvent(g_hEvent);

// getchar();

Sleep(4000);

DeleteCriticalSection(&g_csA);

DeleteCriticalSection(&g_csB);

// CloseHandle(g_hEvent);

}

DWORD WINAPI Fun1Proc(LPVOID lpParameter) //当引用计数为0时,终止线程

{

while (TRUE)

{

EnterCriticalSection(&g_csA);

// Sleep(1);

EnterCriticalSection(&g_csB);

if (tickets>0)

{

Sleep(2);

cout<<"thread1 sell tickes :"<

LeaveCriticalSection(&g_csB);

LeaveCriticalSection(&g_csA);

}

else

{

LeaveCriticalSection(&g_csB);

LeaveCriticalSection(&g_csA);

break;

}

}

return 0;

}

DWORD WINAPI Fun2Proc (LPVOID lpParameter)

{

while (TRUE)

{

EnterCriticalSection(&g_csB);

// Sleep(1);

EnterCriticalSection(&g_csA);

if (tickets>0)

{

cout<<"thread2 sell tickes :"<

LeaveCriticalSection(&g_csA);

LeaveCriticalSection(&g_csB);

}

else

{

LeaveCriticalSection(&g_csA);

LeaveCriticalSection(&g_csB);

break;

}

}

return 0;

}

多线程同步:互斥对象、事件对象、关键代码段

任务调度 多线程

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:零基础学习MongoDB(五)—— 文档CRUD操作丨【绽放吧!数据库】
下一篇:备份和迁移 Kubernetes 利器:Velero
相关文章