• 从公交塞车,看C#多线程问题(转)


       好久没写博客了,可能是因为最近工作太过于压抑的原因吧!有点颓废了.... 而且公司距离住处要坐公交将近40--50分钟(各个原因,纠结中ing...),提前一个半小时起床,居然还能迟到!因为距离公司前两站是个十字路口,每天能在哪里塞上30多分钟....眼看就要到公司了,一辆接一辆阻塞着..看着时间一分一秒的过去..心里不尽拔凉起来(迟到会扣钱的!)... 拔凉之余不禁让我想到C#中线程的同步异步.所以呈此博文,来谈谈我对C#中线程同步的理解,不当之处,请大家多多指点,在此先谢谢了!

        什么是线程同步? 多个线程同时运行时,可能会因为线程之间的逻辑关系而决定谁先执行,谁后执行, 这就是线程同步。

      (1)线程的优先级

              当线程之间争夺CPU时间片时,CPU是按照线程的优先级进行服务的。在C#应用程序中,线程有5个不同的优先级,由高到低分别是:Highest、AboveNormal、Normal 、BelowNormal和Lowest。创建线程时,如果不指定其优先级,则系统默认为Normal。

              如:Thread t = new Thread(MethodName);

              t.priority = ThreadPriority.AboveNormal;

              通过设置线程的优先级可以改变线程执行的顺序,所设置的优先级仅仅适用于这些线程所属的进程。(注:当把某个线程的优先级设置为Htghest时,系统上正在运行的其他线程都会终止,所以使用这个优先级的时候要特别小心。除非遇到“币需”马上处理的任务,否则不要使用这个优先级)。

        (2)线程同步

               多线程处理解决了吞吐量和响应速度的问题,但同时也带来了资源共享问题,如死锁和资源争用。多线程特别适用于需要不同的资源(如文件句柄和网络连接)的任务。为单个资源分配多个线程可能会导致同步问题,这种情况下,线程可能会北频繁阻止以等待其他线程,从而使用多线程的初衷背道而驰。

               所谓同步,是指多个线程之间存在先后执行顺序的关联关系。如果一个线程必须在另一个线程完成某个工作后才能继续执行,则必须考虑如何让其他保持同步,以确保在系统上同时运行多个线程而不会出现死锁或逻辑错误。

               为了解决同步问题,一般使用辅助线程执行不需要大量占用其他线程所使用的资源的耗时任务或时间要求紧迫的任务。但实际上,程序中的某些资源必须由多个线程访问。为了解决这些问题,System.Threading命名空间提供了多个用于同步线程的类。这些类包括Mutex,Monitor,Interlocked,AutoResetEvent.  但是实际应用程序中,我们使用最多的可能不是这些类,而是C#提供的lock语句。

       Lock语句

        为了在多线程应用程序中让同步变得简单,C#专门提供了一个lock语句。lock关键字能确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码段,则它将一直等待(阻塞),知道锁定的对象被释放以后才能进入临界区。

               

    private Object obj = new Object();
    .....
    lock(obj)
    {
    //临界区
    }

               举个例子相信大家会更明白,路人甲和路人乙要上厕所,刚好找到了一个公共厕所,杯具的是公共厕所里面只有一个位置,路人甲是会员(优先级高),先溜进去了,然后把门锁上(Lock)紧接着里面发出一阵阵巨响....(大家都懂的,最近食物不敢乱吃啊 - -!)。路人乙可着急了,捂着肚子,在外面打转,憋得面红耳赤!过了好一段时间,路人甲抽着香烟,吹着口哨,从厕所里面走出来(Lock解锁了),路人乙急忙钻进去,紧接着又是一阵巨响.....

                虽然这个例子举的有点不和谐,但相信大家已经弄明白Lock的作用了。

                值得注意的是:1、锁定的对象名(上面的obj),一般声明为Object类型,注意不要将其声明为值类型,对象名叫什么无所谓,只要符合对象命名原则就行。2、一定要将该Object类型的对象名声明为private(私有),不能将其声明为public(公共),否则将会使lock语句无法控制,从而引发一系列的问题。 (就像上面举例一样:漆黑不见五指的夜晚(没电),路人乙解开裤带,正准备蹲下时,一只手把路人乙的PP托住,喊道:有人!- -#。)3、处于临界区的代码不宜太多。如果在锁定和解锁期间处理的代码过多,由于某个线程执行临界区中的代码时,其他等待运行临界区中代码的线程都会处于阻塞状态,这样就可能会降低应用程序的性能。(路人乙会恨死路人甲的!)

                好了,闲话就说这么多,还是拿代码说事吧,说过随机取款的例子。

           (1)新建一个名为LockExample的Windows应用程序,放下一个listbox,一个button,界面如下:

                

                 

          (2)添加一个类:Account.cs。代码如下:

                

    class Account
        {
            private Object obj = new object();
            int balance;
            Random rd = new Random();
            Form1 form1;
     
            public Account(int initial,Form1 form1)
            {
                this.form1 = form1;
                this.balance = initial;
            }
     
            /// <summary>
            /// Withdraws the specified amount.
            /// </summary>
            /// <param name="amount">The amount.</param>
            /// <returns></returns>
            private int Withdraw(int amount)
            {
                if (balance < 0)
                {
                    form1.AddListBoxItem("余额" + balance + " 兄弟,你以为你这是信用卡啊!还钱吧!");
                }
     
                //将lock(lockedobj)这句话注视掉,看看会发生什么情况
                lock (obj)
                {
                    if (balance >= amount)
                    {
                        string str = Thread.CurrentThread.Name + "取款---";
                        str += string.Format("取款前余额:{0,-6}取款:{1,-6}",balance,amount);
                        balance = balance - amount;
                        str += "取款前余额:" + balance;
                        form1.AddListBoxItem(str);
                        return amount;
                    }
                    else
                    {
                        return 0;
                    }
                }
            }
     
            public void DoTransactions()
            {
                for (int i = 0; i < 100; i++)
                {
                    Withdraw(rd.Next(1,100));
                }
            }
        }

      (3)切换到Form1.cs代码编辑界面,写入一下代码:

    复制代码
    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();
    }

    private void btnLock_Click(object sender, EventArgs e)
    {
    lbLock.Items.Clear();
    Thread[] threads = new Thread[10];
    Account acc = new Account(1000,this);
    for (int i = 0; i < 10; i++)
    {
    Thread t = new Thread(acc.DoTransactions);
    t.Name = "线程" + i;
    threads[i] = t;
    }

    for (int i = 0; i < 10; i++)
    {
    threads[i].Start();
    }
    }

    delegate void AddListBoxItemDelegate(string str);
    public void AddListBoxItem(string str)
    {
    if (lbLock.InvokeRequired)
    {
    AddListBoxItemDelegate d = AddListBoxItem;
    lbLock.Invoke(d, str);
    }
    else
    {
    lbLock.Items.Add(str);
    }
    }
    }
    复制代码

      (4)按<F5>编译并运行,单击 “开始自动随机取款”按钮,观察线程执行后在listbox中添加的可能出现的内容,如图:

    (5) 将lock(obj)这条语句注视掉,再次运行程序,观察线程执行后在listbox中添加的可能出现的内容,如图:

    如图中线程6取款后余额已经是584了,但是在线程7取款时候 余额又变成了746.显然结果不正确。

    好了,对线程同步问题和解决同步问题的理解,就先写道这里,上述不当之处,希望大家多多指点,共同进步。


    作者:康忠鑫(Stephe
    n.Kang) 
    出处:http://www.cnblogs.com/axing/ 
    Q群:诚邀C#和.NET方面志同道合的朋友加入:CSharpAndDotNET 研发群——>13983671 
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    CMD文件分析
    #ifdef __cplusplus
    nginx查看post请求日志
    JNI常见错误1
    include、include_once与require、require_once区别
    在Windows下利用Eclipse调试FFmpeg
    在Windows下利用MinGW编译FFmpeg
    图文详解YUV420, yuv格式2
    yuv rgb 像素格式1
    libyuv 编译for ios
  • 原文地址:https://www.cnblogs.com/lianghong/p/7997445.html
Copyright © 2020-2023  润新知