C# 线程(四):生产者和消费者
From : http://kb.cnblogs.com/page/42530/
前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。
C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:
lock(expression) statement_block
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 < )
{
//如果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();
balance = balance - amount;
return amount;
}
else
{
return ; // transaction rejected
}
}
}
internal void DoTransactions()
{
for (int i = ; i < ; i++)
Withdraw(r.Next(-, ));
}
}
internal class Test
{
static internal Thread[] threads = new Thread[];
public static void Main()
{
Account acc = new Account ();
for (int i = ; i < ; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
threads[i] = t;
}
for (int i = ; i < ; i++)
threads[i].Name=i.ToString();
for (int i = ; i < ; 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()方法
}
}
}
下面定义生产者类 CellProd 和消费者类 CellCons ,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口。
public class CellProd
{
Cell cell; // 被操作的Cell对象
int quantity = ; // 生产者生产次数,初始化为1 public CellProd(Cell box, int request)
{
//构造函数
cell = box;
quantity = request;
}
public void ThreadRun( )
{
for(int looper=; looper<=quantity; looper++)
cell.WriteToCell(looper); //生产者向操作对象写入信息
}
} public class CellCons
{
Cell cell;
int quantity = ; public CellCons(Cell box, int request)
{
//构造函数
cell = box;
quantity = request;
}
public void ThreadRun( )
{
int valReturned;
for(int looper=; looper<=quantity; looper++)
valReturned=cell.ReadFromCell( );//消费者从操作对象中读取信息
}
}
然后在下面这个类MonitorSample的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。
public class MonitorSample
{
public static void Main(String[] args)
{
int result = ; //一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
Cell cell = new Cell( ); //下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
CellProd prod = new CellProd(cell, );
CellCons cons = new CellCons(cell, ); Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
//生产者线程和消费者线程都已经被创建,但是没有开始执行
try
{
producer.Start( );
consumer.Start( ); producer.Join( );
consumer.Join( );
Console.ReadLine();
}
catch (ThreadStateException e)
{
//当线程因为所处状态的原因而不能执行被请求的操作
Console.WriteLine(e);
result = ;
}
catch (ThreadInterruptedException e)
{
//当线程在等待状态的时候中止
Console.WriteLine(e);
result = ;
}
//尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
Environment.ExitCode = result;
}
}
在上面的例程中,同步是通过等待Monitor.Pulse()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Pulse)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Pulese()发出的“脉冲”。
它的执行结果很简单:
Produce: 1
Consume: 1
Produce: 2
Consume: 2
Produce: 3
Consume: 3
...
...
Produce: 20
Consume: 20
事实上,这个简单的例子已经帮助我们解决了多线程应用程序中可能出现的大问题,只要领悟了解决线程间冲突的基本方法,很容易把它应用到比较复杂的程序中去。
-------------->>>
C# 线程(四):生产者和消费者的更多相关文章
- java线程中生产者与消费者的问题
一.概念 生产者与消费者问题是一个金典的多线程协作的问题.生产者负责生产产品,并将产品存放到仓库:消费者从仓库中获取产品并消费.当仓库满时,生产者必须停止生产,直到仓库有位置存放产品:当仓库空时,消费 ...
- JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池
/** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两 ...
- Python-多线程及生产者与消费者
一.前置知识 1. 队列基础 如果不指定队列是什么,请自行查阅 在Python中,队列是最常用的线程间的通信方法,因为它是线程安全的 from queue import Queue # 创建队列 # ...
- 笔记-13-多线程 Thread方法 线程安全 生产者和消费者 死锁和阻塞 练习
题目1 编写程序,创建两个线程对象,一根线程循环输出"播放背景音乐",另一根线程循环输出"显示画面";要求: 1: 1个线程使用Runnable接口的匿名内部类 ...
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- 线程操作案例--生产者与消费者,Object类对线程的支持
本章目标 1)加深对线程同步的理解 2)了解Object类中对线程的支持方法. 实例 生产者不断生产,消费者不断消费产品. 生产者生产信息后将其放到一个区域中,之后消费者从区域中取出数据. 既然生产的 ...
- JAVA之旅(十五)——多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止
JAVA之旅(十五)--多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止 我们接着多线程讲 一.生产者和消费者 什么是生产者和消费者?我们 ...
- 多进程(了解):守护进程,互斥锁,信号量,进程Queue与线程queue(生产者与消费者模型)
一.守护进程 主进程创建守护进程,守护进程的主要的特征为:①守护进程会在主进程代码执行结束时立即终止:②守护进程内无法继续再开子进程,否则会抛出异常. 实例: from multiprocessing ...
- 第44天学习打卡(JUC 线程和进程 并发和并行 Lock锁 生产者和消费者问题 如何判断锁(8锁问题) 集合类不安全)
什么是JUC 1.java.util工具包 包 分类 业务:普通的线程代码 Thread Runnable 没有返回值.效率相比Callable相对较低 2.线程和进程 进程:一个程序.QQ.exe, ...
随机推荐
- PS4 的下载速度问题
折腾了好久了 AC68u路由自启动修改 hosts 问题,打算FQ另外改善 ps4 下载速度太慢问题. 后来看到几个dns, 直接修改后就速度超快,也不用在路由中添加了, 直接在 ps4 中网络设置中 ...
- tomcat监控脚本
工作所需,匆匆忙忙写了个监控tomcat的shell脚本,大概思路是这样的:先检测tomcat进程是否存在,如果不存在就启动,如果进程存在,检测页面返回码状态,如果是200就是正常,如果不是就重启. ...
- PayPal网站付款标准版(for PHP)
简单整理一下PHP项目整合PayPal支付功能. 一.表单的构建: <form method="post" name="form" action=&quo ...
- 转:redis常用命令
一 Redis介绍 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发 ...
- 给windows服务打包,并生成安装程序
一. 添加新建项目-->安装部署-->安装项目 二.安装程序上-->右键视图-->文件系统-->应用程序文件夹-->右键-->添加项目输出 选择做好的wind ...
- 12、Jsp加强/自定义标签/JavaBean
1 Jsp加强回顾 Jsp加强 1)Jsp的9大内置对象 request HttpServletRequet response HttpServletResponse config ...
- HDU 5950:Recursive sequence(矩阵快速幂)
http://acm.hdu.edu.cn/showproblem.php?pid=5950 题意:给出 a,b,n,递推出 f(n) = f(n-1) + f(n-2) * 2 + n ^ 4. f ...
- cocos2dx资源和脚本加密quick-lua3.3final
一.资源加密 版本号:Quick-Cocos2d-x 3.3 Final 调试工具:xCode 工程创建的时候选择的拷贝源码. 项目结构如图: 这个功能七月大神在很早之前就已经实现了,但是在3.3版本 ...
- RTC系统
http://blog.csdn.net/fanqipin/article/details/8089995 一. RTC及驱动简介 RTC即real time clock实时时钟,主要用于为操作系统提 ...
- android 代码整体回退
repo forall -c 'HAHA=`git log --before="3 days" -1 --pretty=format:"%H"`;git res ...