码迷,mamicode.com
首页 > 编程语言 > 详细

多线程面试秒杀系列3---一个带有全局变量的多线程程序

时间:2015-05-26 21:34:29      阅读:178      评论:0      收藏:0      [点我收藏+]

标签:全局变量   面试   多线程   编译器   汇编   

       我们能不能来一个线程报数功能,即第一个子线程输出1,第二个子线程输出2,第三个子线程输出3,……。要实现这个功能似乎非常简单——每个子线程对一个全局变量进行递增并输出就可以了。

代码如下:

    //子线程报数  
    #include <stdio.h>  
    #include <process.h>  
    #include <windows.h>  
    int g_nCount;  
    //子线程函数  
    unsigned int __stdcall ThreadFun(PVOID pM)  
    {  
        g_nCount++;  
        printf("线程ID号为%4d的子线程报数%d\n", GetCurrentThreadId(), g_nCount);  
        return 0;  
    }  
    //主函数,所谓主函数其实就是主线程执行的函数。  
    int main()  
    {  
        printf("     子线程报数 \n");  
          
        const int THREAD_NUM = 10;  
        HANDLE handle[THREAD_NUM];  
      
        g_nCount = 0;  
        for (int i = 0; i < THREAD_NUM; i++)  
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);  
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
        return 0;  
    }  

       但是其实这样是有问题的,其实和上一篇中说明的问题差不多就是脏读问题,因为赋值的那一句C++代码可能会由几句汇编语句组成,如果没有什么措施,比如说一句C++语句由三句汇编组成,线程A执行到第二句汇编语句的时候线程B开始执行改变了变量,最后导致结果不对。

      下面为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是否运行出错。这也非常类似于统计一个网站每天有多少用户登录,每个用户登录用一个线程模拟,线程运行时会将一个表示计数的变量递增。程序在最后输出计数的值表示有今天多少个用户登录,如果这个值不等于我们启动的线程个数,那显然说明这个程序是有问题的。

代码如下:

    #include <stdio.h>  
    #include <process.h>  
    #include <windows.h>  
    volatile long g_nLoginCount; //登录次数  
    unsigned int __stdcall Fun(void *pPM); //线程函数  
    const int THREAD_NUM = 10; //启动线程数  
    unsigned int __stdcall ThreadFun(void *pPM)  
    {  
        Sleep(100); //some work should to do  
        g_nLoginCount++;  
        Sleep(50);   
        return 0;  
    }  
    int main()  
    {  
        g_nLoginCount = 0;  
      
        HANDLE  handle[THREAD_NUM];  
        for (int i = 0; i < THREAD_NUM; i++)  
            handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);  
          
        WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);   
        printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM, g_nLoginCount);  
        return 0;  
    }  

       现在只有10个账户,可能表面上看上去没有什么问题,但是我们小小修改一下看一下。现在模拟50个用户登录,为了便于观察结果,在程序中将50个用户登录过程重复20次。 

代码如下:


    #include <stdio.h>  
    #include <windows.h>  
    volatile long g_nLoginCount; //登录次数  
    unsigned int __stdcall Fun(void *pPM); //线程函数  
    const DWORD THREAD_NUM = 50;//启动线程数  
    DWORD WINAPI ThreadFun(void *pPM)  
    {  
        Sleep(100); //some work should to do  
        g_nLoginCount++;  
        Sleep(50);  
        return 0;  
    }  
    int main()  
    {  
        printf("     原子操作 Interlocked系列函数的使用\n");  
        printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
          
        //重复20次以便观察多线程访问同一资源时导致的冲突  
        int num= 20;  
        while (num--)  
        {     
            g_nLoginCount = 0;  
            int i;  
            HANDLE  handle[THREAD_NUM];  
            for (i = 0; i < THREAD_NUM; i++)  
                handle[i] = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);  
            WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
            printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM, g_nLoginCount);  
        }  
        return 0;  
    }  


       现在你在看一下结果是不是和预想的结果不太一样,应该都是50的数目出现了几个49,是不是感觉那不太对。那下面我们分析一下这个到底是因为什么。要解决这个问题,我们就分析下g_nLoginCount++;操作。在VC6.0编译器对g_nLoginCount++;这一语句打个断点,再按F5进入调试状态,然后按下Debug工具栏的Disassembly按钮,这样就出现了汇编代码窗口。可以发现在C/C++语言中一条简单的自增语句其实是由三条汇编代码组成的,如下图所示。  

技术分享

      

讲解下这三条汇编意思:

第一条汇编将g_nLoginCount的值从内存中读取到寄存器eax中。

第二条汇编将寄存器eax中的值与1相加,计算结果仍存入寄存器eax中。

第三条汇编将寄存器eax中的值写回内存中。

       出现错误可能的原因是,A,B线程都读取了同一个值,然后相加后,写入到 memory 中,这样实际上 A 和 B 是只对 global value 加了一次。这样执行下来,结果是不可预知的——可能会出现50,可能小于50。

       因此在多线程环境中对一个变量进行读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即不可打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作。这种涉及到硬件的操作会不会很复杂了,幸运的是,Windows系统为我们提供了一些以Interlocked开头的函数来完成这一任务。下一篇我们会分析一下Interlocked函数的应用。

       文中的代码均来自morewindows大大的博客。

多线程面试秒杀系列3---一个带有全局变量的多线程程序

标签:全局变量   面试   多线程   编译器   汇编   

原文地址:http://blog.csdn.net/djd1234567/article/details/46011285

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!