• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C#教程 > C#多线程学习之(三)生产者和消费者用法分析

C#多线程学习之(三)生产者和消费者用法分析

作者:乐乐010 字体:[增加 减小] 来源:互联网 时间:2017-05-28

乐乐010 通过本文主要向大家介绍了C#多线程学习之(三)生产者和消费者用法分析等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

本文实例讲述了C#多线程学习之生产者和消费者用法。分享给大家供大家参考。具体实分析如下:

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

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

lock(expression) statement_block 
</div>

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

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

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

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();
  }
 }
}
</div>

Monitor 类锁定一个对象

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

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

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

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

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

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

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

用到的系统命名空间如下:

using System;
using System.Threading;
</div>

首先,定义一个被操作的对象的类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()方法
   }
  }
}
</div>

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

public class CellProd
{
 Cell cell; // 被操作的Cell对象
 int quantity = 1; // 生产者生产次数,初始化为1 
 public CellProd(Cell box, int request)
 {
  //构造函数
  cell = box; 
  quantity = request; 
 }
 public void ThreadRun( )
 {
  for(int looper=1; looper<=quantity; looper++)
  cell.WriteToCell(looper); //生产者向操作对象写入信息
 }
}
public class CellCons
{
 Cell cell; 
 int quantity = 1; 

 public CellCons(Cell box, int request)
 {
  //构造函数
  cell = box; 
  quantity = request; 
 }
 public void ThreadRun( )
 {
  int valReturned;
  for(int looper=1; looper<=quantity; looper++)
  valReturned=cell.ReadFromCell( );//消费者从操作对象中读取信息
 }
}
</div>

然后在下面这个类MonitorSample的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。

public class MonitorSample
{
 public static void Main(String[] args)
 {
  int result = 0; 
 //一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
  Cell cell = new Cell( ); 
  //下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
  CellProd prod = new CellProd(cell, 20); 
  CellCons cons = new CellCons(cell, 20);



 
分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

相关文章

  • 2017-05-28C#编程和Visual Studio使用技巧(下)
  • 2017-05-28C#特性-对象集合初始化器介绍
  • 2017-05-28C#实现自定义FTP操作封装类实例
  • 2017-05-28C# ComboBox的联动操作(三层架构)
  • 2017-05-28c# 随机函数的使用详解
  • 2017-05-28C#中判断某类型是否可以进行隐式类型转换
  • 2017-05-28VB.NET中Caching的使用方法
  • 2017-05-28C# Dynamic之:ExpandoObject,DynamicObject,DynamicMetaOb的应用(下)
  • 2017-05-28C#实现基于IE内核的简单浏览器完整实例
  • 2017-05-28C#调用SQLite的方法实例分析

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • 基于静态Singleton模式的使用介绍
    • C#实现按数据库邮件列表发送邮件的方法
    • C#写日志类实例
    • C#生成随机数实例
    • c# 图片加密解密的实例代码
    • C#利用正则判断输入是否为纯数字、容器类
    • C#实现更改MDI窗体背景颜色的方法
    • 利用WCF双工模式实现即时通讯
    • C#实现简易的加密、解密字符串工具类实例
    • 基于XSLT调试的相关问题

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有