1

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

我有 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);
        }
4

5 回答 5

2

正如其他人指出的那样,这里的直接锁比信号量更合适。

这样做的原因是因为您只有一个资源 ( buffer) 要保护其免受并发访问。只有一个资源要么忙要么不忙,锁是最合适的。

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

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

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

随着人们离开(他们Release)和保镖(Semaphore)减少他的计数器 - 现在可以进入相应数量的人。

所以 - 你在信号量上设置容量,资源的消费者等待它可用(如果容量不可用,它们将阻塞),消费者在使用完资源后释放。

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

信号量与互斥量

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

于 2013-11-04T19:38:06.220 回答
2

你需要在这里做两件事。

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

您可以使用信号量来执行此操作,尽管锁定或监视器更合适。另一个确实需要一个事件,可能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。当然,您需要一种方法来告诉线程退出。查看取消

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

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

于 2013-11-04T19:32:43.090 回答
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()方法中做同样的事情。

于 2013-11-04T09:15:57.233 回答
0

我想您希望在 Add 方法工作后立即启动 Average 方法,并且在您的 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();
  }
}
于 2013-11-04T09:35:43.840 回答
0

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

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

于 2013-11-04T09:56:50.507 回答