C语言多线程中变量累加问题的分析

简介: 问题:请问下面程序中,main函数打印出的g_iTestInteger变量的值是多少?/*********************************************************************** 版权所有 (C)2015, Zhou Zhaoxiong。

问题:请问下面程序中,main函数打印出的g_iTestInteger变量的值是多少?

/**********************************************************************
* 版权所有 (C)2015, Zhou Zhaoxiong。
*
* 文件名称:MultipleThread_1.c
* 文件标识:无
* 内容摘要:多线程中的变量值问题
* 其它说明:无
* 当前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20151117
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 重定义数据类型
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!\n", iLoopFlag);
            return -1;
        }
    }

    // 打印全局变量的值
    printf("In main, TestInteger = %d\n", 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 <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 重定义数据类型
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!\n", iLoopFlag);
            return -1;
        }
    }

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

    return 0;   
}


/**********************************************************************
 * 功能描述: 处理线程
 * 输入参数: pParam-线程编号
 * 输出参数: 无
 * 返 回 值: 无
 * 其它说明: 无
 * 修改日期            版本号            修改人           修改内容
 * ----------------------------------------------------------------------
*  20151117           V1.0          Zhou Zhaoxiong       创建
 ************************************************************************/
void ProcessTask(void *pParam)
{
    g_iTestInteger ++;
    printf("TestInteger = %d\n", 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 <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 重定义数据类型
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!\n", iLoopFlag);
            return -1;
        }
    }

    Sleep(1000);   // 休息1s

    // 打印全局变量的值
    printf("In main, TestInteger = %d\n", 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),那么放到一个单线程里面顺序执行更好。

本文中的程序已经放到了GitHub上,欢迎下载阅读:https://github.com/zhouzxi/MultipleThread

原文链接:http://www.daxixiong.com/?/article/15

目录
相关文章
|
8月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
448 1
|
9月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
402 1
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
1319 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
存储 人工智能 程序员
一文彻底搞清楚C语言的数据类型和变量
本文介绍了数据类型(基本、构造、指针、空类型)、变量(使用、命名规则、作用域)和常量(字面、符号、枚举、表达式),帮助初学者理解编程基础概念。坚持学习,定能创造奇迹!
2153 1
一文彻底搞清楚C语言的数据类型和变量
|
存储 C语言
【C语言程序设计——循环程序设计】利用数列的累加和求 sinx(头歌实践教学平台习题)【合集】
项的累加和,一般会使用循环结构,在每次循环中计算出当前项的值(可能基于通项公式或者递推关系),然后累加到一个用于存储累加和的变量中。在C语言中推导数列中的某一项,通常需要依据数列给定的通项公式或者前后项之间的递推关系来实现。例如,对于一个简单的等差数列,其通项公式为。的级数,其每一项之间存在特定的递推关系(后项的分子是其前项的分子乘上。,计算sinx的值,直到最后一项的绝对值小于。为项数),就可以通过代码来计算出指定项的值。对于更复杂的数列,像题目中涉及的用于近似计算。开始你的任务吧,祝你成功!
436 6
|
存储 编译器 C语言
【C语言】C语言的变量和声明系统性讲解
在C语言中,声明和定义是两个关键概念,分别用于告知编译器变量或函数的存在(声明)和实际创建及分配内存(定义)。声明可以多次出现,而定义只能有一次。声明通常位于头文件中,定义则在源文件中。通过合理组织头文件和源文件,可以提高代码的模块化和可维护性。示例包括全局变量、局部变量、函数、结构体、联合体、数组、字符串、枚举和指针的声明与定义。
865 12
|
消息中间件 Unix Linux
【C语言】进程和线程详解
在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。
642 6
|
C语言
【C语言】全局搜索变量却找不到定义?原来是因为宏!
使用条件编译和 `extern` 来管理全局变量的定义和声明是一种有效的技术,但应谨慎使用。在可能的情况下,应该优先考虑使用局部变量、函数参数和返回值、静态变量或者更高级的封装技术(如结构体和类)来减少全局变量的使用。
392 5
|
消息中间件 存储 负载均衡
C 语言多线程编程:并行处理的利剑
C语言多线程编程是实现并行处理的强大工具,通过创建和管理多个线程,可以显著提升程序执行效率,尤其在处理大量数据或复杂计算时效果显著。
1123 5

热门文章

最新文章