C++多线程——线程同步

线程同步一般有三种实现方法:互斥对象(CreateMutex),事件对象(CreateEvent),关键代码段(CriticalSection),下面分别进行介绍

一、互斥对象(CreateMutex)

创建互斥

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针,NULL表示默认安全性
BOOLbInitialOwner, // 初始化互斥对象的所有者,TRUE表示创建这个互斥对象的线程获得该对象所有权,否则该线程将不获得
LPCTSTRlpName // 指向互斥对象名的指针,NULL表示匿名对象,如果存在命名则下次调用则GetLastError返回ERROR_ALREADY_EXISTS
//返回值:返回创建互斥对象的句柄
);
互斥等待

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
调用该函数后,函数会一直等待,只有在以下两种情况才会返回
1.指定对象变成有信号状态
2.指定的等待时间间隔已过
3.使内部计数器+1

释放互斥

BOOL ReleaseMutex(HANDLE hMutex);//释放指定对象的所有权,使内部计数器-1


实现示例如下:

#include <Windows.h>
#include <iostream>
using namespace std;

typedef struct  _STRUCT_DATA_
{
	int id; //用于标识出票id
	int tickets;
}_DATA,*_pDATA;

HANDLE g_hMutex;

DWORD WINAPI Fun1Proc(LPVOID lpParam);
DWORD WINAPI Fun2Proc(LPVOID lpParam);

void main()
{

	HANDLE hThread1;
	HANDLE hThread2;
	
	_DATA stru_data;
	stru_data.id = 0;
	stru_data.tickets = 100;
	g_hMutex = CreateMutex(NULL,FALSE,L"Ticket");
	if (g_hMutex)
	{
		if (ERROR_ALREADY_EXISTS == GetLastError())
		{
			cout<<"the instance is exist!"<<endl;
			return;
		}
	}
	hThread1 = CreateThread(NULL,0,Fun1Proc,&stru_data,0,NULL);
	hThread2 = CreateThread(NULL,0,Fun2Proc,&stru_data,0,NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	Sleep(4000);
}

DWORD WINAPI Fun1Proc(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while(TRUE)
	{
		WaitForSingleObject(g_hMutex,INFINITE);
		if (data->tickets>0)
		{
			Sleep(1);
			cout<<"id: "<<data->id++<<endl;
			cout<<"thread1 sell<<  ticket: "<<data->tickets--<<endl;
		}
		else
			break;
		ReleaseMutex(g_hMutex);
	}
	return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while(TRUE)
	{
		WaitForSingleObject(g_hMutex,INFINITE);
		if (data->tickets>0)
		{
			Sleep(1);
			cout<<"id: "<<data->id++<<endl;
			cout<<"thread2 sell  ticket: "<<data->tickets--<<endl;
		}
		else
			break;
		ReleaseMutex(g_hMutex);
	}
	return 0;
}

二、事件对象(CreateEvent)

HANDLECreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性
BOOLbManualReset,// 复位方式,TRUE表示人工重置(需要调用ResetEvent将事件置为无信号状态),FALSE表示自动重置(线程得到所有权后立即置为无信号状态)
BOOLbInitialState,// 初始状态,TRUE为有信号状态,FALSE为无信号状态
LPCTSTRlpName // 对象名称
);

BOOL SetEvent(HANDLE hEvent);//把指定事件对象设置为有信号状态
BOOL ResetEvent(HANDLE hEvent);//把指定事件对象设置为无信号状态
同样,我们以上面示例为基础,采用事件对象进行线程同步

#include <Windows.h>
#include <iostream>
using namespace std;

typedef struct  _STRUCT_DATA_
{
	int id; //用于标识出票id
	int tickets;
}_DATA,*_pDATA;

HANDLE g_hEvent;

DWORD WINAPI Fun1Proc(LPVOID lpParam);
DWORD WINAPI Fun2Proc(LPVOID lpParam);

void main()
{
	HANDLE hThread1;
	HANDLE hThread2;
	
	_DATA stru_data;
	stru_data.id = 0;
	stru_data.tickets = 100;
	
	g_hEvent = CreateEvent(NULL,FALSE,FALSE,L"Ticket");
	if (g_hEvent)
	{
		if (ERROR_ALREADY_EXISTS == GetLastError())
		{
			cout<<"the instance is exist!"<<endl;
			return;
		}
	}
	
	hThread1 = CreateThread(NULL,0,Fun1Proc,&stru_data,0,NULL);
	hThread2 = CreateThread(NULL,0,Fun2Proc,&stru_data,0,NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	SetEvent(g_hEvent);
	Sleep(4000);
	CloseHandle(g_hEvent);
}

DWORD WINAPI Fun1Proc(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while(TRUE)
	{
		WaitForSingleObject(g_hEvent,INFINITE);
		if (data->tickets>0)
		{
			Sleep(1);
			cout<<"id: "<<data->id++<<endl;
			cout<<"thread1 sell<<  ticket: "<<data->tickets--<<endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;
		}
	}
	return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while(TRUE)
	{
		WaitForSingleObject(g_hEvent,INFINITE);
		if (data->tickets>0)
		{
			Sleep(1);
			cout<<"id: "<<data->id++<<endl;
			cout<<"thread2 sell  ticket: "<<data->tickets--<<endl;
			SetEvent(g_hEvent);
		}
		else
		{
			SetEvent(g_hEvent);
			break;
		}
	}
	return 0;
}
使用事件对象实现线程间同步需要注意区分人工重置事件对象和自动重置事件对象。

       当人工重置事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程。在一个线程得到该事件对象之后,操作系统并不会将该事件设置为无信号状态,需要显示调用ResetEvent函数将其设置为无信号状态,否则该对象会一直是有信号状态。

       当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度,同时操作系统会将该事件对象设置无信号状态。这样,当对所保护的代码执行完成后,需要调用SetEvent函数将该事件对象设置为有信号状态。

三、关键代码段(CriticalSection)

      关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);//初始化临界区
//另外还有如下三个函数,分别如下:
EnterCriticalSection	//进入临界区
LeaveCriticalSection	//离开临界区
DeleteCriticalSection	//删除临界区
同理,我们可以采用关键代码段(CriticalSection)来实现上述代码线程同步功能,如下所示:

#include <Windows.h>
#include <iostream>
using namespace std;

typedef struct  _STRUCT_DATA_
{
	int id; //用于标识出票id
	int tickets;
}_DATA,*_pDATA;

CRITICAL_SECTION g_cs;

DWORD WINAPI Fun1Proc(LPVOID lpParam);
DWORD WINAPI Fun2Proc(LPVOID lpParam);

void main()
{
	HANDLE hThread1;
	HANDLE hThread2;
	
	_DATA stru_data;
	stru_data.id = 0;
	stru_data.tickets = 100;
	
	hThread1 = CreateThread(NULL,0,Fun1Proc,&stru_data,0,NULL);
	hThread2 = CreateThread(NULL,0,Fun2Proc,&stru_data,0,NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	InitializeCriticalSection(&g_cs);
	Sleep(4000);
	LeaveCriticalSection(&g_cs);
}

DWORD WINAPI Fun1Proc(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while(TRUE)
	{
		EnterCriticalSection(&g_cs);
		if (data->tickets>0)
		{
			Sleep(1);
			cout<<"id: "<<data->id++<<endl;
			cout<<"thread1 sell<<  ticket: "<<data->tickets--<<endl;
			LeaveCriticalSection(&g_cs);
		}
		else
		{
			LeaveCriticalSection(&g_cs);
			break;
		}
	}
	return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParam)
{
	_pDATA data = (_pDATA)lpParam;
	while(TRUE)
	{
		EnterCriticalSection(&g_cs);
		if (data->tickets>0)
		{
			Sleep(1);
			cout<<"id: "<<data->id++<<endl;
			cout<<"thread2 sell  ticket: "<<data->tickets--<<endl;
			LeaveCriticalSection(&g_cs);
		}
		else
		{
			LeaveCriticalSection(&g_cs);
			break;
		}
	}
	return 0;
}

三种方式之间的区别:

       互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

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

使用建议:

       在MFC程序中,可以在类的构造函数中调用InitializeCriticalSection函数,在析构函数中调用DeleteCriticalSection函数,在所需保护的代码前面调用EnterCriticalSection函数,在访问完所需保护的资源后,调用LeaveCriticalSection函数。



  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中,我们可以使用线程库来实现多线程编程。线程的挂起、唤醒与终止是多线程编程中非常重要的一部分。 线程的挂起也称为线程的休眠,它可以让线程停止运行一段时间,等待某个条件满足后再继续运行。在 C++ 中,我们可以使用 std::this_thread::sleep_for() 函数来实现线程的挂起,该函数可以让当前线程挂起一段时间,例如: ```cpp #include <chrono> #include <thread> int main() { // 挂起当前线程 1 秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; } ``` 线程的唤醒可以通过条件变量来实现,条件变量是一种同步机制,用于在线程之间传递信号。在 C++ 中,我们可以使用 std::condition_variable 类来创建条件变量,然后使用 wait() 函数来挂起线程等待条件变量的信号,使用 notify_one() 函数来唤醒一个等待条件变量的线程,例如: ```cpp #include <condition_variable> #include <mutex> #include <thread> std::condition_variable cv; std::mutex mtx; bool ready = false; void worker_thread() { // 等待条件变量的信号 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); // 条件满足后继续执行 // ... } int main() { // 唤醒等待条件变量的线程 { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); return 0; } ``` 线程的终止可以使用 std::thread::join() 函数来实现,该函数可以让当前线程等待另一个线程执行完成后再继续执行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 等待 worker_thread 执行完成 t.join(); return 0; } ``` 另外,线程的终止还可以使用 std::thread::detach() 函数来实现,该函数可以让当前线程与创建的线程分离,使得两个线程可以独立运行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 分离线程,使得两个线程可以独立运行 t.detach(); return 0; } ``` 需要注意的是,分离线程后,主线程不能再使用 join() 函数等待子线程执行完成,否则会导致程序崩溃。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值