程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C語言多線程中變量累加問題的分析

C語言多線程中變量累加問題的分析

編輯:關於C

問題:請問下面程序中,main函數打印出的g_iTestInteger變量的值是多少?

/**********************************************************************
* 版權所有 (C)2015, Zhou Zhaoxiong。
*
* 文件名稱:MultipleThread_1.c
* 文件標識:無
* 內容摘要:多線程中的變量值問題
* 其它說明:無
* 當前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20151117
*
**********************************************************************/
#include 
#include 
#include 

// 重定義數據類型
typedef signed   int    INT32;
typedef unsigned int    UINT32;

// 宏定義
#define THREAD_NUM     100              // 線程個數

// 全局變量
UINT32 g_iTestInteger = 0;

// 函數聲明
void ProcessTask(void *pParam);


/**********************************************************************
* 功能描述:主函數
* 輸入參數:無
* 輸出參數:無
* 返 回 值:無
* 其它說明:無
* 修改日期        版本號     修改人            修改內容
* -------------------------------------------------------------------
* 20151117       V1.0     Zhou Zhaoxiong      創建
***********************************************************************/
INT32 main()
{
    pthread_t MultiHandle  = 0;      // 多線程句柄
    pthread_t SingleHandle = 0;     // 單線程句柄
    UINT32    iLoopFlag    = 0;
    INT32     iRetVal      = 0;  // 創建線程函數的返回值

    // 循環創建線程
    for (iLoopFlag = 0; iLoopFlag < THREAD_NUM; iLoopFlag ++)
    {
        iRetVal = pthread_create(&MultiHandle, NULL, (void * (*)(void *))(&ProcessTask), (void *)iLoopFlag);
        if (0 != iRetVal)
        {
            printf(Create ProcessTask %d failed!
, iLoopFlag);
            return -1;
        }
    }

    // 打印全局變量的值
    printf(In main, TestInteger = %d
, g_iTestInteger);

    return 0;   
}


/**********************************************************************
 * 功能描述: 處理線程
 * 輸入參數: pParam-線程編號
 * 輸出參數: 無
 * 返 回 值: 無
 * 其它說明: 無
 * 修改日期            版本號            修改人           修改內容
 * ----------------------------------------------------------------------
*  20151117           V1.0          Zhou Zhaoxiong       創建
 ************************************************************************/
void ProcessTask(void *pParam)
{
    g_iTestInteger ++;
}

以上程序的功能比較簡單,就是創建100個相同的線程,在線程中對g_iTestInteger的值進行累加,然後在main函數中打印g_iTestInteger的值。

看到這個程序,大家可能會說g_iTestInteger變量的值應該是100,因為每個線程都對g_iTestInteger加了1次。好吧,我們先運行程序,看下打印出來的結果是多少。

我們將程序上傳到Linux機器上,然後執行如下操作:

~/zhouzhaoxiong/zzx/MultipleThread> gcc -g -o MultipleThread MultipleThread_1.c -lpthread
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 99
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 99
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 99
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 98
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 99

出乎大多數人的意料,g_iTestInteger變量的值不但不是100,而且不是固定的值。在這裡,我只是運行了五次程序,大家可以多運行幾次,看結果會不會是100。

那麼,為什麼結果不是100呢?為了查找原因,我們在“g_iTestInteger ++;”代碼之後將g_iTestInteger變量的值打印出來,如下代碼所示:

/**********************************************************************
* 版權所有 (C)2015, Zhou Zhaoxiong。
*
* 文件名稱:MultipleThread_2.c
* 文件標識:無
* 內容摘要:多線程中的變量值問題
* 其它說明:無
* 當前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20151117
*
**********************************************************************/
#include 
#include 
#include 

// 重定義數據類型
typedef signed   int    INT32;
typedef unsigned int    UINT32;

// 宏定義
#define THREAD_NUM     100              // 線程個數

// 全局變量
UINT32 g_iTestInteger = 0;

// 函數聲明
void ProcessTask(void *pParam);


/**********************************************************************
* 功能描述:主函數
* 輸入參數:無
* 輸出參數:無
* 返 回 值:無
* 其它說明:無
* 修改日期        版本號     修改人            修改內容
* -------------------------------------------------------------------
* 20151117       V1.0     Zhou Zhaoxiong      創建
***********************************************************************/
INT32 main()
{
    pthread_t MultiHandle  = 0;      // 多線程句柄
    pthread_t SingleHandle = 0;     // 單線程句柄
    UINT32    iLoopFlag    = 0;
    INT32     iRetVal      = 0;  // 創建線程函數的返回值

    // 循環創建線程
    for (iLoopFlag = 0; iLoopFlag < THREAD_NUM; iLoopFlag ++)
    {
        iRetVal = pthread_create(&MultiHandle, NULL, (void * (*)(void *))(&ProcessTask), (void *)iLoopFlag);
        if (0 != iRetVal)
        {
            printf(Create ProcessTask %d failed!
, iLoopFlag);
            return -1;
        }
    }

    // 打印全局變量的值
    printf(In main, TestInteger = %d
, g_iTestInteger);

    return 0;   
}


/**********************************************************************
 * 功能描述: 處理線程
 * 輸入參數: pParam-線程編號
 * 輸出參數: 無
 * 返 回 值: 無
 * 其它說明: 無
 * 修改日期            版本號            修改人           修改內容
 * ----------------------------------------------------------------------
*  20151117           V1.0          Zhou Zhaoxiong       創建
 ************************************************************************/
void ProcessTask(void *pParam)
{
    g_iTestInteger ++;
    printf(TestInteger = %d
, g_iTestInteger);
}

重新上傳程序,編譯並執行,如下:

TestInteger = 1
TestInteger = 3
TestInteger = 2
TestInteger = 4
TestInteger = 5
TestInteger = 6
TestInteger = 26
TestInteger = 7
TestInteger = 8
TestInteger = 9
TestInteger = 10
TestInteger = 11
TestInteger = 12
TestInteger = 13
TestInteger = 27
TestInteger = 15
TestInteger = 16
TestInteger = 17
TestInteger = 18
TestInteger = 19
TestInteger = 20
TestInteger = 21
TestInteger = 22
TestInteger = 23
TestInteger = 24
TestInteger = 25
TestInteger = 14
TestInteger = 28
TestInteger = 29
TestInteger = 30
TestInteger = 31
TestInteger = 32
TestInteger = 33
TestInteger = 34
TestInteger = 35
TestInteger = 36
TestInteger = 37
TestInteger = 38
TestInteger = 39
TestInteger = 40
TestInteger = 41
TestInteger = 42
TestInteger = 43
TestInteger = 44
TestInteger = 45
TestInteger = 49
TestInteger = 47
TestInteger = 48
TestInteger = 46
TestInteger = 50
TestInteger = 54
TestInteger = 55
TestInteger = 56
TestInteger = 57
TestInteger = 52
TestInteger = 53
TestInteger = 58
TestInteger = 59
TestInteger = 60
TestInteger = 61
TestInteger = 62
TestInteger = 63
TestInteger = 51
TestInteger = 64
TestInteger = 65
TestInteger = 66
TestInteger = 67
TestInteger = 68
TestInteger = 69
TestInteger = 70
TestInteger = 71
TestInteger = 72
TestInteger = 73
TestInteger = 100
TestInteger = 75
TestInteger = 76
TestInteger = 77
TestInteger = 78
TestInteger = 79
TestInteger = 80
TestInteger = 81
TestInteger = 82
TestInteger = 83
TestInteger = 84
TestInteger = 85
TestInteger = 86
TestInteger = 87
TestInteger = 88
TestInteger = 89
TestInteger = 90
TestInteger = 91
TestInteger = 92
TestInteger = 93
TestInteger = 94
TestInteger = 95
TestInteger = 96
TestInteger = 97
TestInteger = 98
In main, TestInteger = 98
TestInteger = 99
TestInteger = 74

可以看到,g_iTestInteger變量的值並不是順序增加的。由此可以看出,這100個線程的執行時間有先後之分,如果按照1~100為它們編號的話,並不一定說10號線程要在9號線程之後執行。除此之外,在main函數中打印g_iTestInteger變量值的時候,也許還有線程在執行(從程序輸出結果來看,確實如此),因此打印出的值不是100,而是小於100的一個數。我們可以猜想,g_iTestInteger的值的范圍是[1, 100]。

為了等線程執行完成之後再打印g_iTestInteger的值,我們可以在線程創建完成之後讓程序休眠一段時間,然後再打印變量值。如下代碼所示:

/**********************************************************************
* 版權所有 (C)2015, Zhou Zhaoxiong。
*
* 文件名稱:MultipleThread_3.c
* 文件標識:無
* 內容摘要:多線程中的變量值問題
* 其它說明:無
* 當前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20151117
*
**********************************************************************/
#include 
#include 
#include 

// 重定義數據類型
typedef signed   int    INT32;
typedef unsigned int    UINT32;

// 宏定義
#define THREAD_NUM     100              // 線程個數

// 全局變量
UINT32 g_iTestInteger = 0;

// 函數聲明
void ProcessTask(void *pParam);
void Sleep(UINT32 iCountMs);


/**********************************************************************
* 功能描述:主函數
* 輸入參數:無
* 輸出參數:無
* 返 回 值:無
* 其它說明:無
* 修改日期        版本號     修改人            修改內容
* -------------------------------------------------------------------
* 20151117        V1.0   Zhou Zhaoxiong       創建
***********************************************************************/
INT32 main()
{
    pthread_t MultiHandle  = 0;      // 多線程句柄
    pthread_t SingleHandle = 0;     // 單線程句柄
    UINT32    iLoopFlag    = 0;
    INT32     iRetVal      = 0;  // 創建線程函數的返回值

    // 循環創建線程
    for (iLoopFlag = 0; iLoopFlag < THREAD_NUM; iLoopFlag ++)
    {
        iRetVal = pthread_create(&MultiHandle, NULL, (void * (*)(void *))(&ProcessTask), (void *)iLoopFlag);
        if (0 != iRetVal)
        {
            printf(Create ProcessTask %d failed!
, iLoopFlag);
            return -1;
        }
    }

    Sleep(1000);   // 休息1s

    // 打印全局變量的值
    printf(In main, TestInteger = %d
, g_iTestInteger);

    return 0;   
}


/**********************************************************************
 * 功能描述: 處理線程
 * 輸入參數: pParam-線程編號
 * 輸出參數: 無
 * 返 回 值: 無
 * 其它說明: 無
 * 修改日期            版本號            修改人           修改內容
 * ----------------------------------------------------------------------
*  20151117           V1.0          Zhou Zhaoxiong       創建
 ************************************************************************/
void ProcessTask(void *pParam)
{
    g_iTestInteger ++;
}


/**********************************************************************
* 功能描述: 程序休眠
* 輸入參數: iCountMs-休眠時間(單位:ms)
* 輸出參數: 無
* 返 回 值: 無
* 其它說明: 無
* 修改日期          版本號       修改人              修改內容
* ------------------------------------------------------------------
* 20151117          V1.0    Zhou Zhaoxiong          創建
********************************************************************/
void Sleep(UINT32 iCountMs)
{
    struct timeval t_timeout = {0};

    if (iCountMs < 1000)
    {
        t_timeout.tv_sec = 0;
        t_timeout.tv_usec = iCountMs * 1000;
    }
    else
    {
        t_timeout.tv_sec = iCountMs / 1000;
        t_timeout.tv_usec = (iCountMs % 1000) * 1000;
    }
    select(0, NULL, NULL, NULL, &t_timeout);   // 調用select函數阻塞程序
}

重新上傳程序,編譯並執行,如下:

~/zhouzhaoxiong/zzx/MultipleThread> gcc -g -o MultipleThread MultipleThread_3.c -lpthread
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 100
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 100
~/zhouzhaoxiong/zzx/MultipleThread> MultipleThread
In main, TestInteger = 100

可以看到,程序運行了多次,g_iTestInteger的值始終是100。看來,“心急吃不得熱豆腐”,我們要等所有線程都全部執行完成之後,再來打印變量值。

通過以上分析,我們可以得出以下結論:

第一,在多線程程序中,盡量不要同時對同一個全局變量執行加減等操作,這樣執行之後的結果很有可能不是我們想要的。

第二,多線程不是萬能的,創建多線程的初衷,是要並行地執行很多互不關聯或關聯度很小的操作。如果某些操作有很強的耦合關系(如本例中的對g_iTestInteger變量加1),那麼放到一個單線程裡面順序執行更好。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved