前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:

lock(expression) statement_block

expression代表你希望跟踪的对象,通常是对象引用。
    如果你想保护一个类的实例,一般地,你可以使用this;
    如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

下面是一个使用lock关键字的典型例子,在注释里说明了lock关键字的用法和用途。
示例如下:

(一并提供有关C#中Random类的使用方法)

using System;
using System.Threading;

namespace ThreadSimple
{
    internal class Account
     {
        int balance;
         Random r = new Random();
        
        internal Account(int initial)
         {
             balance = initial;
         }

internal int Withdraw(int amount)
         {
            if (balance < 0)
             {
                //如果balance小于0则抛出异常
                throw new Exception("Negative Balance");
             }
            //下面的代码保证在当前线程修改balance的值完成之前
            //不会有其他线程也执行这段代码来修改balance的值
            //因此,balance的值是不可能小于0 的
            lock (this)
             {
                 Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);
                //如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
                //另外一个线程却执行了balance=balance-amount修改了balance的值
                //而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
                //但是,这个线程却继续执行balance=balance-amount,所以导致balance可能小于0
                if (balance >= amount)
                 {
                     Thread.Sleep(5);
                     balance = balance - amount;
                    return amount;
                 }
                else
                 {
                    return 0; // transaction rejected
                   }
             }
         }
        internal void DoTransactions()
         {
            for (int i = 0; i < 100; i++)
             Withdraw(r.Next(-50, 100));
         }
     }

internal class Test
     {
        static internal Thread[] threads = new Thread[10];
        public static void Main()
         {
             Account acc = new Account (0);
            for (int i = 0; i < 10; i++)
             {
                 Thread t = new Thread(new ThreadStart(acc.DoTransactions));
                 threads[i] = t;
             }
            for (int i = 0; i < 10; i++)
                 threads[i].Name=i.ToString();
            for (int i = 0; i < 10; i++)
                 threads[i].Start();
             Console.ReadLine();
         }
     }
}


Monitor 类锁定一个对象

当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。

  Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。
Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形:

......
Queue oQueue=new Queue();
......
Monitor.Enter(oQueue);
......//现在oQueue对象只能被当前线程操纵了
Monitor.Exit(oQueue);//释放锁

如上所示,当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-catch-finally结构中的finally代码块里。

对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息:
其一是现在持有锁的线程的引用;
其二是一个预备队列,队列中保存了已经准备好获取锁的线程;
其三是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。

当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。

下面是一个展示如何使用lock关键字和Monitor类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。
这个例程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示(注释中介绍了该程序的精要所在)。

用到的系统命名空间如下:
using System;
using System.Threading;

首先,定义一个被操作的对象的类Cell,在这个类里,有两个方法:ReadFromCell()和WriteToCell。消费者线程将调用ReadFromCell()读取cellContents的内容并且显示出来,生产者进程将调用WriteToCell()方法向cellContents写入数据。

示例如下:

public class Cell
{
        int cellContents; // Cell对象里边的内容
        bool readerFlag = false; // 状态标志,为true时可以读取,为false则正在写入
        public int ReadFromCell( )
         {
            lock(this) // Lock关键字保证了什么,请大家看前面对lock的介绍
             {
                if (!readerFlag)//如果现在不可读取
                 {
                    try
                     {
                        //等待WriteToCell方法中调用Monitor.Pulse()方法
                         Monitor.Wait(this);
                     }
                    catch (SynchronizationLockException e)
                     {
                         Console.WriteLine(e);
                     }
                    catch (ThreadInterruptedException e)
                     {
                         Console.WriteLine(e);
                     }
                 }
                 Console.WriteLine("Consume: {0}",cellContents);
                 readerFlag = false;
                //重置readerFlag标志,表示消费行为已经完成
                 Monitor.Pulse(this);
                //通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
             }
            return cellContents;
         }
    
        public void WriteToCell(int n)
         {
            lock(this)
             {
                if (readerFlag)
                 {
                    try
                     {
                         Monitor.Wait(this);
                     }
                    catch (SynchronizationLockException e)
                     {
                            //当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
                         Console.WriteLine(e);
                     }
                    catch (ThreadInterruptedException e)
                     {
                            //当线程在等待状态的时候中止
                         Console.WriteLine(e);
                     }
                 }
                 cellContents = n;
                 Console.WriteLine("Produce: {0}",cellContents);
                 readerFlag = true;
                 Monitor.Pulse(this);
                //通知另外一个线程中正在等待的ReadFromCell()方法
             }
         }
}

C#多线程学习(三) 生产者和消费者 1——解决线程间冲突的关键相关推荐

  1. C#多线程学习(三) 生产者和消费者

    C#多线程学习(三) 生产者和消费者 原文链接:http://kb.cnblogs.com/page/42530/ 本系列文章导航 C#多线程学习(一) 多线程的相关概念 C#多线程学习(二) 如何操 ...

  2. C#多线程学习(三) 生产者和消费者 (转载系列)——继续搜索引擎研究

    前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生. ...

  3. [转]C#多线程学习(三) 生产者和消费者

    前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生. ...

  4. C#多线程学习(三) 生产者和消费者 2

    下面定义生产者类 CellProd 和消费者类 CellCons ,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口. pu ...

  5. Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)

    条件变量 条件变量本身不是锁!但它也可以造成线程阻塞.通常与互斥锁配合使用.给多线程提供一个会合的场所. 主要应用函数: pthread_cond_init 函数 pthread_cond_destr ...

  6. Java多线程学习三:有哪几种实现生产者消费者模式的方法

    我们先来看看什么是生产者消费者模式,生产者消费者模式是程序设计中非常常见的一种设计模式,被广泛运用在解耦.消息队列等场景.在现实世界中,我们把生产商品的一方称为生产者,把消费商品的一方称为消费者,有时 ...

  7. java多线程生产者与消费者案例_多线程操作实例——生产者与消费者

    面对多线程学习生产者与消费者是最基本的实例 对于java后端开发的人员必须要掌握,还有考研考试计算机操作系统的同鞋. 下面是三个实例对于生产者与消费者的的例子,层层递进,逐步解决问题. 问题:生产者- ...

  8. JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池...

    /*** 多线程共享数据* 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行.* 多线程共享数据的安全问题,使用同步解决.* 线程同步两种方法: ...

  9. Python爬虫的经典多线程方式,生产者与消费者模型

    在之前的文章当中我们曾经说道,在多线程并发的场景当中,如果我们需要感知线程之间的状态,交换线程之间的信息是一件非常复杂和困难的事情.因为我们没有更高级的系统权限,也没有上帝视角,很难知道目前运行的状态 ...

最新文章

  1. vue打印props的值_vue中props传值方法
  2. linux中的for命令
  3. java递归深度克隆_递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。...
  4. 要学好linux运维请从排版画图开始
  5. Hive将查询结果保存到另一张表中
  6. (3)[wp7数据存储] WP7 IsolatedStorage系列篇——通过XmlSerializer读写XML文件 [复制链接]...
  7. 动手动脑及课后实践3
  8. 【大数据】Hbase如何批量删除指定数据
  9. truncate table 与delete table区别
  10. 实现类似黑客帝国的字符流特效屏保
  11. 电脑网线,电脑网线主要分类
  12. 物联网工业串口转WiFi模块 无线路由WiFi模块的选型
  13. .net Core 3.1 项目打包部署到Windows服务
  14. 联通家庭宽带光猫DDNS设置
  15. 一致性检验Kappa 与 混淆矩阵
  16. uniapp canvas 刮刮乐
  17. 2017年国庆随笔----- 心理学随笔
  18. 你心有喜欢的明星吗??
  19. ❤️UI自动化轻松解决微信手工群发消息的烦恼❤️
  20. 【视频分享】尚硅谷HTML5前端视频_Vue核心技术视频

热门文章

  1. obs多推流地址_什么都比不上动手能力,OBS 推流实践小记
  2. 步步为营 .NET 设计模式学习笔记 九、Command(命令模式)
  3. 资深架构师手写教你使用Docker安装RabbitMQ(SpringCloud)
  4. 【bug】掘金md文本解析器bug
  5. Confluence 6 Windows 中以服务方式自动重启修改运行服务的用户
  6. 使用 OpCache 提升 PHP 5.5+ 程序性能
  7. 【数据结构笔记27】树习题:完全二叉搜索树(Complete Binary Search Tree)
  8. ajax修改属性后如何遍历,Ajax遍历jSon后对每一条数据进行相应的修改和删除(代码分享)...
  9. 计算机论文与护理,快速护理论文范文
  10. linux ssh 推送文件_通过SSH实现Windows与linux之间传输文件