c++多线程笔记3 线程同步机制

线程同步机制


由于线程共享同一进程的内存空间,多个线程可能需要同时访问同一个数据。如果没有正确的保护措施,对共享数据的访问会造成数据的不一致和错误。
常用的几种同步机制:

全局变量、临界区(critical section)、信号量(simphore)、互斥量(mutex)、管程(monitor)

1 全局变量

全局变量:进程中的所有线程均可以访问所有的全局变量,因而全局变量成为Win32多线程通信的最简单方式。

全局变量使用举例:

#include<stdio.h>
#include <windows.h>
bool threaddone=false;
DWORD WINAPI helloFunc(LPVOIDarg)
printr("Hello Thread\n");o
return 0;
main()
{
HANDLE hThread= CreateThread(NULL,0, helloFunc, NULL,0,
NULL);
while(threaddone);
}
//举例二,在这个例子里面主线程一直运行直到附线程执行一次改变全局变量。
#include "stdafx.h"
#include <windows. h>
#include <iostream>
using namespace std;
int globalvar=false:I/同步全局变量
DWORD WINAPI ThreadFunc(LPVOID pParam)
{
 cout<<"ThreadFunc"<<endl;
 Sleep(200);
 Beep(5000,2000)
 globalvar= true;
 return 0;
 }
int main()
{
HANDLE hthread=CreateThread(NULL,0, ThreadFunc,NUL,0,NULL);//创建新线/
if(hthread)
  {
    cout<<"Thread Create Error!"<<endl;
    CloseHandle(hthread);
  }
while(lglobalvar)
    cout<<"Thread while"<<endl;//主线程运行
cout<<"Thread exit"<<endl;
return 0;
}

2 临界区(critical section)

临界区同步:临界区是指包含有共享资源的一段代码块,而且这些共享资源和多个线程之间都存在相关关系。临界区也成为同步块,每个临界区都有一个入口点和一个出口点。在进行临界区同步时,多个线程对临界区资源的访问只能有一个,线程停留在临界区,此时其他的线程将被挂起。

使用临界区的原则:
每次只允许一个线程处于它的临界区(CS)中
若有多个线程同时想进入CS,应在有限时间内让其中一个线程进入CS,以免阻塞
线程在CS内只能逗留有限时间;
不应使要进入CS的线程无限期地等待在CS之外;
在CS之外的线程不可以阻止其他进程进入CS;
不要预期和假定线程进展的相对速度以及可用的处理器数目,因为这是不可预期的;

■CRITICAL_SECTION cs(变量可自定义);定义临界区
■InitializeCriticalSection(&cs);初始化临界区
■EnterCriticalSection(&cs);进入临界区
■LeaveCriticalSection(&cs);离开临界区
■ DeleteCriticalSection(&cs);删除临界区

临界区程序设计的方法如下:

DWORD WINAPI ThreadProc (PVOID pParam)

{

EnterCriticalSection(&cs);//进入临界区,锁定共享资源

//data writing;//共享资源的操作

LeaveCriticalSection(&cs);//退出临界区,释放锁定

}

临界区同步举例:

#include <stdio.h>
#include<windows.h>
HANDLE evDone; int nun;
CRITICAL_SECTION csDone;
DWORD WINAPI helloFunc(LPVOIDarg) //多线程处理函数
{
EnterCriticalSection(&csDone);
printf("NUM=%d", num++);
LeaveCriticalSection(&csDone);
SetEvent(evDone);
return 0;
}
main()
{
 num=0;
 evDone=CreateEvent(NULL, FALSE, FALSE,NULL);//这里创建了个事件来控制同步
 InitializeCriticalSection(&csDone);
 HANDLE hThread=_beginThread(helloFunc,0,NULL);
 WaitForSingleObject(evDone, INFINITE);//等待事件结束,目的是防止主线程先退出
 DeleteCriticalSection(&csDone);
 cout<<"DONE"<<endl;
}
//这里不使用事件写WaitForSingleObject(hThread, INFINITE);效果一样

3 互斥量(mutex)

互斥量:Win32 的互斥量 Mutex 属于内和对象,它的用途和临界区的使用十分类似,同样是确保在同一个时间内只有一个线程拥有 Mutex,即在同一时间内只有唯一的一个线程能够访问共享资源。

互斥量相关函数:
创建互斥量:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESIpMutexAttributes,//安全属性结构指针
BOOLblnitialOwner,/是否占有该互斥量,TRUE:占有,FALSE:不占有
LPCTSTR lpName/信号量的名称
)
释放互斥量:
BOOL WINAPI ReleaseMutex(HANDLE hMutex);

使用互斥编程的一般方法是:
void UpdateResource()

{

WaitForSingleObject(hMutex,…);
./do something
ReleaseMutex(hMutex);
}
互斥量举例说明:

//互斥量同步编程示例
/******************使用互斥量同步代码************************/
#include<windows.h>
#include <stdio.h>
#define WORK_THREAD_COUNT 10
HANDLE hMutex;
DWORD WINAPI ThreadProc(LPVOID)
{
	//等待互斥量信号
	WaitForSingleObject(hMutex, INFINITE);
	//保护区
	printf("Thread %d:获取互斥量….处理完毕\n", GetCurrentThreadId());
	Sleep(100);
	ReleaseSemaphore(hMutex, 1, NULL); //释放互斥量
		return 0;
}
int main(int argc, TCHAR*argV)
{
	HANDLE hThread[WORK_THREAD_COUNT];
	DWORD ThreadID;
	hMutex = CreateMutex(NULL,FALSE, NULL);
	for (int i = 0; i < WORK_THREAD_COUNT; i++)
		hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, &ThreadID);//创建十个线程

	WaitForMultipleObjects(WORK_THREAD_COUNT, hThread, TRUE, INFINITE);
	for (int i = 0; i < WORK_THREAD_COUNT; i++)
		CloseHandle(hThread[i]);

	CloseHandle(hMutex);
	return 0;
}

(1)互斥对象的运行速度比关键代码段要慢;
(2)不同进程中的多个线程能够访问单个互斥对象;
(3)线程在等待访问资源时可以设定一个超时值。Mutex 对象的状态在它不被任
何线程拥有时才有信号,而当它被拥有时则无信号。Mutex 对象很适合用来协调多个线
程对共享资源的互斥访问。

4 信号量(semaphore)

信号量同步:
信号对象是内核对象,它拥有一个计数器,可用来控制对多个线程对共享资源进行
访问,在创建对象时指定最大可同时访问的线程数。当一个线程申请访问成功后,信号
对象中的计数器减1,调用ReleaseSemaphore函数后,信号对象中的计数器加1。其中,
计数器值大于或等于0,但小于或等于创建时指定的最大值。如果一个应用在创建一个
信号对象时,将其计数器的初始值设为0,就阻塞了其他线程,保护了资源。等初始化
完成后,调用ReleaseSemaphore 函数将其计数器增加至最大值,则可进行正常的存取访问。
互斥量同步的相关函数:
创建信号对象:
HANDLE CreateSemaphore (
PSECURITY_ATTRIBUTE psa,//安全属性。
LONG InitialCount,//初值,开始时可供使用的资源数
LONG IMaximumCount,//最大资源数
PCTSTR pszName)//Seaphone名称(字符串)
当信号量存在时打开一个信号对象:
HANDLE OpenSemaphore(
DWORD fdwAccess,
BOOLblnherithandle,
PCTSTR pszName
);
共享资源访问完成后,释放对信号对象的占用:
BOOL WINAPI ReleaseSemaphore(
HANDLE hSemaphore,
LONGIReleaseCount, //信号量的当前资源数增加ReleaseCount
LPLONG IpPreviousCount
);
信号量的特点和用途可用下列几句话定义:
(1)如果当前资源的数量大于O,则信号量有效;
(2)如果当前资源数量是0,则信号量无效;
(3)系统决不允许当前资源的数量为负值;
(4)当前资源数量决不能大于最大资源数量。

信号量举例:下面的示例代码创建12个线程,信号量允许同时启用的线程数为4个,在
输出结果时我们可以看到,屏幕将每4个一组输出后延时,再处理其他的线程。

/********** 使用信号量同步代码***************/
#include<windows.h>
#include<stdio.h>
#define MAX_SEM_COUNT 4
#define WORK_THREAD_COUNT 12
HANDLE hSemaphore;
DWORD WINAPI ThreadProc(LPVOID)
{
WaitForSingleObject(hSemaphore, INFINITE); // 等待 Semaphore 信号量 > 0
//保护下
printf("Thread %d:获取信号量….处理完毕\n", GetCurrentThreadId());
Sleep(1000);
ReleaseSemaphore(hSemaphore, 1, NULL); //释放信号量, 信号对象计数器加1
return 0;
}
void main()
{
HANDLE hThread[WORK_THREAD_COUNT];
DWORD ThreadID;
hSemaphore = CreateSemaphore(NULL,MAX_SEM_COUNT,MAX_SEM_COUNT,NULL);

for (int i = 0; i < WORK_THREAD_COUNT; i++)
	hThread[i] = CreateThread(NULL,0,ThreadProc,NULL,0,&ThreadID);

WaitForMultipleObjects(WORK_THREAD_COUNT, hThread, TRUE, INFINITE);

for (int i = 0; i < WORK_THREAD_COUNT; i++ )
	CloseHandle(hThread[i]); 
CloseHandle(hSemaphore);
}

5 事件同步(event)

事件对象的最大特点是:它完全是在你的控制之下的。不像互斥量和信号量一样,
互斥量和信号量的状态都会因为WaiteForSingleObject()函数的调用而变化,事件对象的可以让你完全控制它的状态。
创建事件对象:
HANDLE CreateEvent (LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset,
OOLblnitialState,//初始状态
LPCTSTRIpName//事件的名称
);
说明:

IpEventAttributes: SECURITY_ATTRIBUTES结构指;
bManualReset: //手动(TRUE)自动设置(FALSE)
事件对象创建函数中,如果第二个参数是手工重置事件(TRUE),那么WaitForSingleObject()函数的调用不会影响它的状态,它将总是保持有信号状态,直到用 ResetEvent 函数重置成无信号的事件。如果是自动重置事件,那么它的状态在
WaitForSingleObject()调用后会自动变为无信号的。

打开事件对象:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR IpName);

BOOLSetEvent(HANDLE hEvent)
BOOLResetEvent(HANDLE hEvent);
BOOLPulseEvent(HANDLE hEvent);

事件同步举例

/****** 使用事件同步代码****/
#include<windows.h>
#include<stdio.h>
HANDLE hEvent; // 事件对象
DWORD WINAPI Thread1(LPVOID)
{
	WaitForSingleObject(hEvent, INFINITE);//等待事件激活
	printf("ThreadI Workingin");
	return 0;
}
DWORD WINAPI Thread2(LPVOID)
{
	printf("激活Threadl\n");
	Sleep(500);
	SetEvent(hEvent); // 激活事件对象
		return 0;
}
int main(int argc, TCHAR*argVJ)
{
	HANDLE hThread[2];
	hEvent = CreateEvent(NULL,TRUE, FALSE,NULL);
	hThread[0] = CreateThread(NULL,0, Thread1, NULL, 0,NULL);
	hThread[1] = CreateThread(NULL,0, Thread2, NULL, 0,NULL);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	CloseHandle(hEvent);
	return 0;
}

6 注意事项

死锁:两个线程分别拥有锁,并等待对方的锁

死锁避免方法:线程以相同次序进行加锁、非阻塞的加锁操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章