C# 线程问题之争用条件

5fb6df8b042b3a2db6fd239a2a5e58d9.png

用多个线程编程并不容易。在启动访问相同数据的多个线程时,会间歇性地遇到难以发现的问题。如果使用任务、并行 LINQ 或 Parallel 类,也会遇到这些问题。为了避免这些问题,必须特别注意同步问题和多个线程可能发生的其他问题。下面探讨与线程相关的问题争用条件。

ThreadingIssues示例的代码使用了如下名称空间: 

System.Diagnostics 

System.Threading

System.Threading.Tasks

static System.Console

可以使用命令行参数启动 ThreadingIssues示例应用程序,来模拟争用条件。

e61bb77bfcdf2fa19c251d761ae5939f.png

如果两个或多个线程访问相同的对象,并且对共享状态的访问没有同步,就会出现争用条件。为了说明争用条件,下面的例子定义一个 StateObject 类,它包含一个 int 字段和一个 ChangeState() 方法。在 ChangeState() 方法的实现代码中,验证状态变量是否包含5。如果它包含,就递增其值。下一条语句是Trace.Assert,它立刻验证 state 现在是包含 6。

435633d7cf5f86fe63d5fc819a539f5c.png

在给包含 5 的变量递增了 1 后,可能认为该变量的值就是 6。但事实不一定是这样。例如,如果一个线程刚刚执行完  if(_state==5)语句,它就被其他线程抢占,调度器运行另一个线程。第二个线程现在进入 if 体,因为 state 的值仍是 5,所以将它递增到 6。第一个线程现在再次被调度,在下一条语句中,State 递增到 7。这时就发生了争用条件,并显示断言消息:

public class StateObject
{private int _state = 5;public void ChangeState(int loop) 
{if (_state == 5){_state++;If (_state != 6){Console.WriteLine($"Race conditon occurred after {loop} loops"); Trace.Fail("race condition");}}_state = 5;}
}

dab4e7fdd16f1484f8628ecda447f7c2.png

下面通过给任务定义一个方法来验证这一点。SampleTask 类的 RaceCondition()方法将一个 StateObject 类作参数。在一个无限while循环中,调用ChangeState() 方法。变量 i 仅用于显示断言消息中的循环次数。

public class SampleTask
{public void RaceCondition(object o){Trace.Assert(o is StateObject, "o must be of type StateObject"); StateObject state = o as StateObject;int i = 0; while (true){state.ChangeState(i++);}}
}

7c0c8d59d3ccb3c628ef99f828e8eb4a.png

在程序的 Main() 方法中,新建了一个 StateObject 对象,它由所有任务共享。通过使用传递给 Task 的 Run 方法的 lambda 表达式调用 RaceCondition 方法来创建 Task 对象。然后,主线程等待用户输入。但是,因为可能出现争用,所以程序很有可能在读取用户输入前就挂起:

public void RaceConditions()
{var state = new StateObject(); for (int i = 0; i < 2; i++){Task.Run(() => new SampleTask().RaceCondition(state));}
}

ffe0d217d9f36080599ed7619d409ddb.png

启动程序,就会出现争用条件。多久以后出现第一个争用条件要取决于系统以及将程序构建为发布版本还是调试版本。如果构建为发布版本,该问题的出现次数就会比较多,因为代码被优化了。如果系统中有多个 CPU 或使用双核/四核 CPU,其中多个线程可以同时运行,则该问题也会比单核 CPU 的出现次数多。在单核CPU中,因为线程调度是抢占式的,也会出现该问题,只是没有那么频繁。

b012637add9048cbc98478e5dcb53b34.png

在我的系统上运行程序时,显示在 85232 个循环后出现错误;在另一次运行程序时,显示在 70037 个循环后出现错误。多次启动应用程序,总是会得到不同的结果。

779e479743447bf505bb4f64c992543e.png

要避免该问题,可以锁定共享的对象。这可以在线程中完成:用下面的 lock 语句锁定在线程中共享的 state 变量。只有一个线程能在锁定块中处理共享的 state 对象。由于这个对象在所有的线程之间共享,因此,如果一个线程锁定了 state,另一个线程就必须等待该锁定的解除。一旦接受锁定,线程就拥有该锁定,直到该锁定块的末尾才解除锁定。如果改变 state 变量引用的对象的每个线程都使用一个锁定,就不会出现争用条件。

public class SampleTask
{public void RaceCondition(object o){Trace.Assert(o is StateObject, "o must be of type StateObject"); StateObject state = o as StateObject; int t = 0;while (true){lock (state) // no race condition with this lock{state.ChangeState(i++);}}}
}

7b08a40ec02a1ced76ef8f5b14f5df19.png

注意:

在下载的示例代码中,需要取消锁定语句的注释,才能解决争用条件的问题。

95159ac242eb50dc0efce420bd6afaf0.png

在使用共享对象时,除了进行锁定之外,还可以将共享对象设置为线程安全的对象。在下面的代码中, ChangeState() 方法包含一条 lock 语句。由于不能锁定 state 变量本身(只有引用类型才能用于锁定),因此定义一个object 类型的变量 sync,将它用于lock 语句。如果每次 state 的值更改时,都使用同一个同步对象来锁定,就不会出现争用条件。

public class StateObject
{private int state = 5;private _object sync = new object(); public void ChangeState(int loop) {lock (_sync){if (_state == 5){_state++;if (_state != 6){Console.WriteLine($"Race condition occured after {loop} loops");Trace.Fail($"race condition at {loop}");}}_state = 5;}}}

a2f01648c48d9948289de1324ff10939.png

30c8844a69a63a166ca2bf1ec6c0a0aa.png

 微信公众号 

Dotnet讲堂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289799.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BFS HDOJ 2102 A计划

题目传送门 题意:中文题面 分析:双层BFS,之前写过类似的题.总结坑点: 1.步数小于等于T都是YES  2. 传送门的另一侧还是传送门或者墙都会死  3. 走到传送门也需要一步 #include <bits/stdc.h> using namespace std;char maze[2][11][11]; int dx[4] {-1, 1, 0, 0}; i…

MVC 之 Partial View 用法

Partial View 顾名思义就是Html代码片段&#xff0c;因此可以用Partial View 把部分的Html或显示逻辑包装起来&#xff0c;方便多次使用。Partial View 需要放在Views/Shared 目录下&#xff0c;任何Controlller 下的Action 或 View 都可以载入。如何载入Partial View?MVC 的 …

Matlab 7.1安装及打不开问题解决

一、安装方法 1、解压[MATLAB.V7.1.Windows版本].MATLAB.V7.1.R14.SP3.CD1.iso,双击setup进行安装,输入用户名,单位,找到crack下的PLP number.txt文件夹的PLP序列,复制粘贴。 2、安装过程中选择[MATLAB.V7.1.Windows版本].MATLAB.V7.1.R14.SP3.CD2和[MATLAB.V7.1.…

Android之在linux终端执行shell脚本直接打印当前运行app的日志

1、问题 我们一般很多时候会需要在ubuntu终端上打印当前运行app的日志,我们一般常见的做法是 1)、获取包名 打开当前运行的app,然后输入如下命令,然后在第一行TASK后面的就可以看到包名 adb shell dumpsys activity top 2)、我们的终端安装了pidcat.py脚本,然后执行如下…

尾调用优化 java_为什么JVM仍然不支持尾调用优化?

拉丁的传说也许您已经知道这一点&#xff0c;但是这个功能并不像听起来那么简单&#xff0c;因为Java语言实际上将堆栈跟踪暴露给程序员。考虑以下程序&#xff1a;public class Test {public static String f() {String s Math.random() > .5 ? f() : g();return s;}publ…

【AngularJS】—— 2 初识AngularJs(续)

前一篇了解了AngularJS的一些简单的使用&#xff0c;这里继续跟着w3c学习一下剩下的内容。 本篇根据w3cschool.cc继续学习AngularJS剩余的内容&#xff0c;包括&#xff1a; 1 事件 2 模块 3 表单 4 数据验证 5 bootstrap CSS风格 6 include包含其他页面 7 应用程序 8 参考手册…

08_drain a node on the swarm

在之前的小节&#xff0c;所有的节点的状态都是运行着的可用状态。swarm manager 可以分配任务给任意可用的节点。有时候&#xff0c;你可能需要对某台服务器进行维护&#xff0c;你需要配置某个节点为drain状态&#xff0c;即排干该节点上面的所有运行的容器。drain状态可以防…

特斯拉为何使用.NET 技术栈?

【精选转载】| 来源/知乎在知乎上有一个帖子非常热闹&#xff1a;“为何特使拉使用.net core技术栈 而不用 java&#xff1f;”1回答1&#xff1a;Kasim作者&#xff1a;Kasim链接&#xff1a;https://www.zhihu.com/question/496204534/answer/2269157872这题我熟啊&#xff0…

java之DocumentBuilderFactory解析xml

1、About documentBuilderFactory API description 1&#xff09;、 javax.xml.parsers 包DocumentBuilderFactory创建DOM模式的解析器对象, DocumentBuilderFactory是抽象工厂类&#xff0c;不能直接实例化&#xff0c;但是有newInstance方法 2&#xff09;、DocumentBuilderF…

java jdk实现快速排序_Java实现快速排序过程分析

快速排序过程没有既不浪费空间又可以快一点的排序算法呢&#xff1f;那就是“快速排序”&#xff01;光听这个名字是不是就觉得很高端呢。假设我们现在对“52 39 67 95 70 8 2552”这个8个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了&#xff0c…

深入理解计算机系统读书笔记

由于这本书的前半部分习题大多是相关计算和简单汇编代码编写&#xff0c;所以当时都是在稿纸上练习的&#xff0c;不过现在那些稿纸似乎也不见了: ( 所以现在仅有后半部分的课后习题代码以及示例练习代码&#xff08;家里作业习题当时并没有做&#xff0c;准备阅读第二遍时再做…

Blazor University (6)组件 — 组件事件

原文链接&#xff1a;https://blazor-university.com/components/component-events/组件事件源代码[1]EventCallback<T> 类是一个特殊的 Blazor 类&#xff0c;可以作为参数公开&#xff0c;以便组件可以在发生感兴趣的事情时轻松通知使用者。一旦声明了 EventCallback&l…

JavaScript匿名函数以及在循环中的匿名函数

一 历史 JavaScript其实是一门奇异的语言&#xff0c;TA的一大特性是没有块级作用域 for(var i0;i<10;i){} console.log(i)大家猜测下值是多少&#xff1f;答案是 10&#xff0c; 虽然我们在一个块内申明了变量&#xff0c;但i却是在全范围内起作用的&#xff0c;所以就引入…

Linux内核笔记--内存管理之用户态进程内存分配

内核版本&#xff1a;linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作&#xff0c;内存分配是其中非常重要的一环&#xff0c;作为一个linux程序员必然会想要知道这个过程到底是怎么样的&#xff0c;内核源码会告诉你这一切。 线性区 一个可执行程序&#xff…

Android之javax.net.ssl.SSLPeerUnverifiedException: Hostname ip not verified:解决办法

1、问题 用HttpURLConnection去请求的时候抛了下面的异常 HttpRequest$HttpRequestException: javax.net.ssl.SSLPeerUnverifiedException: Hostname ip not verified: 2、分析和解决 从异常来看是因为SSL协议握手的过程中,这个服务度地址的证书没有被证实,被信任。 clien…

php 字符串进行计算_怎么在php中利用eval对字符串格式进行计算

怎么在php中利用eval对字符串格式进行计算发布时间&#xff1a;2020-12-16 16:42:57来源&#xff1a;亿速云阅读&#xff1a;101作者&#xff1a;Leah本篇文章给大家分享的是有关怎么在php中利用eval对字符串格式进行计算&#xff0c;小编觉得挺实用的&#xff0c;因此分享给大…

Xamarin效果第十四篇之玩耍GIS

最近再次拾起Xamarin然后也实现了祖传PLC控制和弹窗配置;这不又一次勾起来我想基于他玩玩原来一直玩耍的GIS,毕竟咱前面一直玩耍二维和三维的GIS相关的知识点;有兴趣的小伙伴可以翻翻我的历史文章;趁着激情满满;来看看最终咱实现的加载高德平面地图效果(有水印):再者就是满足群…

Android下载apk异常java.net.SocketTimeoutException: timeout解决办法

1、问题 实现下载apk的时候&#xff0c;抛出下面异常 java.net.SocketTimeoutException: timeout 2、分析 很明显是socket超时了&#xff0c;由于我的wifi网络比较慢&#xff0c;设置的超时时间可能短了。 在写入由 GetRequestStream 方法返回的流时&#xff0c;或在读取由…

SQL SERVER两种分页的存储过程介绍

由于现在很多的企业招聘的笔试都会让来招聘的写一个分页的存储过程,有的企业甚至要求应聘者用两种方式实现分页,如果没有在实际项目中使用过分页,那么很多的应聘者都会出现一定的问题,下面介绍两种分页的方法。 一、 以学生表为例,在数据库中有一个Student表,字段有 …

Java Socke 探究

Java中的Socket可以分为普通Socket和NioSocket两种。 普通Socket的用法 Java中的网络通信是通过Socket实现的&#xff0c;Socket分为ServerSocket和Socket两大类&#xff0c;ServerSocket用于服务端&#xff0c;可以通过accept方法监听请求&#xff0c;监听到请求后返回Socket&…