一、分析上一篇程序的现象

我们先从上一篇文章中的最后一个程序开始分析。



#include <stdio.h>
#include <windows.h> const unsigned int THREAD_NUM = 10;
DWORD WINAPI ThreadFunc(LPVOID); int main()
{
printf("我是主线程, pid = %d\n", GetCurrentThreadId()); //输出主线程pid
HANDLE hThread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
{
hThread[i] = CreateThread(NULL, 0, ThreadFunc, &i, 0, NULL); // 创建线程
} WaitForMultipleObjects(THREAD_NUM,hThread,true, INFINITE); //一直等待,知道所有子线程全部返回
return 0;
} DWORD WINAPI ThreadFunc(LPVOID p)
{
int n = *(int*)p;
Sleep(1000*n); //第 n 个线程睡眠 n 秒
printf("我是, pid = %d 的子线程\n", GetCurrentThreadId()); //输出子线程pid
printf(" pid = %d 的子线程退出\n\n", GetCurrentThreadId()); //延时10s后输出 return 0;
}

看程序的输出:

按照正常情况来看应该是每一行输出两列,但是中间有一行多出了一列,看图中圈出来的地方,pid = 208 的线程输出线程pid后并没有马上退出,而是等到了最后才退出。(可能每次运行的情况不一样,这里只说明这一种情况),这是为什么的。 这里涉及到了线程调度的问题, 说明pid = 208 的线程输出线程pid后操作系统进行了线程调度,cpu资源被其它线程抢占,这个线程直到最后才又重新分配到cpu资源,重新往下执行。

二、原子操作

这里明明是要写原子操作,但是到目前为止,并没有任何地方提及什么是原子操作,不要着急,接下来就慢慢来说。那么什么是原子操作呢?一个操作如果能够不受中断地完成,我们称之为原子操作

我们来看这个程序


#include <stdio.h>
#include <windows.h> const unsigned int THREAD_NUM = 50;
unsigned int g_Count = 0;
DWORD WINAPI ThreadFunc(LPVOID); int main()
{
HANDLE hThread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
{
hThread[i] = CreateThread(NULL, 0, ThreadFunc, 0, 0, NULL); // 创建线程
}
WaitForMultipleObjects(THREAD_NUM, hThread, true, INFINITE); //一直等待,直到所有子线程全部返回
printf(" 总共 %d 个线程给 g_Count 的值加一,现在 g_Count = %d\n", THREAD_NUM, g_Count);
return 0;
} DWORD WINAPI ThreadFunc(LPVOID p)
{
Sleep(50);
g_Count++;
Sleep(50); return 0;
}

有一个全局变量 g_Count ,每个线程给这个全局变量加一,照这么来看最后应该输出 50 ,我们看一下程序的输出(每次都可能不一样的结果)

为什么会这样呢??? 明明有 50 个线程都给 g_Count 加一了,为什么输出 46,根源在于 g_Count++; 这条语句上,这里就只有一条c++语句,按理说不应该有问题,其实不然,现在,在这里打下断点,开始调试,打开反汇编窗口(Vs编译器快捷键 Alt+8),如下图

可以看到,这一条c++语句,被分成了三条汇编语句,先是把 g_Count 的值给寄存器 eax,然后寄存器 eax 的值加一,再把 eax 的值给 g_Count ,这样就完成一次 g_Count++ 操作。出问题的原因就在于,在这几条汇编语句执行的过程中发送了线程切换,比如,A线程刚执行完 add eax,1 还没有把 eax的值给 g_Count,这时B线程开始执行,把 g_Count 原先的值又存入 eax,这就修改了 eax 中A线程计算好的值。

因此在多线程环境中对一个变量进行读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即这个操作不可以被打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作。Windows系统为我们提供了一些以Interlocked开头的函数来完成这一任务。这里只是介绍原子操作的概念,这和线程同步息息相关,但是这些以 以Interlocked 开头的函数我们基本不用,就不一一介绍了,感兴趣的可以自己去了解。

最新文章

  1. python之路1(初识python)
  2. MFC窗口和控件大小等比例变化
  3. [tools]google神器浏览器下载
  4. Redis主从同步介绍
  5. Oracle查询出最最近一次的一条记录
  6. Java.lang.RuntimeException: Unable to instantiate activity ComponentInfo
  7. centos 6.5 安装jenkins
  8. c语言学习之基础知识点介绍(八):函数的基本用法
  9. SpringMVC过程中@RequestBody接收Json的问题 总是报415
  10. LeetCode 437. Path Sum III (路径之和之三)
  11. Python学习(二十二)—— 前端基础之BOM和DOM
  12. 【杭电OJ3938】【离线+并查集】
  13. git 拉取某个分支到本地
  14. liunx 安装maven
  15. C 指针使用误区
  16. FTP服务器文件上传的代码实现
  17. WPF简单模拟QQ登录背景动画(转)
  18. PHP 获取指定日期的星期几的方法
  19. BZOJ3874:[AHOI2014&amp;JSOI2014]宅男计划(爬山法)
  20. 【luogu P3275 [SCOI2011]糖果】 题解

热门文章

  1. spark-submit python 程序,&quot;/home/.python-eggs&quot; permission denied 问题解决
  2. SQL Server 的通用分页显示存储过程(转载)
  3. Struts 2 访问Servlet API的方式
  4. [Processing] 弹球
  5. ubuntu HackRF One相关环境搭建
  6. 从零系列--开发npm包(二)
  7. 用shell实现bat批处理的pause命令-追加改进
  8. centos7.6 安装nginx-1.14.2
  9. Linux(Contos7.5)环境搭建之JDK1.8安装(二)
  10. tty命令详解