多线程编程对很多程序员来说并不容易,在启动访问相同数据的多个线程时,会间歇性地遇到难以发现的问题。如果使用任务、并行LINQ或Parallel类,也会遇到这些问题。为了避免这一系列问题,开发程序中必须注意同步问题和多个线程可能发生的其它问题。下面我们看一下争用条件和死锁。

一、争用条件

  如果两个或多个线程访问相同的对象,或者访问不同步的共享状态(例如EF的实体),就会出现争用条件。为了说明争用条件,我们定义一个StateObject类,它包含一个int字段和一个ChangeState()方法。在该方法的实现代码中,验证状态变量是否包含5,。如果包含,就递增其值。然后用Trace.Assert方法来验证state是否包含6.在给包含5的变量递增了1后,可能希望变量的值是6,。但是事实却不一定是这样的。例如,如果一个线程刚刚执行完If(state==5)这一句代码,它就被其它线程调用,调度器运行另一个线程。第二个线程刚进入if语句,因为state的值仍是5,所以将它递增为6.现在,线程1再次被调度,那么结果state就变成了7,这时就发生的争用条件。

public class StateObject
{
private int state=;
public void ChangeState(int loop){
if(state==){
state++;
Trace.Assert(state==,"发生争用"+loop+" loops");
}
state=;
}
}

下面通过给任务定义一个方法来验证这一点,SameTask类的RaceCondition()方法经一个StateObject类作为参数。在一个无限while循环中,调用其方法。变量i仅用于标示循环次数。

public class SampleTask
{
public void RaceCondition(object o){
Trace.Assert(o is StateObject,"o 必须是 StateObject类型");
StateObject state=o as StateObject;
int i=;
while(true){
state.ChangeState(i++);
}
}
}

下面我们在Main方法中,新建一个StateObject对象,它由所有任务共享。我们看一下代码

static void Main()
{
var state=new StateObject();
for(int i=;i<;i++){
Task.Factory.StartNew(new SampleTask().RaceCondition,state);
}
Thread.Sleep();
}

  

  运行程序,我们会看到错误提示,多次启动程序,会得到不同的结果,那么我们如何避免类似的问题呢,我们可以锁定共享的对象,这可以在线程中完成:用下面的lock语句锁定在线程中共享的state变量。只有一个线程能在锁定块中处理共享的对象。由于这个对象在所有的线程之间共享,因此如果一个线程锁定了改对象,那么其他线程就必须等待改锁的解除。一旦接受锁定,线程就拥有该锁定,直到改锁定块的你、末尾才解除锁定。

public class SampleTask
{
public void RaceCondition(object o){
Trace.Assert(o is StateObject,"o 必须是 StateObject类型");
StateObject state=o as StateObject;
int i=;
while(true){
state.ChangeState(i++);
lock(state)
{
state.ChangeState(i++);
}
}
}
}

  在使用共享对象时,除了进行锁定外,还可以将共享对象设置为线程安全的对象。其中ChangeState()方法包含了lock语句,由于不能锁定state变量本身(只有引用类型才能用于锁定),因此定义一个object类型的变量,将它用于lock语句。

public class StateObject
{
private int state=;
private object o=new object();
public void ChangeState(int loop){
lock(o){
if(state==){
state++;
Trace.Assert(state==,"发生争用"+loop+" loops");
}
state=;
}
}
}

二、死锁
  过多的锁定也会有问题,在死锁中,至少有两个线程被挂起,并等待对象解除锁定。由于两个线程都在等待对方,就出现了死锁,那么后面线程将无限期等待下去。
下面我们看一个死锁的例子,我们创建两个任务,

var state1=new StateObject();
var state2=new StateObject();
Task.Factory.StartNew(new SampleTask(state1,state2).Deadlock1);
Task.Factory.StartNew(new SampleTask(state1,state2).Deadlock1); public class SampleThread
{
private StateObject s1;
private StateObject s2;
public SampleThread(StateObject s1,StateObject s2)
{
this.s1=s1;
this.s2=s2;
} public void Deadlock1()
{
int i=;
while(true){
lock(s1){
lock(s2){
s1.ChangeState(i);
s2.ChangeState(i++);
Console.WriteLine("{0}",i);
}
}
}
} public void Deadlock2()
{
int i=;
while(true){
lock(s2){
lock(s1){
s1.ChangeState(i);
s2.ChangeState(i++);
Console.WriteLine("{0}",i);
}
}
}
}
}

  Deadlock1()和DeadLock2()方法现在改变两个对象s1、s2的状态,这容易造成死锁,前一个方法先锁定s1,接着锁定s2,而两一个则相反,现在有可能前者s1的锁定被解除,出现一次线程切换,Deadlock2方法开始运行,并锁定s2,那么第二个线程现在等待s1锁定的解锁,因为它需要等待,所以线程调度器再次调度第一个线程,但第一个线程在等待s2的解锁,那么会造成两个线程都在等待,只要锁定块没有结束,就不会解锁,结果就是造成了死锁。

【C#】线程问题的更多相关文章

  1. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  2. [高并发]Java高并发编程系列开山篇--线程实现

    Java是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相 ...

  3. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  4. Java 线程

    线程:线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程.线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源.它与父进程的其他线程共享该进程的所有资 ...

  5. C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 template <class T> class sing ...

  6. 记一次tomcat线程创建异常调优:unable to create new native thread

    测试在进行一次性能测试的时候发现并发300个请求时出现了下面的异常: HTTP Status 500 - Handler processing failed; nested exception is ...

  7. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  8. C#多线程之线程池篇3

    在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...

  9. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

  10. C#多线程之线程池篇1

    在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...

随机推荐

  1. 金牌架构师:我们是这样设计APP数据统计产品的

    前言:近期,智能大数据服务商“个推”推出了应用统计产品“个数”,今天我们就和大家来谈一谈个数实时统计与AI数据智能平台整合架构设计. 很多人可能好奇,拥有数百亿SDK的个推,专注消息推送服务多年,现在 ...

  2. 前端PHP入门-018-内置函数之文件包含函数

    在实际开发中,常常需要把程序中的公用代码放到一个文件中,使用这些代码的文件只需要包含这个文件即可.这种方法有助于提高代码的重用性,给代码的编写与维护带来很大的便利. 在PHP中, 有require.r ...

  3. [DeeplearningAI笔记]序列模型1.10-1.12LSTM/BRNN/DeepRNN

    5.1循环序列模型 觉得有用的话,欢迎一起讨论相互学习~Follow Me 1.10长短期记忆网络(Long short term memory)LSTM Hochreiter S, Schmidhu ...

  4. 2017北京国庆刷题Day4 afternoon

    期望得分:100+100+0=200 实际得分:5+0+0=5 每加入一个数,x的因数位置++ 注意:根号x枚举时,如果x是完全平方数,根号x会重复累计2次,要减去 考场上没减,5分 /(ㄒoㄒ)/~ ...

  5. NOIP模拟赛11

    T1 [HAOI2016]放棋子 https://daniu.luogu.org/problem/show?pid=3182 障碍交换行不影响 所以第i列有障碍的行换到第i行 然后错排公式 本校自测要 ...

  6. GYM 101128 G.Game of Cards(博弈论) 或者 UVALIVE 7278

    题目链接:http://codeforces.com/gym/101128/my 如果可以,就看这个人的代码吧,我还不是很懂唉:http://blog.csdn.net/loy_184548/arti ...

  7. HDP安全之集成kerberos/LDAP、ranger(knox自带LDAP)

    ----------------------目录导航见左上角------------------------------- 环境 HDP 3.0.1.0 (已有) JDK   1.8.0_91 (已有 ...

  8. 【计蒜客】是男人就过 8 题--Pony.AI 题 A. A String Game 后缀自动机+SG函数

    [题目]A. A String Game [题意]给定目标串S和n个子串Ti,Alice和Bob轮流选择一个子串操作,必须且只能在子串末尾添加一个字符使得新串也是S的子串,不能操作即输,求胜利者.|S ...

  9. Coursera在线学习---第三节.归一化处理(Normalize)

    一.归一化(也说标准化)作用 1)将有量纲特征转化为无量纲特征 2)能够加快收敛(主要指梯度下降法时) 二.Octave中计算          mean(A)   求解矩阵中每一列的均值 std(A ...

  10. 五. Jmeter--HTTP Cookie Manager

    1. 添加HTTP Cookie Manager 2.添加登录login http,request info 和 HTTP Header Manager 中的信息是从fiddler中拿的, 至于hea ...