C#学习笔记 线程同步问题
2016-03-21 22:29
639 查看
这是用C#提供的各种类实现的几个线程同步问题。
这里先定义一个自己的线程安全队列。该队列使用两个信号量来处理同步问题。另外在进行操作的时候需要锁定临界区,这里使用lock语句实现。
有了线程安全的队列之后,就可以解决生产者消费者问题了。
首先定义哲学家类,在Eat方法里使用了双检锁技术。
定义好了哲学家类之后,就可以来解决哲学家问题了。
C#中包含了一个读写锁ReaderWriterLockSlim,专门用来解决读者写者问题的。因此这里就直接使用这个类来实现。
首先定义读者写者使用的数据类和接口:
然后使用ReaderWriterLockSlim类来实现读者和写者的同步:
读者和写者实现之后,就可以着手解决问题了:
两个类定义好了之后,就可以模拟这个问题了:
然后是HorseRace类,其中Print方法打印几匹马每一次的行进轨迹。NextRun方法让每匹马再继续跑下一步,在Start方法中一直调用这两个方法,模拟赛马的状态。当有一匹马超出重点线,程序结束。
生产者消费者问题
生产者消费者问题大体是这样的:有几个生产者和几个消费者,共享一个缓冲区。生产者会向缓冲区中添加数据;消费者会从缓冲区中将数据取走。需要处理这两者之间的同步问题。这里先定义一个自己的线程安全队列。该队列使用两个信号量来处理同步问题。另外在进行操作的时候需要锁定临界区,这里使用lock语句实现。
public class FixedQueue<T> { private T[] array; private int _count; private SemaphoreSlim empty; private SemaphoreSlim full; public FixedQueue(int length) { array = new T[length]; empty = new SemaphoreSlim(length, length); full = new SemaphoreSlim(0, length); } public int Count { get { return _count; } } public void Enqueue(T element) { empty.Wait(); lock (array) { array[_count] = element; _count++; Console.WriteLine("生产者生产了:" + element); } full.Release(); } public T Dequeue() { full.Wait(); var first = array[0]; lock (array) { for (int i = 0; i < _count - 1; ++i) { array[i] = array[i + 1]; } _count--; Console.WriteLine(" 消费者消费了:" + first); } empty.Release(); return first; } }
有了线程安全的队列之后,就可以解决生产者消费者问题了。
public static void Problem1() { Console.WriteLine("生产者消费者问题1:"); var queue = new FixedQueue<int>(5); var producer1 = new Producer1<int>(queue); var consumer1 = new Consumer1<int>(queue); var produce = Task.Run(() => { for (int i = 0; i < 8; ++i) { producer1.Produce(i); } }); var consume = Task.Run(() => { for (int i = 0; i < 8; ++i) { consumer1.Comsume(); } }); Task.WhenAll(produce, consume).Wait(); Console.WriteLine("--------------------------------"); } public class Producer1<T> : IProducer<T> { private FixedQueue<T> _queue; public Producer1(FixedQueue<T> queue) { _queue = queue; } public void Produce(T element) { _queue.Enqueue(element); } } public class Consumer1<T> : IConsumer<T> { private FixedQueue<T> _queue; public Consumer1(FixedQueue<T> queue) { _queue = queue; } public T Comsume() { return _queue.Dequeue(); } }
哲学家问题
哲学家问题是这样的:有若干个哲学家围坐在一个圆桌前,有同样数量的筷子均匀放在每个哲学家之间。哲学家吃饭的时候需要使用其左右两边的两个筷子才能吃饭。这里用的策略是让哲学家同时拿起左右两个筷子,如果任何一边没有筷子,哲学家就会一直等待。当每个哲学家吃饱后结束。首先定义哲学家类,在Eat方法里使用了双检锁技术。
public class Philosopher { private static int _count; private int _order; private bool isFull; private bool[] _chopsticks; public Philosopher(int order, bool[] chopsticks) { _order = order; _chopsticks = chopsticks; } public int Count { get { return _count; } set { _count = value; } } public bool Eat() { if (IsAvailable()) { lock (_chopsticks) { if (IsAvailable()) { isFull = true; Console.WriteLine($"哲学家{_order}用{_order}和{(_order + 1) % Count}号筷子吃完了"); Thread.Sleep(1000); } } } return isFull; } private bool IsAvailable() { return _chopsticks[_order] && _chopsticks[(_order + 1) % _count]; } }
定义好了哲学家类之后,就可以来解决哲学家问题了。
public static void Problem3() { Console.WriteLine("哲学家进餐问题:"); int N = 5; var philosophers = new Philosopher ; var chopsticks = new bool ; var tasks = new Task ; for (int i = 0; i < N; ++i) { chopsticks[i] = true; philosophers[i] = new Philosopher(i, chopsticks); philosophers[i].Count = N; int order = i; tasks[i] = new Task(() => { while (!philosophers[order].Eat()) { } }); } Parallel.ForEach(tasks, task => task.Start()); Task.WhenAll(tasks).Wait(); Console.WriteLine("所有哲学家都用餐完毕"); Console.WriteLine("--------------------------------"); }
读者写者问题
读者写者问题描述如下:有若干个读者和写者共同操作一份数据。同一时间只允许一个写者对其进行写入。多个读者可以同时读取数据。读者和写者不能同时读写数据。C#中包含了一个读写锁ReaderWriterLockSlim,专门用来解决读者写者问题的。因此这里就直接使用这个类来实现。
首先定义读者写者使用的数据类和接口:
/// <summary> /// 读写者问题使用的数据类接口 /// </summary> public interface IData { int Data { set; get; } } public class MyData : IData { private int _data; public int Data { get { return _data; } set { _data = value; } } public override string ToString() { return _data.ToString(); } }
然后使用ReaderWriterLockSlim类来实现读者和写者的同步:
/// <summary> /// 读者类 /// </summary> /// <typeparam name="T"></typeparam> public class Reader<T> where T : IData { private ReaderWriterLockSlim _rwlock; private T _data; public Reader(ReaderWriterLockSlim rwlock, T data) { _rwlock = rwlock; _data = data; } public void Read() { _rwlock.EnterReadLock(); Console.WriteLine($"读者读到的数据是:{_data.ToString()}"); _rwlock.ExitReadLock(); } } /// <summary> /// 写者类 /// </summary> /// <typeparam name="T"></typeparam> public class Writer<T> where T : IData { private ReaderWriterLockSlim _rwlock; private Random rand = new Random(); private T _data; public Writer(ReaderWriterLockSlim rwlock, T data) { _rwlock = rwlock; _data = data; } public void Write() { _rwlock.EnterWriteLock(); _data.Data = rand.Next(0, 100); Console.WriteLine($"写者将数据写为{_data.ToString()}"); _rwlock.ExitWriteLock(); } }
读者和写者实现之后,就可以着手解决问题了:
public static void Problem4() { var rwlock = new ReaderWriterLockSlim(); var data = new MyData(); var writer = new Writer<IData>(rwlock, data); var rand = new Random(); var readers = new List<Reader<IData>>() { new Reader<IData>(rwlock, data), new Reader<IData>(rwlock, data), new Reader<IData>(rwlock, data) }; var task1 = Task.Run(() => { for (int i = 0; i < 2; ++i) { writer.Write(); Thread.Sleep(1000); } }); Parallel.ForEach(readers, reader => { for (int i = 0; i < 2; ++i) { reader.Read(); Thread.Sleep(rand.Next(500, 2000)); } }); Console.ReadKey(); Console.WriteLine("--------------------------------"); }
CountdownEvent的例子
学生老师问题
这个问题的情景如下:有多个学生同时做作业,所有作业都完成之后通知老师开始批改作业。这里使用CountdownEvent实现。首先定义学生类和老师类,学生类调用Signal方法将其将计数减1,老师类在CountdownEvent上等待所有学生做完作业。/// <summary> /// 利用CountdownEvent类进行同步的学生类 /// </summary> public class Student { private CountdownEvent _count; private int _no; public Student(CountdownEvent count, int no) { _count = count; _no = no; } public void FinishWork() { //如果输出语句放到下面,可能会出现在输出学生完成作业之前 //老师开始批改作业的情况 Console.WriteLine($"学生{_no}做完了作业"); _count.Signal(); } } /// <summary> /// 利用CountDownEvent类进行同步的老师类 /// </summary> public class Teacher { private CountdownEvent _count; public Teacher(CountdownEvent count) { _count = count; } public void CheckWork() { _count.Wait(); Console.WriteLine("老师开始批改作业"); } }
两个类定义好了之后,就可以模拟这个问题了:
public static void Problem5() { Console.WriteLine("所有学生做完作业之后,老师才能开始批改作业:"); int studentsNo = 5; var count = new CountdownEvent(studentsNo); var students = new Student[studentsNo]; var dowork = new Task[studentsNo]; var teacher = new Teacher(count); for (int i = 0; i < studentsNo; ++i) { students[i] = new Student(count, i + 1); int no = i; dowork[i] = Task.Run(() => students[no].FinishWork()); } teacher.CheckWork(); Task.WhenAll(dowork).Wait(); Console.WriteLine("--------------------------------"); }
赛马的模拟程序
这个例子是我看《Java编程思想》里面的一个例子,在这里用C#改写了一下。首先先看看Horse类,这个类中的Track方法返回一个模拟赛马行进的字符串。Run方法模拟赛马的行进,每匹马每次随机前进0-2步。class Horse { private static int _count; private int id; private int _strides; private object _syncLock = new object(); private CountdownEvent _down; private static Random _rand = new Random(); public Horse(CountdownEvent down) { id = _count++; _down = down; } public override string ToString() { return $"Horse {id}"; } public string Tracks() { var sb = new StringBuilder(); for (int i = 0; i < Strides; ++i) { sb.Append("*"); } sb.Append(id); return sb.ToString(); } public int Strides { get { lock (_syncLock) { return _strides; } } } public void Run() { _strides += _rand.Next(0, 3); _down.Signal(); } }
然后是HorseRace类,其中Print方法打印几匹马每一次的行进轨迹。NextRun方法让每匹马再继续跑下一步,在Start方法中一直调用这两个方法,模拟赛马的状态。当有一匹马超出重点线,程序结束。
public static void Problem6() { var race = new HorseRace(7); race.Start(); } class HorseRace { public const int FinishLine = 25; private List<Horse> _horses = new List<Horse>(); private CountdownEvent _down; public HorseRace(int nHorse) { _down = new CountdownEvent(nHorse); for (int i = 0; i < nHorse; ++i) { _horses.Add(new Horse(_down)); } } private void Print() { _down.Wait(); var sb = new StringBuilder(); for (int i = 0; i < FinishLine; ++i) { sb.Append("="); } Console.WriteLine(sb); foreach (var horse in _horses) { Console.WriteLine(horse.Tracks()); if (horse.Strides >= FinishLine) { Console.WriteLine($"{horse} 赢了"); return; } } _down.Reset(); } private void NextRun() { Parallel.ForEach(_horses, horse => horse.Run()); } public void Start() { while (_horses.Select(horse => horse.Strides).Max() < FinishLine) { NextRun(); Print(); Thread.Sleep(500); } } }
相关文章推荐
- VS2015 C#的单元测试
- “一片空白”的c#
- 编写c#程序,输出平均分和高于平均分的同学
- C#学习前辈的真言
- 作业C#程序分析
- 读C#程序最小公倍数答案就是:2123581660200
- 读C#程序
- c#程序阅读分析
- C#程序解读
- c#开发中遇到System.AccessViolationException
- C# 面向对象:封装、继承、多态
- C# 版本 支付
- C# 事件与委托详解
- C#解leetcode 11. Container With Most Water
- 读C#程序
- 2013 C#单元测试
- 机房收费系统遇到的问题(四) 几点一定要注意的
- C#代码分析--阅读程序,回答问题
- C#中打印出当前堆栈
- c#