C#多线程编程:使用Monitor.Wait、Lock和PulseAll

15

我是C#和线程方面的新手。

为了熟悉Monitor.Wait、Monitor.lock和Monitor.PulseAll,我描述了下面的情况:

“不同的团队共享一个足球场地进行训练。同时只有一个团队能够使用场地进行训练。一个团队可以使用场地进行30分钟的训练。当时间到达25分钟时,它应该向其他线程发出信号,告知场地将在5分钟后释放。当场地潮湿(枚举有三个值:free,allotted,wet)时,所有团队都不能锁定场地,必须等待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

      }

   }

}

我不知道如何应用锁和信号,请帮助我。


4
好的,我会尽力进行翻译。以下是需要翻译的内容:See also: https://dev59.com/93I_5IYBdhLWcg3wBuT3 and https://dev59.com/4UnSa4cB1Zd3GeqPQ8dz - Marc Gravell
2个回答

13

实际上,你的情况与lock不是非常相关 - 但我们仍然会尝试 ;-p

我稍微调整了设置; 相反:

  • 一次只能有一个实体拥有基地
  • 当团队完成时,他们会告诉他们看到的第一个人(如果有人在等待)
  • 当清洁工完成时,他们很吵闹 - 所以每个人都会注意到他们离开并试图进入场地

这是代码; 请注意,在使用场地时,他们没有lock,因为这将阻止其他人加入Pulse的队列。

实际上,我们可以仅使用lock(根本不使用Pulse),并且只使用标准的阻塞行为来完成所有这些事情。但是,此示例显示在满足条件时如何使用PulsePulseAll重新激活线程。

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);
        }
    }    
}    

1
很好,无可挑剔和巧妙的回答。 :) - user186973

5
在锁定和多线程应用程序中始终要记住的重要事情是,只有当访问已锁定资源的所有代码都遵循相同的规则时,锁定才有效,即如果一个线程可以锁定资源,则可以访问该资源的所有其他线程在访问该资源之前都应该使用锁。
监视器和锁 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's 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代替lockMonitor,因为我们需要直接控制锁的状态何时被脉冲以使其他线程能够玩足球比赛。

  • 向每个Team传递对FootBallGrounds的引用,因为每个团队都将在特定的足球场上进行比赛,并且每个足球场可能会被另一个团队占用。

  • 向正在FootBallGround上进行比赛的当前团队传递引用,因为一次只能有一个团队在场地上比赛。

  • 使用ThreadPool.QueueUserWorkItem,因为它比我们手动创建线程更有效。理想情况下,我们也可以使用Timer实例。


感谢Mike花费宝贵的时间:) - user186973

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接