现在有五个工人在果园摘水果,一次只能摘一个,摘下的水果放入一个框中,这种框最多只能装50个橘子,一共有两个这样的框。当一个工人装框时,其他工人不能在使用这个框。当两个框都装满了,工人只有等框里有剩余位置后,才能在摘果子。然后,有四个小孩来框里拿橘子,一次最多拿一个,并且当一个小孩在框前拿橘子时,其他小孩只能到另一个框拿橘子,如果两个框前都有小孩拿橘子,那么剩余要拿橘子的小孩只能等待。(这个题目是我自己编的,可能不是很准确)
现在要设计一个程序模拟这样一个过程。
 
分析:框是互斥资源,每次放橘子前 得判断有没有空闲得框,有就占住加锁,然后里面执行放橘子得方法。放完之后再解锁。框可以用队列表示。
工人和小孩可以用Task模拟。
 
这里需要两种锁,一种是放橘子得时候得一把Monitor锁,一种是当没有空闲得框后,加的AutoResetEvent锁。
当使用两把锁得时候,需要特别小心,稍不注意都会引发死锁。
 
Monitor锁再使用得时候,得用引用变量作为加锁得对象,不要用字符串和值变量。虽然再用值变量时,编译器不会报错,但是运行时,Enter会装箱,把值变量变为引用变量,但是再Exit时,依然是个值变量,这样Enter和Exit的锁变量就不是同一个变量,造成找不到锁的情况,就会抛出异常。
 
另外使用Monitor枷锁时,应该使用 try{}finally{}语句块,保证总是会被解锁,否则遇到异常,不执行解锁语句,就死锁了。
其实lock语句块就是Monitor的简便方法,内部使用的还是Monitor。
 
对于AutoResetEvent而言,可以暂停和唤醒线程,再不同线程可以相互唤醒和阻塞。这样就非常的灵活。其实推荐使用ManualResetEvent,因为比起AutoResetEvent,可以唤起多个线程,如果说小孩一次拿多个橘子,这种方式就比AutoResetEvent有优势,因AutoResetEvent只唤醒一个线程。
 
线程的同步还有其他方法,比如再数值上同步 有InterLock,其他的如信号量(Sema'phore)同步,CountDownEvent。
同步的应用,还可以是用进程间同步的方法,实现在一台主机上,每次只能启动一个相同的应用程序,这时可以使用Mutex。
 
避免资源在线程同步中互斥,还可以用 线程本地存储技术,ThreadLocal,例子:
详见:《C#本质论》第三版,第19章
下面直接看代码:
 internal class Program
{
//最多容纳50个橘子每个框
static readonly int MAX = 50;
//两个框
static List<ConcurrentQueue<Orange>> Queues =
new List<ConcurrentQueue<Orange>>();
//记录空闲的框
static List<int> QidxBags = new List<int>(); static int MaxO = 1000; //最多摘1000个橘子 static readonly object Sync = new object();
static readonly object Sync2 = new object();
//比起AutoResetEvent,可以唤起多个线程,如果说小孩一次拿多个橘子,而不是一个,
//这种方式比AutoResetEvent有优势,因为AutoResetEvent只唤醒一个线程。
static ManualResetEvent MResetEvent = new ManualResetEvent(false); static void Main(string[] args)
{
Queues.Add(new ConcurrentQueue<Orange>());
Queues.Add(new ConcurrentQueue<Orange>()); for (int i = 0; i < Queues.Count; i++)
{
QidxBags.Add(i);
}
TaskProduceAndComsummer();
Console.ReadKey();
} static int GetQueuesIdx()
{
int idx = -1; int count = QidxBags.Count; if (count > 0)
{
return count;
} return idx;
} static bool IsEmpty()
{
foreach (var item in Queues)
{
if (item.Count >0)
{
return false;
}
}
return true;
} static bool IsFull()
{
foreach (var item in Queues)
{
if (item.Count < MAX)
{
return false;
}
}
return true;
} static void TaskProduceAndComsummer()
{
for (int i = 0; i < 5; i++)
{
string name = "工人_" + (i + 1);
Task t = new Task(Produce, (object)(name)); t.Start();
} for (int i = 0; i < 3; i++)
{
string name = "小孩_" + (i + 1);
Task t = new Task(Consumer, (object)(name)); t.Start();
} }
static void Produce(object name)
{
while (true&&MaxO>0)
{
int count = -1;
int iPos = -1;
lock (Sync2)
{
count = GetQueuesIdx();
}
if (count > 0&&!IsFull())
{
bool refTaken = false; Monitor.Enter(Sync, ref refTaken);
bool isPut = false;
try
{ for (int i = 0; i < count; i++)
{ iPos = QidxBags[i];
var q = Queues[iPos]; if (q.Count < MAX)
{
QidxBags.Remove(iPos);
q.Enqueue(Orange.GetOrange());
MaxO -= 1;
Console.WriteLine(name + ":+摘了一个橘子,放入框【" + iPos + "】中");
Console.WriteLine("框一数量:{0},框二数量{1}", Queues[0].Count, Queues[1].Count);
isPut = true;
//唤醒小孩线程
MResetEvent.Set();
break;
} }
}
finally
{
if (refTaken)
{
if (iPos > -1)
{
QidxBags.Add(iPos);
} Monitor.Exit(Sync);
if (!isPut)
{
Console.WriteLine("满了");
} }
}
}
else
{
MResetEvent.WaitOne();
} }
} static void Consumer(object name)
{
while (true)
{
int count = GetQueuesIdx();
int iPos = -1;
if (count > 0&&!IsEmpty())
{
bool refTaken = false;
bool isPut = false;
Monitor.Enter(Sync, ref refTaken);
try
{
for (int i = 0; i < count; i++)
{ iPos = QidxBags[i];
var q = Queues[iPos]; if (q.Count >0)
{
QidxBags.Remove(iPos);
Orange o = null;
q.TryDequeue(out o);
Console.WriteLine(name + ":+拿了一个橘子,从框【" + iPos + "】中");
Console.WriteLine("框一数量:{0},框二数量{1}", Queues[0].Count, Queues[1].Count);
isPut = true;
//框有容量了,可以放了,所以唤醒被阻塞得工人线程
MResetEvent.Set();
break;
} }
}
finally
{
if (refTaken)
{
if (iPos > -1)
{
QidxBags.Add(iPos);
}
Monitor.Exit(Sync);
if (!isPut)
{ Console.WriteLine("都空了"); } }
}
}
else
{
MResetEvent.WaitOne();//阻塞
}
}
}
} public class Orange
{
public static Orange GetOrange()
{
Random rand = new Random();
int t = rand.Next(10, 20);
Thread.Sleep(t);
return new Orange();
}
}

部分结果:

线程同步介绍及 生产者消费者问题举例 C#版的更多相关文章

  1. 第三节: List类型的介绍、生产者消费者模式、发布订阅模式

    一. List类型基础 1.介绍 它是一个双向链表,支持左进.左出.右进.右出,所以它即可以充当队列使用,也可以充当栈使用. (1). 队列:先进先出, 可以利用List左进右出,或者右进左出(Lis ...

  2. java线程之多个生产者消费者

    温故一下上一节所学习的生产者消费者代码: 两个线程时: 通过标志位flag的if判断和同步函数互斥较好解决两个线程,一个生产者.一个消费者交替执行的功能 类名:ProducterConsumerDem ...

  3. python进阶:Python进程、线程、队列、生产者/消费者模式、协程

    一.进程和线程的基本理解 1.进程 程序是由指令和数据组成的,编译为二进制格式后在硬盘存储,程序启动的过程是将二进制数据加载进内存,这个启动了的程序就称作进程(可简单理解为进行中的程序).例如打开一个 ...

  4. java线程之多个生产者消费者2.0

    上一节中,通过while和notifyAll解决了多个生产者,消费者对共享资源的访问问题,现在开始升级 但是,仍然有改进之处,主要体现在两点: 1)使用新版本1.5开始后的锁Lock解决,目的将其全部 ...

  5. 基于线程池的线程管理(BlockingQueue生产者消费者方式)实例

    1.线程池管理类: public class ThreadPoolManager { private static ThreadPoolManager instance = new ThreadPoo ...

  6. 线程_FIFO队列实现生产者消费者

    import threading # 导入线程库 import time from queue import Queue # 队列 class Producer(threading.Thread): ...

  7. Ruby:线程实现经典的生产者消费者问题

    运行结果: ProAndCon 0 produced 1 produced consumed 0 2 produced 3 produced consumed 1 consumed 2 consume ...

  8. Python之多线程:线程互斥与线程同步

    一.锁在多线程中的使用:线程互斥 lock = threading.Lock()#创建一个锁对象 1.with lock: pass 和进程使用的方式相同   2.控制线程结束的时间 通过一个全局变量 ...

  9. 使用Win32 API实现生产者消费者线程同步

    使用win32 API创建线程,创建信号量用于线程的同步 创建信号量 语法例如以下 HANDLE semophore; semophore = CreateSemaphore(lpSemaphoreA ...

随机推荐

  1. JAVA获取当前日期时间所在周的周一和周日日期

    /** * 获取当前时间所在周的周一和周日的日期时间 * @return */ public static Map<String,String> getWeekDate() { Map&l ...

  2. 【LeetCode】366. Find Leaves of Binary Tree 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS 日期 题目地址:https://leetcod ...

  3. 【LeetCode】240. Search a 2D Matrix II 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  4. 【LeetCode】816. Ambiguous Coordinates 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.me/ 题目地址:https://leetcode.com/problems/ambiguous ...

  5. 「HAOI2016」找相同字符

    知识点: SA,线段树,广义 SAM 原题面 Loj Luogu 给定两字符串 \(S_1, S_2\),求出在两字符串中各取一个子串,使得这两个子串相同的方案数. 两方案不同当且仅当这两个子串中有一 ...

  6. spring练习,使用Eclipse搭建的Spring开发环境,属性注入通过构造方法方式实现,模拟用户的正常登录。

    相关 知识 >>> 相关 练习 >>> 实现要求: 使用Eclipse搭建的Spring开发环境,属性注入通过构造方法方式实现,模拟用户的正常登录.要求如下: 通过 ...

  7. Log4j2日志框架集成Slf4j日志门面

    1.说明 本文介绍使用日志门面Slf4j打印日志, 底层日志实现使用Log4j2框架, 方便以后切换底层日志实现, Log4j2可以替换成Logback等. 2.依赖管理 在pom.xml依赖管理中导 ...

  8. Springboot+Javamail实现邮件发送

    Springboot+Javamail实现邮件发送 使用的是spring-context-support-5.2.6.RELEASE.jar里的javamail javamail 官方文档:javam ...

  9. pip list 精确查找某一模块的方法

    1. 今天搜资料的时候get一项技能: pip list精确查找某一模块 命令如下: pip list | findstr "win32" (此处win32可以替换成任意想查找的模 ...

  10. 关于vue部署到nginx服务下,非根目录,刷新页面404的问题

    如果在根目录则添加 try_files $uri $uri/ /index.html; 如果不在根目录则添加,格式如下 location  /xxxx  { try_files $uri $uri/ ...