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),那么放到一个单线程里面顺序执行更好。
上一篇: 喝了会得软骨病?放久了有毒?有关饮用水那些事儿一次说清楚!
下一篇: 【MYSQL】查询不区分大小写