【问题标题】:Multithreading: How to check if static class is busy多线程:如何检查静态类是否忙
【发布时间】:2017-05-24 21:56:29
【问题描述】:

我有一个静态类,它有一个静态函数 IsDataCorrect(),它执行 http 请求。

该函数可以同时从多个线程调用,我想让第一个线程做请求,其他的应该被拒绝(意味着他们应该得到 false 作为返回值,他们不应该只是被阻塞!) 直到第一个线程完成请求后半秒。

之后,下一个获胜的线程应该可以做下一个请求,其他的应该被拒绝,以此类推。

这是我的方法,请有人确认这是否合理:

static class MyClass
{
    private static bool IsBusy = false;

    private static object lockObject = new object();

    public static bool IsDataCorrect(string testString)
    {
        lock (lockObject)
        {
            if (IsBusy) return false;
            IsBusy = true;
        }

        var uri = $"https://something.com";

        bool htmlCheck = GetDocFromUri(uri, 2);

        var t = new Thread(WaitBeforeFree);
        t.Start();

        //Fast Evaluations
        //...           

        return htmlCheck;
    }

    private static void WaitBeforeFree()
    {
        Thread.Sleep(500);
        IsBusy = false;
    }
}

【问题讨论】:

  • 你为什么不在WaitBeforeFree() 中使用锁定?我还将volatile 关键字添加到IsBusy 成员。
  • 这个想法是只有获胜的线程才能到达那个部分,所以对我来说似乎没有必要这样做。
  • 但是您正在获取/设置IsBusy 在启动IsDataCorrect() 中获胜线程的线程中,因此您有2 个线程获取/设置它。
  • 您不应该在多个线程上执行任何。从服务器获取文档是 I/O 绑定的。延迟操作不需要整个线程!你不会雇一个工人为你睡觉!所有这些工作流程都可以通过单线程异步完成。
  • 感谢您的所有 cmets!我采用了 LB2 下面的解决方案,它应该涵盖我希望的所有反对意见!

标签: c# multithreading locking


【解决方案1】:

您访问该函数的线程仍将在访问中序列化以检查IsBusy 标志,因为由于lockObject 上的同步,一次只有一个线程能够检查它。相反,您可以简单地尝试获取锁,因此,您不需要标志,因为锁本身将用作锁。其次,我会替换每次启动新线程只是为了休眠并重置标志,并将其替换为检查DateTime 字段。

static class MyClass
{
    private static DateTime NextEntry = DateTime.Now;
    private static ReaderWriterLockSlim timeLock = new ReaderWriterLockSlim();

    private static object lockObject = new object();

    public static bool IsDataCorrect(string testString)
    {
        bool tryEnterSuccess = false;
        try
        {
            try
            {
                timeLock.EnterReadLock()
                if (DateTime.Now < NextEntry) return false;
            }
            finally 
            {
                timeLock.ExitReadLock()
            }
            Monitor.TryEnter(lockObject, ref tryEnterSuccess);
            if (!tryEnterSuccess) return false;

            var uri = $"https://something.com";

            bool htmlCheck = GetDocFromUri(uri, 2);

            //Fast Evaluations
            //...           
            try
            {
                timeLock.EnterWriteLock()
                NextEntry = DateTime.Now.AddMilliseconds(500);
            } finally {
                timeLock.ExitWriteLock()
            }
            return htmlCheck;
        } finally {
            if (tryEnterSuccess) Monitor.Exit(lockObject);
        }
    }
}

这种方式对于不启动新线程更有效,DateTime 访问是安全且并发的,因此线程仅在绝对必要时停止。否则,一切都会随着资源使用量最小化而不断发展。

【讨论】:

  • @@B2 我明白了,谢谢。我会暂时选择你的解决方案:)
  • 等等,我在最后一个 finally 块行中遇到异常,Monitor.Exit(lockObject): 'System.Threading.SynchronizationLockException' 发生。对象同步方法是从未同步的代码块中调用的。当一个无法进入它的线程调用该行时,是否会引发此问题?没有进入 - 因此不可能退出?
  • 我猜第一个“try {”实际上应该放在 tryEnterSucess 检查的正下方,在“Var uri = $”行之前……试了一下,效果很好。跨度>
  • @flo,是的,一个错误,这就是我在凌晨 1 点在没有编译器的情况下编写代码所得到的。我刚刚做了应该有效的更正。
  • 谢谢,如果你在第一次尝试之前声明 bool tryEnterSuccess,那么它将完美运行;)
【解决方案2】:

我看到你们正确地解决了问题,但我认为仍然有空间让它同时正确、高效和简单:)。 这样怎么样?

编辑:编辑以使平静更容易,并且是示例的一部分。

public static class ConcurrentCoordinationExtension
{
    private static int _executing = 0;

    public static bool TryExecuteSequentially(this Action actionToExecute)
    {
        // compate _executing with zero, if zero, set 1,
        // return original value as result,
        // successfull entry then result is zero, non zero returned, then somebody is executing
        if (Interlocked.CompareExchange(ref _executing, 1, 0) != 0) return false;

        try
        {
            actionToExecute.Invoke();
            return true;
        }
        finally
        {
            Interlocked.Exchange(ref _executing, 0);//
        }
    }

    public static bool TryExecuteSequentially(this Func<bool> actionToExecute)
    {
        // compate _executing with zero, if zero, set 1,
        // return original value as result,
        // successfull entry then result is zero, non zero returned, then somebody is executing
        if (Interlocked.CompareExchange(ref _executing, 1, 0) != 0) return false;

        try
        {
            return actionToExecute.Invoke();
        }
        finally
        {
            Interlocked.Exchange(ref _executing, 0);//
        }
    }
}


class Program
{
    static void Main(string[] args)
    {
        DateTime last = DateTime.MinValue;

        Func<bool> operation= () =>
        {
            //calming condition was not meant
            if (DateTime.UtcNow - last < TimeSpan.FromMilliseconds(500)) return false;
            last = DateTime.UtcNow;
            //some stuff you want to process sequentially
            return true;
        };

        operation.TryExecuteSequentially();
    }
}

【讨论】:

  • 这当然是一种方法,但我反对它更有效。 CompareExchange,虽然是一个非常短的操作,但它是一次一个线程的操作,因此是一个序列化的访问。我可能错了,但我认为EnterReadLock 是并发的(只有在有意获取写锁时才会阻塞)。所以线程不应该序列化访问。其次,您的解决方案忽略了 OP 声明的 500 毫秒延迟要求。
  • 您认为序列化访问是什么?它没有阻塞线程,也没有旋转。它被翻译成 CPU 原子指令。你知道,原子操作本质上是并发的,对吧?在 c# 中没有什么比同时协调多个线程更快的了。顺便说一句,像 ReaderWriterLockSlim 这样的同步原语的精简版本实际上是在 Interlocked 操作之上编写的。但在这里它是矫枉过正的。
  • 我没有看到需要用 500ms 的平静来培养糟糕的设计,所以我没有把它放在那里。从设计的角度来看,平静一些操作不应该与操作的并发序列化有关!它将多个关注点混合在一起。冷静者应该在调用并发序列化程序之前,当满足冷静者条件时,应该尝试执行并发操作。
  • 是的,我知道原子操作是并发的。事实上,我起初认为时间戳可能不需要保护,只要它适合处理器的一个字,但不记得结构大小。 AFAIK,我再次不确定并且可能是错误的,如果两个尝试同时更改值,联锁将阻塞线程。简要地。我还认为 readwritelock 不会阻止发出读取访问(除非挂起写入),并且两个线程可以同时请求读取访问(如在请求与拥有的过程中)。如果他们有互锁支持,那么确实没有区别。
  • 顺便说一句,我同意 500 毫秒的冷却时间很奇怪。我也在质疑它背后的理由,但我允许一些我们不知道的约束或推理。质疑它是有效的,我同意这一点。
猜你喜欢
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多