您的位置:首页 > 编程语言 > C#

C#学习笔记 线程同步问题

2016-03-21 22:29 639 查看
这是用C#提供的各种类实现的几个线程同步问题。

生产者消费者问题

生产者消费者问题大体是这样的:有几个生产者和几个消费者,共享一个缓冲区。生产者会向缓冲区中添加数据;消费者会从缓冲区中将数据取走。需要处理这两者之间的同步问题。

这里先定义一个自己的线程安全队列。该队列使用两个信号量来处理同步问题。另外在进行操作的时候需要锁定临界区,这里使用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);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: