С# Threading: использование Monitor.Wait, Lock и PulseAll
Я новичок в CSharp и Threading.
Чтобы быть знакомым с Monitor.Wait, Monitor.lock и Monitor.PulseAll, я создал сценарий, описанный ниже.
<я > "FootballGround разделяют разные команды для практической цели. В любой момент только одна команда может использовать почву для своей практики. Команда может использовать землю в течение 30 минут для своей практики. По прошествии 25 минут она должна сигнализировать о других нити, которые земля собирается освободить через 5 минут. Когда земля мокрая (перечисление имеет три значения бесплатно, выделено, мокрое), никакая команда не может заблокировать землю, и все должны ждать 10 минут"
Честно говоря, я не знаю, как превратить описание в фактическое кодирование. Основываясь на моем понимании, я разработал схему.
namespace ThreadingSimulation
{
// A Random Activity can be picked up from this enum by a team
public enum RandomGroundStatus
{
free,
allotted,
Wet
}
class FootBallGround
{
public void Playing(object obj)
{
// Here the name of the team which is using the ground will be printed
// Once the time is reached to 25 minnutes the active thread acquired
// the lock will signal other threads
}
public void GroundCleaningInProgress(object obj)
{
// Ground cleaning is in progress all of you
// wait for 10 minutes
}
}
class Team
{
string teamName;
static void Main()
{
//select random value for GrandStatus from enum
// if the ground is wet no team is allowed to get the
// ground for 10 minutes
//if the ground is free "Team A" locks the ground
// otherwise "Team B" locks the ground
}
}
}
Здесь я не знаю, как применять блокировки и signalls.kindly помочь мне.
Ответы
Ответ 1
На самом деле ваш сценарий не очень похож на lock
, но мы все равно попытаемся: -p
Я немного изменил настройку; вместо:
- одно лицо может иметь основания за раз
- Когда команда заканчивается, они сообщают первому человеку, которого они видят (если кто-то ждет)
- когда очиститель заканчивается, они шумны - так что все замечают, что они уходят и пытаются попасть на поле.
Здесь код; обратите внимание, что они не имеют блокировки при использовании оснований, поскольку это предотвратит присоединение других людей к очереди за Pulse
.
В реальности мы могли бы сделать все это всего лишь с помощью lock
(не используя Pulse
вообще) и просто используем стандартное поведение блокировки. Но этот пример показывает, что Pulse
и PulseAll
используются для повторной активации потоков при выполнении условия.
using System;
using System.Threading;
interface IGroundUser
{
bool Invoke(); // do your stuff; return true to wake up *everyone*
// afterwards, else false
}
class Team : IGroundUser
{
private readonly string name;
public Team(string name) { this.name = name; }
public override string ToString() { return name; }
public bool Invoke()
{
Console.WriteLine(name + ": playing...");
Thread.Sleep(25 * 250);
Console.WriteLine(name + ": leaving...");
return false;
}
}
class Cleaner : IGroundUser
{
public override string ToString() {return "cleaner";}
public bool Invoke()
{
Console.WriteLine("cleaning in progress");
Thread.Sleep(10 * 250);
Console.WriteLine("cleaning complete");
return true;
}
}
class FootBallGround
{
static void Main()
{
var ground = new FootBallGround();
ThreadPool.QueueUserWorkItem(delegate { ground.UseGrounds(new Team("Team A")); });
ThreadPool.QueueUserWorkItem(delegate { ground.UseGrounds(new Team("Team B")); });
ThreadPool.QueueUserWorkItem(delegate { ground.UseGrounds(new Cleaner()); });
ThreadPool.QueueUserWorkItem(delegate { ground.UseGrounds(new Team("Team C")); });
ThreadPool.QueueUserWorkItem(delegate { ground.UseGrounds(new Team("Team D")); });
ThreadPool.QueueUserWorkItem(delegate { ground.UseGrounds(new Team("Team E")); });
Console.ReadLine();
}
bool busy;
private readonly object syncLock = new object();
public void UseGrounds(IGroundUser newUser)
{
// validate outside of lock
if (newUser == null) throw new ArgumentNullException("newUser");
// only need the lock when **changing** state
lock (syncLock)
{
while (busy)
{
Console.WriteLine(newUser + ": grounds are busy; waiting...");
Monitor.Wait(syncLock);
Console.WriteLine(newUser + ": got nudged");
}
busy = true; // we've got it!
}
// do this outside the lock, allowing other users to queue
// waiting for it to be free
bool wakeAll = newUser.Invoke();
// exit the game
lock (syncLock)
{
busy = false;
// wake up somebody (or everyone with PulseAll)
if (wakeAll) Monitor.PulseAll(syncLock);
else Monitor.Pulse(syncLock);
}
}
}
Ответ 2
Важно помнить, что блокировка и mutithreaded приложения - это то, что блокировка эффективна только в том случае, если весь ваш код, который обращается к заблокированному ресурсу, играет по тем же правилам, т.е. если один поток может блокировать ресурс, все остальные потоки, которые могут получить доступ к этому тот же ресурс должен использовать блокировки перед доступом к этому ресурсу.
Монитор и блокировка
Ключевое слово lock
- это удобная оболочка для класса Monitor
. Это означает, что lock(obj)
совпадает с Monitor.Enter(obj)
(хотя Monitor
имеет добавленную функциональность до таймаута через некоторое время, если не может получить блокировку для объекта).
Импульсные события и потоки
Когда количество потоков ожидает получения блокировки на каком-либо ресурсе, через код вы можете сигнализировать, когда поток владельца завершен ресурсом. Это известно как сигнализация или пульсирование и может быть достигнуто с помощью Monitor.Pulse
, Monitor.PulseAll
, ManualResetEvent.Set
или даже AutoResetEvent.Set
.
Пример футбола
Итак, ваш приведенный ниже пример для футбола будет закодирован для включения блокировки потоков следующим образом:
namespace ThreadingSimulation
{
// A Random Activity can be picked up from this enum by a team
public enum RandomGroundStatus
{
Free,
Allotted,
Wet
}
class FootBallGround
{
private Team _currentTeam;
// Set the initial state to true so that the first thread that
// tries to get the lock will succeed
private ManualResetEvent _groundsLock = new ManualResetEvent(true);
public bool Playing(Team obj)
{
// Here the name of the team which is using the ground will be printed
// Once the time is reached to 25 minutes the active thread the lock will
// signal other threads
if (!_groundsLock.WaitOne(10))
return false;
_currentTeam = obj;
// Reset the event handle so that no other thread can come into this method
_groundsLock.Reset();
// Now we start a separate thread to "timeout" this team lock
// on the football grounds after 25 minutes
ThreadPool.QueueUserWorkItem(WaitForTimeout(25));
}
public void GroundCleaningInProgress(object obj)
{
// Ground cleaning is in progress all of you wait for 10 minutes
}
private void WaitForTimeout(object state)
{
int timeout = (int)state;
// convert the number we specified into a value equivalent in minutes
int milliseconds = timeout * 1000;
int minutes = milliseconds * 60;
// wait for the timeout specified
Thread.Sleep(minutes);
// now we can set the lock so another team can play
_groundsLock.Set();
}
}
class Team
{
string teamName;
FootBallGround _ground;
public Team(string teamName, FootBallGround ground)
{
this.teamName = teamName;
this._ground = ground;
}
public bool PlayGame()
{
// this method returns true if the team has acquired the lock to the grounds
// otherwise it returns false and allows other teams to access the grounds
if (!_ground.Playing(this))
return false;
else
return true;
}
}
static void Main()
{
Team teamA = new Team();
Team teamB = new Team();
// select random value for GrandStatus from enum
RandomGroundStatus status = <Generate_Random_Status>;
// if the ground is wet no team is allowed to get the
// ground for 10 minutes.
if (status == RandomGroundStatus.Wet)
ThreadPool.QueueUserWorkItem(WaitForDryGround);
else
{
// if the ground is free, "Team A" locks the ground
// otherwise "Team B" locks the ground
if (status == RandomGroundStatus.Free)
{
if (!teamA.PlayGame())
teamB.PlayGame();
}
}
}
}
** Примечания **
-
Используйте ManualResetEvent
вместо lock
и Monitor
, так как нам нужно прямое управление, когда состояние блокировки пульсирует, чтобы другие потоки могли играть в футбол.
-
Передайте ссылку FootBallGrounds
каждому Team
, потому что каждая команда будет играть на определенных площадках для ног, и каждая футбольная площадка может быть занята другой командой.
-
Передайте ссылку на текущую команду, играющую на FootBallGround
, потому что только одна команда может играть по основаниям за раз.
-
Используйте ThreadPool.QueueUserWorkItem
, поскольку он более эффективен при создании простых потоков, чем мы вручную создаем потоки. В идеале мы могли бы использовать экземпляр Timer
.