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

来源:互联网 发布:紫云剑淘宝 编辑:程序博客网 时间:2024/09/21 08:14

线程:

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










原创粉丝点击