前段时间遇到一个需求,我需要一个可以阻塞线程又可以被任意线程解锁的锁。
它需要有如下几个特性:
- 可随时读取上锁状态
- 无所属线程(非排它)
- 可阻塞线程(可以使上锁者陷入阻塞、等待)
- 任意线程可随时解锁
微软貌似并没有去搞这些东东出来用,那我就自己瞎搞出来一个:
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>
/// 1.可随时读取上锁状态
/// 2.无所属线程(非排它)
/// 3.可阻塞线程(可以使上锁者陷入阻塞、等待)
/// 4.任意线程可随时解锁
/// </summary>
public class SimpleStateLock
{
private Thread _aliveWithThread = null;
private Thread _aliveWithThreadMonitor = null;
/// <summary>
/// 是否到了要销毁 SimpleStateLock 对象的时候了
/// </summary>
private bool _isDestory = false;
/// <summary>
/// 唤醒 服务员 的重要媒介
/// </summary>
private object _handlerLock = new object();
/// <summary>
/// 服务员 的 任务清单
/// </summary>
private Queue<Pipe> _actionQueue = new Queue<Pipe>();
/// <summary>
/// 实现类似管道的东西,一端发号施令陷入等待(睡眠),
/// 另一端完成指令,通知(唤醒)另一端。
/// </summary>
private class Pipe
{
public Pipe(LockAction lockAction)
{
_lockAction = lockAction;
}
/// <summary>
/// 具体任务
/// </summary>
private LockAction _lockAction;
/// <summary>
/// 一方面提供阻塞、唤醒功能,
/// 另一方面保证了 _isDone 不存在脏读脏写
/// </summary>
private object _birge = new object();
/// <summary>
/// 任务是否受理
/// </summary>
private bool _isDone = false;
public LockAction TheAction => _lockAction;
public bool IsDone => _isDone;
public void Wait()
{
Monitor.Enter(_birge);
if (_isDone)
{
Monitor.Exit(_birge);
return;
}
Monitor.Wait(_birge);
Monitor.Exit(_birge);
}
public void Notify()
{
Monitor.Enter(_birge);
_isDone = true;
Monitor.Pulse(_birge);
Monitor.Exit(_birge);
}
}
private enum LockAction
{
Lock,
UnLock,
Nothing
}
private object _lockFunctionLock = new object();
private object _unlockFunctionLock = new object();
private ReaderWriterLockSlim _theLock = new ReaderWriterLockSlim();
public bool IsLocked => _theLock.CurrentReadCount > 0;
public void Lock()
{
//确保只有一个上锁者,防止被重复上锁
Monitor.Enter(_lockFunctionLock);
if (_isDestory)
{
Monitor.Exit(_lockFunctionLock);
return;
}
// Debug.WriteLine(Thread.CurrentThread.ManagedThreadId + " Start to do.");
//如果已锁
if (IsLocked)
{
// Debug.WriteLine(Thread.CurrentThread.ManagedThreadId + " Waiting.");
//陷入等待
_theLock.EnterWriteLock();
//只要 “读锁” 被解,那么 “写锁” 必然可以占用
//如果上面一行成功执行,则“读锁”必然已解锁
_theLock.ExitWriteLock();
// Debug.WriteLine(Thread.CurrentThread.ManagedThreadId + " Wake up.");
}
//上锁
Pipe pipe = new Pipe(LockAction.Lock);
_actionQueue.Enqueue(pipe);
//唤醒 服务员
WakeUp();
//等待 服务员 完成任务的好消息
pipe.Wait();
// Debug.WriteLine(Thread.CurrentThread.ManagedThreadId + " All done.");
Monitor.Exit(_lockFunctionLock);
}
public bool TryLock()
{
bool result = false;
//确保只有一个上锁者,防止被重复上锁
Monitor.Enter(_lockFunctionLock);
if (_isDestory)
{
Monitor.Exit(_lockFunctionLock);
return false;
}
//如果没有被锁上
if (!IsLocked)
{
//上锁
Pipe pipe = new Pipe(LockAction.Lock);
_actionQueue.Enqueue(pipe);
//唤醒 服务员
WakeUp();
//等待 服务员 完成任务的好消息
pipe.Wait();
result = IsLocked;
}
Monitor.Exit(_lockFunctionLock);
return result;
}
/// <summary>
/// 解锁
/// </summary>
public void UnLock()
{
Monitor.Enter(_unlockFunctionLock);
if (_isDestory)
{
Monitor.Exit(_unlockFunctionLock);
return;
}
if (IsLocked)
{
Pipe pipe = new Pipe(LockAction.UnLock);
_actionQueue.Enqueue(pipe);
//唤醒 服务员
WakeUp();
//等待 服务员 完成任务的好消息
pipe.Wait();
}
Monitor.Exit(_unlockFunctionLock);
}
public void UnLockThenFree()
{
UnLock();
Free();
}
/// <summary>
/// 一波操作叫醒 服务员,让 服务员 干活
/// </summary>
private void WakeUp()
{
Monitor.Enter(_handlerLock);
Monitor.Pulse(_handlerLock);
Monitor.Exit(_handlerLock);
}
/// <summary>
/// 服务员 处理任务
/// </summary>
private void Handle()
{
Monitor.Enter(_handlerLock);
while (true)
{
//如果任务都完成了,服务员 就可以摸鱼一会儿了
if (0 == _actionQueue.Count)
{
Monitor.Wait(_handlerLock);
Monitor.Enter(_handlerLock);
Monitor.Exit(_handlerLock);
}
if (_isDestory)
{
if (Monitor.IsEntered(_handlerLock))
{
Monitor.Exit(_handlerLock);
}
break;
}
Pipe pipe = _actionQueue.Dequeue();
if (LockAction.Lock == pipe.TheAction)
{
if (!IsLocked)
{
_theLock?.EnterReadLock();
// Debug.WriteLine("Lock succeed.");
}
else
{
// Debug.WriteLine("No need to lock.");
}
}
else if (LockAction.UnLock == pipe.TheAction)
{
if (IsLocked)
{
_theLock?.ExitReadLock();
}
}
//任务完成,通知一下
pipe.Notify();
}
}
/// <summary>
/// 为了可以自动释放该对象,增设该函数
/// </summary>
/// <param name="thread"></param>
public void SetAliveWithThread(Thread thread)
{
_aliveWithThread = thread;
if (null == _aliveWithThreadMonitor)
{
_aliveWithThreadMonitor = new Thread(() =>
{
while (true)
{
if (!_aliveWithThread.IsAlive)
{
Free();
Debug.WriteLine(
nameof(SimpleStateLock)
+ "." + nameof(SetAliveWithThread)
+ ":" + "thread="
+ _aliveWithThread.ManagedThreadId
+ ",exit.");
break;
}
//每隔 1 秒检查一下线程情况
Thread.Sleep(1000);
}
});
_aliveWithThreadMonitor.Start();
}
}
public void Destory() => Free();
public void Release() => Free();
/// <summary>
/// 欲使得GC可以释放该对象的内存占用,调用此函数。
/// </summary>
public void Free()
{
_isDestory = true;
_actionQueue.Clear();
WakeUp();
_theLock.Dispose();
}
/// <summary>
/// 析构函数,完整地释放这个对象所占用的内存。
/// 实际上这个根本没有什么卵用,
/// 因为有线程在死循环,RunTime永远不会执行这个析构函数
/// </summary>
~SimpleStateLock()
{
Free();
}
public SimpleStateLock(bool isLocked = false)
{
//服务员上岗
new Thread(() => { Handle(); }).Start();
if (isLocked)
{
Lock();
}
}
}
}
这个……自己慢慢看吧,原理不想写了。
为什么要折腾这种锁?
因为有一种东西,叫做“事件”和“回调”。
一般情况下,为避免UI卡死,我们会在按钮的回调函数里新起线程,保证后台逻辑不会阻塞UI。
当你希望在UI上点击某个按钮之后,给状态锁上锁,然后后续线程上锁时可以陷入阻塞(线程:我就赖着不走了,多少时间我都愿意等!)。再在UI上点击某个按钮之后,给状态锁解锁,让等待上锁的线程的其中一个继续执行。你会发现做不到。
因为Monitor这个东西,是认主的,它只认线程。假如A线程上锁,B线程解锁,Monitor会抛出异常,说这个锁的拥有者是A不是B,B无权限解锁,Monitor是一种排它锁。
那有没有非排它锁呢?
不知道有没有,反正当我花了3小时面向谷歌搜索都没找到就放弃了。找还不如造呢。