Windows用户模式下的线程同步

想要了解线程的同步,唯一的途径就是实际使用

原子访问

原子是物理中最小的单位,原子操作也就是CPU内部执行的最小单位,不可以再被分割

long g_x = 0;
DWORD WINPAI Thread1(LPVOID pvParam)
{
	g_x++;
	return 0;
}
DWORD WINPAI Thread2(LPVOID pvParam)
{
	g_x++;
	return 0;
}

反汇编代码:

//Thread1
MOV EAX,[g_x]
INC EAX
MOV [g_x],EAX

//Thread2
MOV EAX,[g_x]
INC EAX
MOV [g_x],EAX

//这样执行没有任何毛病,但是发生如下:
MOV EAX,[g_x]		//Thread1
INC EAX				//Thread1

MOV EAX,[g_x]		//Thread2
INC EAX				//Thread2
MOV [g_x],EAX		//Thread2

MOV [g_x],EAX		//Thread1

//最终两次自增运算,正确结果为2,错误为1
//两个线程发生错乱,导致数据发生错误
//因为++自增运算在C语言看来是原子操作,翻译为汇编后是3次原子操作,所以存在错误

所以必须进行控制(Interlocked系列函数):
InterlockedExchangeAdd(
PLONG volatile plAddend, //控制的变量
LONG lIncrement); //变动值

long g_x = 0;
DWORD WINPAI Thread1(LPVOID pvParam)
{
	InterlockedExchangeAdd(&g_x,1);
	return 0;
}
DWORD WINPAI Thread2(LPVOID pvParam)
{
	InterlockedExchangeAdd(&g_x,1);
	return 0;
}

//自减时:	InterlockedExchangeAdd(&g_x,-1);

关键段

关键段是一小段代码,它在执行前需要独占对一些共享资源的访问权。
举个例子:比如在飞机上,厕所里有一个马桶,所以同一时刻只允许一个人(线程)在卫生间(关键段)内使用马桶(被保护的资源)。当厕所显示“无人”时,进去上厕所标记为“有人”,上完后标记为“无人”。

CRITICAL_SECTION g_cs;
long g_x = 0;
DWORD WINPAI Thread1(LPVOID pvParam)
{
	EnterCriticalSection(&g_cs);
	g_x ++;
	printf("%d\n",g_x);
	LeaveCriticalSection(&g_cs);
	return 0;
}
DWORD WINPAI Thread2(LPVOID pvParam)
{
	EnterCriticalSection(&g_cs);
	g_x ++;
	printf("%d\n",g_x);
	LeaveCriticalSection(&g_cs);
	return 0;
}

void main()
{
	InitializeCriticalSection(PCRITICAL_SECTION pcs);	//初始化临界区
	DeleteCriticalSection(PCRITICAL_SECTION pcs);	//销毁临界区
}

CRITICAL_SECTION g_cs:是一个数据结构,把所有要进行共享的资源放在EnterCriticalSection(&g_cs)和LeaveCriticalSection(&g_cs)之间,调用时传入g_cs的地址。

存在问题:当厕所上完后没有进行标记“无人”,之后的所有人都无法上厕所。如果有人憋不住,强制进行上厕所,一定会把厕所门给弄坏的。

关键段和自旋锁

当线程试图进入一个关键段,但关键段被另一个线程占用,函数会立即把调用线程切换到等待状态,这意味着线程从用户模式转到内核模式,这个切换开销非常大(大约1000个CPU周期)。
为了提高关键段的性能,在关键段中加入自旋锁,当等待时会用一个自旋锁不断循环尝试进入关键段,只有在尝试失败后,线程才会切换到内核模式并进入等待状态。

BOOL InitializeCriticalSectionAndSpinCount(
	PCRITICAL_SECTION pcs,	//关键段结构地址
	DWORD dwSpinCount);	//旋转循环的次数
DWORD SetCriticalSectionSpinCount(
		PCRITICAL_SECTION pcs,	//关键段结构地址
	DWORD dwSpinCount);	//旋转循环的次数
//主机只有一个处理器时,函数会忽略dwSpinCount参数

相关文章

文章浏览阅读2.2k次,点赞6次,收藏20次。在我们平时办公工作...
文章浏览阅读1k次。解决 Windows make command not found 和...
文章浏览阅读3.2k次,点赞2次,收藏6次。2、鼠标依次点击“计...
文章浏览阅读1.3w次。蓝光版属于高清版的一种。BD英文全名是...
文章浏览阅读974次,点赞7次,收藏8次。提供了更强大的功能,...
文章浏览阅读1.4w次,点赞5次,收藏22次。如果使用iterator的...