【C#】线程问题

多线程编程对很多程序员来说并不容易,在启动访问相同数据的多个线程时,会间歇性地遇到难以发现的问题。如果使用任务、并行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=5;
    public void ChangeState(int loop){
        if(state==5){
            state++;
            Trace.Assert(state==6,"发生争用"+loop+"  loops");
        }
        state=5;
    }
}

下面通过给任务定义一个方法来验证这一点,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=0;
        while(true){
            state.ChangeState(i++);
        }
    }
}

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

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

  

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

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

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

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

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

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=0;
        while(true){
            lock(s1){
                lock(s2){
                    s1.ChangeState(i);
                    s2.ChangeState(i++);
                    Console.WriteLine("{0}",i);
                }
            }
        }
    }

    public void Deadlock2()
    {
        int i=0;
        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的解锁,那么会造成两个线程都在等待,只要锁定块没有结束,就不会解锁,结果就是造成了死锁。

时间: 2024-10-11 21:15:59

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

各科基础详实

一. Java基础部分 1. JAVA的基本数据类型有哪些 ?  String 是不是基本数据类型 ? 2. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 3. Java有没有goto? 7 4. 说说&和&&的区别. 7 5. 在JAVA中如何跳出当前的多重嵌套循环? 7 6. switch语句能否作用在byte上,能否作用在long上,能否作用在String上? 8 7. short s1 = 1; s1 = s1 + 1;有什么

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

201709018工作日记--线程状态的转换

先来张图: 线程在一定条件下,状态会发生变化: 1.新建状态(New):新创建了一个线程对象 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权. 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码. 4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行.直到线程进入就绪状态,才有机会转到运行状态.阻塞的情况分三种:   (一).等

POSIX 线程详解(经典必看)

总共三部分: 第一部分:POSIX 线程详解                                   Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  2000 年 7 月 01 日 第二部分:通用线程:POSIX 线程详解,第 2部分       Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  20

线程的控制和线程池

一.WaitHandle: ”.Net 中提供了一些线程间更自由通讯的工具,他们提供了通过"信号"进行通讯的机制 可以通过ManualResetEvent,AutoResetEvent(他是在开门并且一个 WaitOne 通过后自动关门)来进行线程间的通讯 waitOne:    等待开门 Set:           开门 Reset:       关门 static void Main(string[] args) { ManualResetEvent mre = new Manu

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

线程高级

例题,哲学家用餐: 在一张餐桌上坐着五个哲学家,但是却只有五根筷子,每个哲学家只有一根筷子,所以当一个哲学家要夹菜的时候需要用他旁边那个哲学家的筷子,被拿走筷子的哲学家则只能等待那个哲学家吃完放下筷子他才能夹菜. 示意图:  设计思路: 首先编写一个筷子类,每根筷子都是一个对象,这个类里有拿起.放下两个方法:当一个哲学家线程来调用拿起方法时,下一个哲学家线程就要进入等待状态,然后这个哲学家线程调用放下方法后,就激活那个等待中的哲学家线程,以此循环,轮流使用筷子. 代码示例:  接着设计哲学家类,

缓冲池,线程池,连接池

SSH:[email protected]:unbelievableme/object-pool.git   HTTPS:https://github.com/unbelievableme/object-pool.git 缓冲池 设计要点:包含三个队列:空缓冲队列(emq),装满输入数据的输入的队列(inq),装满输出数据的输出队列(outq),输入程序包括收容输入(hin),提取输入(sin),输出程序包括收容输出(hout)和提取输出(sout). 注意点:输入程序和输出程序会对缓冲区并发访

能够在子线程绘画的View SurfaceView

转载请注明出处:王亟亟的大牛之路 近期两天都没有写文章,一方面是自己在看书.一方面不知道写什么,本来昨天想写Glide或者RxAndroid的东西结果公司的"狗屎"网怎么都刷不好Gradle我也是无语了(FQ也没用).准备今天背着笔记本 回家搞.真是服了.. 抱怨的话不说了,来看下这一篇要讲的主角 SurfaceView,关于SurfaceView的文章事实上在别的一些网站上也有,由于我之前没写过,所以也就一直没整这部分的内容(别人写的好坏反正找好的点自己吸收吧,嘿嘿) 问题:Surf

java线程安全问题之静态变量、实例变量、局部变量

Java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同<java并发编程实践>中所说: 写道 给线程安全下定义比较困难.存在很多种定义,如:"一个类在可以被多个线程安全调用时就是线程安全的". 此处不赘述了,首先给出静态变量.实例变量.局部变量在多线程环境下的线程安全问题结论,然后用示例验证,请大家擦亮眼睛,有错必究,否则误人子弟! 静态变量:线程非安全. 静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态