20170701Windows10_06_线程函数、原子操作问题分析

线程:

volatile关键字:

1:在之前的代码中,线程回调函数为:

unsigned int WINAPI ThreadRun(void* lParam)
{
	int nNumber = 0;
	while (TRUE)
	{
		bUsed = TRUE;//bUsed可能变为直接值。
		_tprintf(TEXT("ThreadRun:%d\r\n"), nNumber++);
		bUsed = FALSE;
	}
}

2:在Release下面,由于编译器的优化,可能导致我们的bUsed变成了直接值,就可能直接执行里面的_tprintf函数,要让他不为直接值,要去全局找这个变量,就需要在变量申请前面加上关键字volatile关键字,告诉编译器不再优化这个变量。

3:编译器的优化规则使我们最容易忘记的一点,在线程中用到了全局变量,可能VS各个版本编译出来的代码都是不一样的。还有,如果线程里面没有_tprintf,只是对bUsed进行了一系列判断,编译器一般也会将这个优化掉,认为这个线程里面没有做任何事情。如果前面加上了volatile,编译器就不会优化这个了,会按程序一步一步执行。

问题线程函数分析——详解线程函数:

1:在main里面创建很多个线程的时候,在这里,主线程称之为M线程,其他线程的执行并不是按照线程创建顺序执行的。

#include <Windows.h>
#include <process.h>
#include <tchar.h>

int gNum = 0;
const int LOOPCOUNT = 1000;
const int THREADCOUNT = 1000;

unsigned WINAPI ThreadFun(void *lParam)
{
	int *nThread = (int*)lParam;//将void*转为int*
	gNum = 0;
	for (int i = 0; i < (*nThread); ++i)
	{
		gNum += i;
	}
	_tprintf(TEXT("Thread%4d:gNum=%d\r\n"), *nThread, gNum);
	return 0;
}

int main()
{
	HANDLE hThread[THREADCOUNT] = { INVALID_HANDLE_VALUE };
	for (int i = 0; i < THREADCOUNT; ++i)
	{
		hThread[i] = (HANDLE)_beginthreadex(nullptr, 0, ThreadFun, &i, 0, nullptr);
	}
	WaitForMultipleObjects(THREADCOUNT, hThread, TRUE, INFINITE);
	for (int i = 0; i < THREADCOUNT; ++i)
	{
		CloseHandle(hThread[i]);
	}

	return 0;
}

2:M线程会一直不停地执行,会创建很多个线程,0号线程会访问lParam,实际是访问的M线程里面的值,然而,多个线程都会访问这个值,也会随着创建线程而更改,因此,访问这个值很可能是我们不希望得到的值,与线程号并不一定对应。我们希望的是线程号与取到的值是一样的。

3:要解决这个地址访问问题,我们可以直接传递一个值就可以了,不用传递地址。既是将一个数值强转为地址(这个地址不是我需要的,需要的仅仅是地址这个值),通过这样的方法就可以将值传递过来。

问题线程函数分析——详解原子操作和旋转锁:

1:线程对的执行是抢占式的,没有顺序可言:

#include <Windows.h>
#include <process.h>
#include <tchar.h>

volatile int gNum = 0;
const int LOOPCOUNT = 1000;
const int THREADCOUNT = 1000;

unsigned WINAPI ThreadFun(void *lParam)
{
	int nThread = (int)lParam;
	gNum = 0;
	for (int i = 0; i < LOOPCOUNT; ++i)
	{
		gNum += i;//很可能出现在执行这里的时候,其他线程抢占,导致结果出错。
	}
	_tprintf(TEXT("Thread%4d:gNum=%d\r\n"), nThread, gNum);
	return 0;
}

int main()
{
	HANDLE hThread[THREADCOUNT] = { INVALID_HANDLE_VALUE };
	for (int i = 0; i < THREADCOUNT; ++i)
	{
		hThread[i] = (HANDLE)_beginthreadex(nullptr, 0, ThreadFun, (void*)i, 0, nullptr);
	}
	WaitForMultipleObjects(THREADCOUNT, hThread, TRUE, INFINITE);
	for (int i = 0; i < THREADCOUNT; ++i)
	{
		CloseHandle(hThread[i]);
	}

	return 0;
}

2:执行上面的代码,我们可以发现少部分线程的输出是错误的,这也是多线程编程的最大麻烦——有的时候是错的,有的时候不会错。使用InterlockedExchangeAdd()函数,也会有一样的错误,而且错误可能更多。其反汇编代码如下:

		InterlockedExchangeAdd((long*)&gNum, i);
00CE14A9  mov         eax,dword ptr [ebp-14h]  
00CE14AC  mov         ecx,0CE8160h  
00CE14B1  lock xadd   dword ptr [ecx],eax  //在加的时候,会将这个值锁定,其他都不能动这个东西。

这个原子操作,只能保证加法这句话具有原子性。可以看出:上面的代码仅仅能够保证在gNum+=的时候不会出现错误,但是他无法确保在其他的时候出问题。我们需要确保gNum = 0到1000次加法期间都是安全的,要确保这个gNum的原子性,始终只有一个线程可以使用。

原子性:具有原子性即,在我们进行CPU切换的时候,最小的单元就是原子,CPU的原子就是一句一句的汇编代码,一句一句的汇编代码将决定CPU原子的大小。

3:InterlockedExchange(a,b)能以原子操作的方式交换俩个参数a, b,并返回a以前的值;因为InterlockedExchange 是原子函数,不会要求中止中断,所以交换指针的方式是安全的。
      假设有线程1和线程2调用f()函数,线程1先调用到InterlockedExchange(&g, TRUE);线程2再调用时,函数InterlockedExchange()总返回TRUE,则线程2sleep(0);而直到线程1调用InterlockedExchange(&g, FALSE);后线程2才可能由于调用InterlockedExchange(&g,, TRUE),返回FALSE而退出循环接着工作。这样在//进行其他的操作这里就能操作共享数据而不会引起争议。当然这种方法会浪费cpu时间,因为cpu要不断地执行InterlockedExchange()函数,使用时应注意。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值