【问题标题】:Thread programming using semaphors使用信号量进行线程编程
【发布时间】:2013-11-04 09:08:28
【问题描述】:

我正在尝试了解如何在使用线程时使用信号量。

我有 2 个线程使用相同的资源 - 一个 Arraylist。 一种方法将随机温度添加到列表中,另一种方法计算列表的平均温度。

如何在这种情况下使用信号量属性 Wait 和 Release? 以及如何控制计算平均温度的线程在将某些内容添加到我的列表后启动。

这是我的一些代码:

class Temperature
{
    private static Random random = new Random();
    private static ArrayList buffer = new ArrayList();
    static SemaphoreSlim e, b;

    public static void Main (string[] args)
    {
        e = new SemaphoreSlim(6); //how will this work?
        b = new SemaphoreSlim(1);
        Thread t1 = new Thread (Add);
        t1.Start ();
        Thread t2 = new Thread (Average);
        t2.Start ();
    }

    public static void Add()
    {
        int temperature;
        for (int i=0; i<50; i++)
        {
            temperature = random.Next (36, 42);
            Console.WriteLine ("Temperature added to buffer: " + temperature);
            b.Wait ();
            e.Wait ();
            buffer.Add(temperature);
            b.Release ();
            Thread.Sleep (50);
        }

【问题讨论】:

  • 您应该为此使用lock,而不是信号量IMO。

标签: c# multithreading semaphore thread-synchronization


【解决方案1】:

你需要两件事。

  1. 为防止线程在更新列表时相互干扰,您需要一个锁或一个信号量。
  2. 要让平均线程在添加项目时更新平均值,您需要它等待事件,并让添加线程在添加项目时设置该事件。

你可以用信号量来做,虽然锁或Monitor 更合适。另一个确实需要一个事件,可能是AutoResetEvent

private static Random random = new Random();
private static ArrayList buffer = new ArrayList();
static SemaphoreSlim BufferLock = new SemaphoreSlim(1);
static AutoResetEvent ItemAdded = new AutoResetEvent(false);

public static void Main (string[] args)
{
    BufferLock.Wait();  // initially acquire the semaphore.
    Thread t1 = new Thread (Add);
    t1.Start ();
    Thread t2 = new Thread (Average);
    t2.Start ();

    // wait for adding thread to finish
    t1.Wait();
}

public static void Add()
{
    int temperature;
    for (int i=0; i<50; i++)
    {
        temperature = random.Next (36, 42);
        BufferLock.Wait();
        buffer.Add(temperature);
        ItemAdded.Set();
        BufferLock.Release();
        Console.WriteLine ("Temperature added to buffer: " + temperature);
        Thread.Sleep (50);
    }
}

public static void Average()
{
    while (true)
    {
        ItemAdded.Wait();  // wait for item to be added
        BufferLock.Wait();
        ComputeAverage();  // however you want to do this
        BufferLock.Release();
    }
}

如果您想确保计算出最后一个平均值,则必须等待t2。当然,您需要一种方法来告诉线程退出。查看Cancellation

你真的不应该使用ArrayListList< int > 会是更好的选择。使用 ArrayList 的唯一原因是当您为 2.0 之前的 .NET 版本编写代码时,或者当您支持非常旧的代码时。

可以AutoResetEvent 替换为Semaphore 来模拟事件通知,但这样做有点麻烦,需要非常仔细的编码才能正确处理。我当然不想那样做。

【讨论】:

  • 这是我最终用于我的项目的解决方案。我尝试了其他一些建议(比如使用锁等),但这个例子最适合我的项目:)
【解决方案2】:

只使用一个信号量:

var b = new SemaphoreSlim(1); // 1 -> allow 1 thread to enter the critical section at one time.

然后用它来保护你的缓冲区(=关键部分)

b.Wait ();
buffer.Add(temperature);
b.Release ();

如果您不介意使用锁,请改用:

private readonly object _locker_ = new object();

然后将其与lock 语句一起使用:

lock(_locker_)
{
    buffer.Add(temperature);
}

别忘了在你的 Average() 方法中做同样的事情。

【讨论】:

  • 是的,当你的临界区不是多个函数的一部分或依赖于对象的状态时,通常锁会更好。我只想补充一点,如果您创建了一个储物柜对象,请始终将其设置为readonly,以便无法更改它(这会干扰任何正在进行的同步)。 IE。 private readonly object _locker_ = new Object();
【解决方案3】:

正如其他人所指出的,在这里,简单的锁比信号量更合适。

这样做的原因是因为您只有一个资源 (buffer) 需要防止并发访问。只有一个资源忙或不忙,锁是最合适的。

当资源容量有限时,信号量很有用。一个例子:

酒吧或夜总会等有防火限制的场所。人们(线程)等待,直到保镖(信号量)看到场地(资源)有容量。第一批客户不必等待很长时间,事实上他们会立即进入。

当他们进入保镖时(Semaphore)点击他的计数器来记录消耗了多少容量。一旦场地满员,就会排起长队 - 人们必须等待,直到里面有人离开。

当人们离开时(他们释放)和保镖(Semaphore)减少他的计数器 - 并且相应数量的人现在可以进入。

所以 - 你在信号量上设置容量,资源的消费者等待它可用(如果容量不可用,他们将阻塞)和消费者释放什么时候他们已经完成了资源。

一个共同特征(但不是必需的)是,当您不知道(或关心)正在消耗资源的哪个特定部分时,通常会使用信号量,而只是使用了其中的一些可用部分.

信号量与互斥量

Mutex 类似于lock,但它是一个命名的系统范围资源。 Mutex 必须由获得它的 Thread 释放,而 Semaphore 没有线程标识。这可以解释为什么您有时会看到使用计数为 1 的 Semaphore 而不是 Mutex

【讨论】:

  • 很好地解释了信号量的工作原理!我希望我的老师使用这个例子。
【解决方案4】:

我猜你希望你的 Average 方法在 Add 方法工作后立即启动,并且在你的 ArrayList 对象周围是线程安全的。 所以……

var s = new SemaphoreSlim(0);
private object myLock = new object();

Add()
{
  Calculate();
  lock(myLock)
  {
      buffer.Add(temperature);
      SemaphoreSlim.Release();
  }
}

Average()
{
  SemaphoreSlim.Wait();
  lock(myLock)
  {
     CalcAvg();
  }
}

【讨论】:

    【解决方案5】:

    虽然您可以使用初始为零的 Semaphore 来实现所需的信号,但您最好使用 AutoResetEvent 代替。让您的计算线程等待事件句柄并在添加到列表后触发事件。保持locks 与@klark 的解决方案一样。

    有关为作业选择正确的同步原语的更多信息,请阅读http://msdn.microsoft.com/en-us/library/ms228964(v=vs.110).aspx

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-22
      • 1970-01-01
      • 1970-01-01
      • 2015-04-13
      • 2014-08-15
      • 1970-01-01
      相关资源
      最近更新 更多