如何保护可能在多线程或异步环境中使用的资源?

Posted

技术标签:

【中文标题】如何保护可能在多线程或异步环境中使用的资源?【英文标题】:How to protect resources that may be used in a multi-threaded or async environment? 【发布时间】:2014-01-27 11:17:01 【问题描述】:

我正在开发一个可供各种消费者使用的 C# API。此 API 提供对共享资源(在我的情况下是进行串行通信的硬件)的访问,通常会有几个不同的参与者尝试同时使用它。

我遇到的问题是我的一些消费者希望在多线程环境中使用它——每个参与者独立工作并尝试使用资源。一个简单的锁在这里工作正常。但是我的一些消费者更喜欢使用 async-await 并对资源进行时间切片。 (据我了解)这需要一个异步锁来将时间片返回给其他任务;在锁处阻塞会停止整个线程。

我认为拥有串行锁充其量是性能不佳,最坏的情况是潜在的竞争条件或死锁。

那么我怎样才能在一个公共代码库中保护这个共享资源,以供两种潜在的并发使用呢?

【问题讨论】:

【参考方案1】:

您可以使用 SemaphoreSlim 和 1 作为请求数。 SemaphoreSlim 允许使用 WaitAsync 和旧的同步方式以 async 方式锁定:

await _semphore.WaitAsync()
try

    ... use shared resource.

finally

    _semphore.Release()

您也可以根据 Stephen Toub 的精彩帖子 Building Async Coordination Primitives, Part 6: AsyncLock 编写自己的 AsyncLock。我在我的应用程序中做到了这一点,并允许在同一个构造上同时使用同步和异步锁。

用法:

// Async
using (await _asyncLock.LockAsync())

    ... use shared resource.


// Synchronous
using (_asyncLock.Lock())

    ... use shared resource.

实施:

class AsyncLock

    private readonly Task<IDisposable> _releaserTask;
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    private readonly IDisposable _releaser;

    public AsyncLock()
    
        _releaser = new Releaser(_semaphore);
        _releaserTask = Task.FromResult(_releaser);
    
    public IDisposable Lock()
    
        _semaphore.Wait();
        return _releaser;
    
    public Task<IDisposable> LockAsync()
    
        var waitTask = _semaphore.WaitAsync();
        return waitTask.IsCompleted
            ? _releaserTask
            : waitTask.ContinueWith(
                (_, releaser) => (IDisposable) releaser,
                _releaser,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);
    
    private class Releaser : IDisposable
    
        private readonly SemaphoreSlim _semaphore;
        public Releaser(SemaphoreSlim semaphore)
        
            _semaphore = semaphore;
        
        public void Dispose()
        
            _semaphore.Release();
        
    

【讨论】:

这看起来并不能解决我的问题。这提供了相同的机制,但仍会强制我的 API 在异步锁和同步锁之间进行选择 - 当它必然无法知道两者中的哪一个正在调用它时。除非我误会了? @Telastyn:您的 API 方法是同步的或异步的。如果它们是同步的,请使用Wait;如果它们是异步的,请使用WaitAsync @Telastyn:做async的正确方法是做async“一路”。是的,这意味着如果您确定既要异步又要同步,那么您最终会得到并行路径。有一些技巧可以通过异步进行异步或通过异步进行同步,但是它们都不能完美地工作或在所有情况下都可以工作。 好的,我做了一些实验,看起来异步锁应该做我需要的,因为即使在多线程场景中它也会保护共享资源。延续(似乎)只是在那个单独的线程中运行,提供正确的行为。并且资源和 API 边界之间的中间层可能会出现非异步。 补充@StephenCleary 所说的,在调用堆栈中交织同步和异步会很快导致有效的死锁。很多时候异步任务被安排在同一个线程上,如果在两个异步调用之间混合了同步阻塞调用,父异步调用可能会无限期阻塞,等待子异步调用,因为同步阻塞调用永远无法完成.因为它们都在同一个线程上运行,所以所有工作都停止了。或类似的东西。我曾经发生过这种情况,我发誓再也不会这样做了。

以上是关于如何保护可能在多线程或异步环境中使用的资源?的主要内容,如果未能解决你的问题,请参考以下文章

Spring在多线程环境下如何确保事务一致性

Spring在多线程环境下如何确保事务一致性

HttpClient在多线程环境下踩坑总结

在JAVA中ArrayList如何保证线程安全

java中的同步与异步

在多线程环境中更新 DataGrid 视图