使整个方法线程安全的最简单方法?

发布于 2024-11-27 07:33:44 字数 824 浏览 1 评论 0原文

关于多线程编程似乎有很多东西需要学习,而且都有点令人生畏。

对于我当前的需求,我只想防止在完成之前从另一个线程再次调用一个方法,我的问题是:

这是创建方法的适当(安全)方法吗?线程安全?

class Foo
{
    bool doingWork;
    void DoWork()
    {
        if (doingWork)  // <- sophistocated thread-safety
            return;     // <-

        doingWork = true;

        try
        {
            [do work here]
        }
        finally
        {
            doingWork = false;
        }
    }
}

如果这还不够,实现这一点的最简单方法是什么?


编辑:有关该场景的更多信息:

  • 只有一个 Foo 实例

  • 将从 ThreadPool 调用 Foo.DoWork()已过去的线程 System.Timers.Timer 的事件。

  • 通常 Foo.DoWork() 会在下一次之前完成亿万次 被调用,但我想为它运行很长时间的微小机会进行编码, 并在完成之前再次被调用。


(我也不够聪明,无法确定这个问题是否可以被标记为与语言无关,所以我没有。开明的读者,如果适用,请随意这样做。)

There seems to be a lot to learn about multithreaded programming and it's all a bit intimidating.

For my current needs, I just want to protect against a method being called again from another thread before it finishes, and my question is:

Is this an adequate (safe) way to make a method thread-safe?

class Foo
{
    bool doingWork;
    void DoWork()
    {
        if (doingWork)  // <- sophistocated thread-safety
            return;     // <-

        doingWork = true;

        try
        {
            [do work here]
        }
        finally
        {
            doingWork = false;
        }
    }
}

If that isn't sufficient, what is the simplest way to achieve this?


EDIT: More info about the scenario:

  • There is only one instance of Foo

  • Foo.DoWork() will be called from a ThreadPool thread on the Elapsed
    event of a System.Timers.Timer.

  • Normally Foo.DoWork() will finish eons before the next time it's
    called, but I want to code for the slim chance that it will run long,
    and get called again before finishing.


(I'm also not smart enough to be sure if this question could be tagged language-agnostic, so I haven't. Enlightened readers, feel free to do so if applicable.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

情徒 2024-12-04 07:33:44

您的代码不是线程安全的。您应该使用 lock 关键字。

在您当前的代码中:

  if (doingWork)
        return;

  // A thread having entered the function was suspended here by the scheduler.

  doingWork = true;

当下一个线程通过时,它也会进入该函数。

这就是为什么应该使用lock 结构。它基本上与您的代码执行相同的操作,但没有线程在中间被中断的风险:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        lock(lockObject)
        {
            [do work here]
        }
    }
}

请注意,此代码的语义与原始代码有些不同。此代码将导致第二个线程进入等待,然后执行工作。您的原始代码使第二个线程中止。为了更接近原始代码,不能使用 C# lock 语句。必须直接使用底层 Monitor 构造:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        if(Monitor.TryEnter(lockObject))
        {
            try
            {
                [do work here]
            }
            finally
            {
                Monitor.Exit(lockObject);
            }
        }
    }
}

Your code is not thread safe. You should use the lock keyword instead.

In your current code:

  if (doingWork)
        return;

  // A thread having entered the function was suspended here by the scheduler.

  doingWork = true;

When the next thread comes through, it will also enter the function.

This is why the lock construct should be used. It basically does the same as your code, but without the risk for a thread being interrupted in the middle:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        lock(lockObject)
        {
            [do work here]
        }
    }
}

Note that this code has somewhat different semantics than your original. This code will cause the second thread entering to wait and then do the work. Your original code made the second thread just abort. To come closer to your original code, the C# lock statement cannot be used. The underlying Monitor construct has to be used directly:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        if(Monitor.TryEnter(lockObject))
        {
            try
            {
                [do work here]
            }
            finally
            {
                Monitor.Exit(lockObject);
            }
        }
    }
}
幽梦紫曦~ 2024-12-04 07:33:44

可重入与多线程无关。

可重入方法是一种最终可以在同一线程上从其自身内部调用的方法。
例如,如果一个方法引发一个事件,并且处理该事件的客户端代码在事件处理程序内再次调用该方法,则该方法是可重入的。
保护该方法免于重入意味着确保如果您从其内部调用它,它不会执行任何操作或抛出异常。

只要所有内容都在同一线程上,您的代码就不会在同一对象实例中重入。

除非[do work here]能够运行外部代码(例如,通过引发事件,或通过从其他东西调用委托或方法),否则它一开始就不是可重入的。

您编辑的问题表明这整个部分与您无关。
无论如何你应该读一下它。


您可能(编辑:正在)寻找排他性 - 确保该方法在被多个线程同时调用时不会同时运行两次。
您的代码不是唯一的。如果两个线程同时运行该方法,并且它们都同时运行 if 语句,那么它们都将通过 if,然后都设置 doingWork< /code> 标志,并且都将运行整个方法。

为此,请使用 lock 关键字。

Re-entrancy has nothing to do with multi-threading.

A re-entrant method is a method that can end up being called from within itself, on the same thread.
For example, if a method raises an event, and the client code that handles that event calls the method again inside the event handler, that method is re-entrant.
Protecting that method from re-entrancy means making sure that if you call it from inside itself, it will either not do anyhting or throw an exception.

Your code is protected from re-entrancy within the same object instance, as long as everything is on the same thread.

Unless [do work here] is capable of running external code (eg, by raising an event, or by calling a delegate or method from something else), it isn't re-entrant in the first place.

Your edited question indicates that this entire section is irrelevant to you.
You should probably read it anyway.


You may be (EDIT: are) looking for exclusivity – ensuring that the method will not run twice at once if called by multiple threads simultaneously.
Your code is not exclusive. If two threads run the method at once, and they both run the if statement at once, they will both get past the if, then both set the doingWork flag, and will both run the entire method.

To do that, use the lock keyword.

我一直都在从未离去 2024-12-04 07:33:44

如果你想要简单代码并且不太关心性能,它可以像

class Foo
{
    bool doingWork;
object m_lock=new object();
    void DoWork()
    {
        lock(m_lock) // <- not sophistocated multithread protection
{
        if (doingWork)  
            return;     
         doingWork = true;
}


        try
        {
            [do work here]
        }
        finally
        {
lock(m_lock) //<- not sophistocated multithread protection
{
            doingWork = false;
}
        }
    }

}

一样简单如果你想封装一点锁定,你可以创建一个线程安全的属性,如下所示:

public bool DoingWork
{
get{ lock(m_Lock){ return doingWork;}}
set{lock(m_lock){doingWork=value;}}
}

现在你可以使用它代替了 field ,但是这会导致锁定花费更多时间,导致锁使用次数增加。

或者您可以使用完整的栅栏方法(来自伟大的线程书籍 Joseph Albahari 在线线程

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

他指出,完整的栅栏比锁定语句快 2 倍。在某些情况下,您可以通过删除不必要的 MemoryBarrier() 调用来提高性能,但使用 lock 更简单、更清晰且不易出错。

我相信这也可以使用基于 int 的 doingWork 字段的 Interlocked 类来完成。

if you want easy code and dont care about performance too much it can be as easy as

class Foo
{
    bool doingWork;
object m_lock=new object();
    void DoWork()
    {
        lock(m_lock) // <- not sophistocated multithread protection
{
        if (doingWork)  
            return;     
         doingWork = true;
}


        try
        {
            [do work here]
        }
        finally
        {
lock(m_lock) //<- not sophistocated multithread protection
{
            doingWork = false;
}
        }
    }

}

If you want to incapsulate locking a little you can create a property that is thread safe like this:

public bool DoingWork
{
get{ lock(m_Lock){ return doingWork;}}
set{lock(m_lock){doingWork=value;}}
}

Now you can use it instead field , however it will result in more time spent for locking cause number of lock uses increases.

Or you can use full fence approach ( from great threading book Joseph Albahari online threading )

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

He states that full fence is 2x faster than lock statement. In some cases you can improve performance by removing unneeded calls to MemoryBarrier(), but using lock is simple, more clear and less error prone.

I believe this can also be done using Interlocked class around int based doingWork field.

相思故 2024-12-04 07:33:44

http://msdn.microsoft.com/en-us/library /system.threading.barrier.aspx

可能想考虑使用屏障,它会为您完成所有工作。这是控制可重入代码的标准方法。还允许您控制一次执行该工作的线程数量(如果允许超过 1 个)。

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

Might want to look into using barrier instead, it does all of the work for you. It's the standard way of controlling reentrant code. Also allows you to control the amount of threads doing that work at once (if you allow more than 1).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文