最近在看操作系统,研究并发处理方面的问题,试着用C#写了“哲学家就餐”问题,已测试。
using System;
using System.Diagnostics;
using System.Threading;
namespace Philosopher
{
class Program
{
static void Main(string[] args)
{
Method(5);
}
/// <summary>
/// 哲学家就餐问题,5个哲学家围坐一张餐桌,每两个科学家之间各摆放一把叉子(或一根筷子),
/// 科学家只有拿到左右两把叉子才能进餐,科学家只有两种状态:进餐和思考,设计算法让科学家尽快进餐。
/// </summary>
/// <param name="n"></param>
static void Method(int n)
{
if (n < 2)
{
throw new ArgumentOutOfRangeException("n", "n should be larger than 1.");
}
Person[] people = new Person[n];
bool[] resource = new bool[n]; // resource is the fork or chopstick, false:vacant, true:occupied
for (int i = 0; i < n; i++)
{
people[i] = new Person(i);
resource[i] = false;
}
int hungryCount = n;
Stopwatch watch = new Stopwatch();
watch.Start();
foreach (var p in people)
{
Thread thread = new Thread(() =>
{
Console.WriteLine("ThreadId:" + Thread.CurrentThread.ManagedThreadId);
while (hungryCount != 0)
{
if (p.State == 0)
{
bool canEat = false;
lock (resource)
{
// CanEat() and Eat() must be locked together in the same lock block
canEat = CanEat(resource, p.Index);
if (canEat)
{
Eat(resource, p);
Console.WriteLine("Eating...");
}
}
if (canEat)
{
Thread.Sleep(5000); // the time the eating progress takes
Think(resource, p);
hungryCount--;
Console.WriteLine("Elapsed:" + watch.Elapsed.TotalMilliseconds);
}
}
}
});
thread.Start();
}
}
static bool CanEat(bool[] resource, int i)
{
bool result = false;
//lock (resource)
//{
if (!resource[i])
{
if (i == 0)
{
result = !resource[resource.Length - 1];
}
else
{
result = !resource[i - 1];
}
if (result)
{
Console.WriteLine("[CanEat]The {0}th person: both of the left and right fork are vacant.", i + 1);
}
else
{
//Console.WriteLine("[CannotEat]The {0}th person: the left fork is vacant but the right one is occupied.", i + 1);
}
}
else
{
//Console.WriteLine("[CannotEat]The {0}th person: the left fork is occupied.", i + 1);
}
//}
return result;
}
static void Eat(bool[] resource, Person p)
{
//lock (resource)
//{
resource[p.Index] = true;
if (p.Index == 0)
{
resource[resource.Length - 1] = true;
}
else
{
resource[p.Index - 1] = true;
}
//}
p.State = 1;
Console.WriteLine("The {0}th person is eating.", p.Index + 1);
}
static void Think(bool[] resource, Person p)
{
lock (resource)
{
resource[p.Index] = false;
if (p.Index == 0)
{
resource[resource.Length - 1] = false;
}
else
{
resource[p.Index - 1] = false;
}
}
p.State = 2;
Console.WriteLine("The {0}th person just has finished the meal.", p.Index + 1);
}
}
public class Person
{
/// <summary>
/// the index of person, for printing log
/// </summary>
public int Index { get; set; }
/// <summary>
/// the state of person, 0:hungry, 1:eating, 2:thinking
/// </summary>
public int State { get; set; }
public Person(int index)
: this(index, 0)
{
}
public Person(int index, int state)
{
this.Index = index;
this.State = 0;
}
}
}
后来仔细想想,改进了一下程序
1. 把CanEat()与Eat()合并成CanEat(),这样lock block写在方法里,更合理,外层调用也更简洁
2. 希望各个子线程几乎完全同时进行,把初始化线程代码与线程启动分离成不同循环(上面代码运行时有可能有些线程还没初始化完,有的初始化完的已经开始跑了,可能造成最终运行时间比预想的多)
3. 并且希望main thread可以等所有子线程完成(异步)再去做些其它事情(上面的代码main可以做其它事但是是与子线程同时进行的,即同步),可以使用AutoResetEvent,改进代码:
using System;
using System.Diagnostics;
using System.Threading;
namespace Philosopher
{
class Program
{
static void Main(string[] args)
{
Method(5);
}
/// <summary>
/// 哲学家就餐问题,5个哲学家围坐一张餐桌,每两个科学家之间各摆放一把叉子(或一根筷子),
/// 科学家只有拿到左右两把叉子才能进餐,科学家只有两种状态:进餐和思考,设计算法让科学家尽快进餐。
/// </summary>
/// <param name="n"></param>
static void Method(int n)
{
if (n < 2)
{
throw new ArgumentOutOfRangeException("n", "n should be larger than 1.");
}
Person[] people = new Person[n];
bool[] resource = new bool[n]; // resource is the fork or chopstick, false:vacant, true:occupied
for (int i = 0; i < n; i++)
{
people[i] = new Person(i);
resource[i] = false;
}
int hungryCount = n;
Stopwatch watch = new Stopwatch();
watch.Start();
Thread[] threads = new Thread[n];
AutoResetEvent[] events = new AutoResetEvent[n];
foreach (var p in people)
{
events[p.Index] = new AutoResetEvent(false);
threads[p.Index] = new Thread(() =>
{
Console.WriteLine("ThreadId:" + Thread.CurrentThread.ManagedThreadId);
while (hungryCount != 0)
{
if (p.State == 0 && CanEat(resource, p))
{
Thread.Sleep(5000); // the time the eating progress takes
Think(resource, p);
hungryCount--;
//Console.WriteLine("Elapsed:" + watch.Elapsed.TotalMilliseconds);
}
}
Console.WriteLine("[END]ThreadId:" + Thread.CurrentThread.ManagedThreadId);
events[p.Index].Set();
});
}
for (int i = 0; i < n; i++)
{
threads[i].Start();
}
AutoResetEvent.WaitAll(events);
Console.WriteLine("total elapsed:" + watch.Elapsed.TotalMilliseconds);
watch.Stop();
}
static bool CanEat(bool[] resource, Person p)
{
bool result = false;
lock (resource)
{
if (!resource[p.Index])
{
if (p.Index == 0)
{
result = !resource[resource.Length - 1];
}
else
{
result = !resource[p.Index - 1];
}
if (result)
{
Console.WriteLine("[CanEat]The {0}th person: both of the left and right fork are vacant.", p.Index + 1);
resource[p.Index] = true;
if (p.Index == 0)
{
resource[resource.Length - 1] = true;
}
else
{
resource[p.Index - 1] = true;
}
p.State = 1;
Console.WriteLine("The {0}th person is eating.", p.Index + 1);
Console.WriteLine("Eating...");
}
else
{
//Console.WriteLine("[CannotEat]The {0}th person: the left fork is vacant but the right one is occupied.", i + 1);
}
}
else
{
//Console.WriteLine("[CannotEat]The {0}th person: the left fork is occupied.", i + 1);
}
}
return result;
}
static void Think(bool[] resource, Person p)
{
lock (resource)
{
resource[p.Index] = false;
if (p.Index == 0)
{
resource[resource.Length - 1] = false;
}
else
{
resource[p.Index - 1] = false;
}
}
p.State = 2;
Console.WriteLine("The {0}th person just has finished the meal.", p.Index + 1);
}
}
public class Person
{
/// <summary>
/// the index of person, for printing log
/// </summary>
public int Index { get; set; }
/// <summary>
/// the state of person, 0:hungry, 1:eating, 2:thinking
/// </summary>
public int State { get; set; }
public Person(int index)
: this(index, 0)
{
}
public Person(int index, int state)
{
this.Index = index;
this.State = 0;
}
}
}
上面第三点使main thread异步的方法,除了使用AutoResetEvent,还可以调用Join(),这个更常规,但是没有AutoResetEvent使用灵活(比如你不想等线程完全结束再异步,AutoResetEvent可以实现想在线程的哪一步开始结束异步都可以,就是说可以进行一半就结束异步,该线程后面部分与其它线程同步进行,只需调用Set()即可)。注意这里有多个子线程要等待,所以要在启动子线程的循环后再另写一个循环去Join,不可以只写一个循环Start之后下一句直接Join。正确写法如:
for (int i = 0; i < n; i++)
{
threads[i].Start();
}
for (int i = 0; i < n; i++)
{
threads[i].Join();
}