线程:
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()函数,使用时应注意。