- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
当线程 A 在等待一个同步构造,另一个线程 B 持有构造一直不释放,那么就会导致线程 A 阻塞。同步构造有用户模式构造和内核模式构造.
活锁和死锁: 当线程获取不到资源,从而不停在 CPU 上自旋等待资源,就会形成活锁。这是通过用户构造实现的。 当线程获取不到资源,被操作系统阻塞,就会形成死锁。这是通过内核构造实现的.
.Net 提供了两种用户构造,易变构造 Volatile、互锁构造 Interlocked,这两种构造都提供了原子性读写的功能。 .Net 提供了基于易变构造、互锁构造、SpinWait 实现的自旋锁 SpinLock.
原子性读写: 在 32 位 CPU 中,CPU 一次只能存储 32 位的数据,所以如果是 64 位的数据类型(如 double),就得执行两次 MOV 指令,所以在 32 位 CPU 和 32 位操作系统中,不同线程对 64 位的数据类型进行读写可能得到不同的结果。原子性读写就是保证了即使是 64 位的数据类型,不同线程读写也会得到相同的结果。现在的 CPU 和操作系统基本都是 64 位的,所以一般也不会遇到这种问题.
Volatile 一般用于阻止编译器代码优化,编译器优化代码会优化掉一些在单线程情况下无用的变量或者语句,在多线程代码下有时候会导致程序运行结果跟设计的不一样。 Volatile.Read() 强制对变量的取值必须在调用时读取,Volatile.Write() 强制对变量的赋值必须在调用时写入.
/// <summary>
/// 在 debug 模式下不开启代码优化,所以需要用 release 模式下生成。
/// 执行 dotnet build -c release --no-incremental 后运行代码,如果没有标记为易变,则不会打印 x。
/// </summary>
public void Test2()
{
var switchTrue = false;
var t = new Thread(() =>
{
var x = 0;
while (!switchTrue) // 如果没有标记变量为易变,编译器会把 while(!switchTrue) 优化为 while(true) 从而导致永远不会打印出 x 的值
//while (!Volatile.Read(ref switchTrue)) // 标记为易变,可以保证在调用时才进行取值,不会进行代码优化。
{
x++;
}
Console.WriteLine($"x: {x}");
});
t.IsBackground = true;
t.Start();
Thread.Sleep(100);
switchTrue = true;
Console.WriteLine("ok");
}
/// <summary>
/// 用 Interlocked 实现一个简单的自旋锁
/// 注意:
/// 1. 自旋锁在获取不到锁的时候,会进行空转。所以在自旋的时候,会占用 CPU,所以一般不在单 CPU 机器上用。
/// 2. 当占有锁的线程优先级比获取锁的线程更低的时候,会导致占有锁的线程一直获取不到CPU进行工作,从而无法释放锁,导致活锁。
/// 所以使用自旋锁的线程,应该禁用线程优先级提升功能。
/// </summary>
public class SimpleSpinLock
{
private int _count;
public void Enter()
{
while (true)
{
if (Interlocked.Exchange(ref _count, 1) == 0)
{
return;
}
}
}
public void Exit()
{
Volatile.Write(ref _count, 0);
}
}
/// <summary>
/// 使用 Interlocked 实现的单例,轻量且简单。
/// 可能会同时调用多次构造函数,所以适合构造函数没有副作用的类
/// </summary>
internal class DoubleCheckLocking3
{
private static DoubleCheckLocking3? _value;
private DoubleCheckLocking3()
{
}
private DoubleCheckLocking3 GetInstance()
{
if (_value != null) return _value;
Interlocked.CompareExchange(ref _value, new DoubleCheckLocking3(), null);
return _value;
}
}
/// <summary>
/// 使用 lock 和双检索实现的单例化
/// </summary>
internal class DoubleCheckLocking
{
private static DoubleCheckLocking? _value;
private static readonly object _lock = new();
private DoubleCheckLocking()
{
}
public static DoubleCheckLocking GetInstance()
{
if (_value != null) return _value;
lock (_lock)
{
if (_value == null)
{
var t = new DoubleCheckLocking();
Volatile.Write(ref _value, t);
}
}
return _value;
}
}
.Net 提供了一个轻量化的同步构造 SpinLock,很适合在不常发生竞争的场景使用。如果发生竞争了,会先在 CPU 上自旋一段时间,如果还不能获取到资源,就会让出 CPU 控制权给其他线程(使用 SpinWait 实现的).
重入锁(Re-Enter): 就是一个线程调用了 SpinLock.Enter() 后,没有调用 SpinLock.Exit(),再次调用了 SpinLock.Enter().
/// <summary>
/// 测试 SpinLock 重入锁
/// </summary>
public void Test3()
{
var spinLock = new SpinLock(true); // 如果传 true,如果 SpinLock 重入锁,就会抛出异常,传 false 则不会,只会死锁。
ThreadPool.QueueUserWorkItem(_ => DoWork());
void DoWork()
{
var lockTaken = false;
for (int i = 0; i < 10; i++)
{
try
{
Thread.Sleep(100);
if (!spinLock.IsHeldByCurrentThread) // SpinLock.IsHeldByCurrentThread 可以判断是不是当前线程拥有锁,如果是就不再获取锁
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 获取锁,i 为 {i}");
spinLock.Enter(ref lockTaken);
}
//spinLock.Enter(ref lockTaken); // 重入锁会死锁
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
if (lockTaken) // 使用 lockTaken 来判断锁是否已经被持有
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 释放锁");
spinLock.Exit();
}
Console.WriteLine("结束");
}
}
/// <summary>
/// 测试装箱拆箱问题
/// </summary>
public void Test4()
{
var spinLock = new SpinLock(false);
Task.Run(() => DoWork(ref spinLock));
Task.Run(() => DoWork(ref spinLock));
// SpinLock 是 Struct 类型,要注意装箱拆箱的问题,试试看不加 ref 关键字的效果
void DoWork(ref SpinLock spinLock)
{
var lockTaken = false;
Thread.Sleep(500);
spinLock.Enter(ref lockTaken);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 获取锁");
}
}
.Net 提供了 System.Threading.WaitHandle 和 WaitHandle 的子类来支持内核构造,WaitHandle 封装内核同步构造的句柄,并且提供了操作的方法,并且每个方法都会在调用处建立内存屏障.
WaitHandle 有以下实现类,这些类定义了一个信号机制,根据信号去释放线程或者阻塞线程,用于在多线程的场景下访问共享资源: WaitHandle:抽象基类,封装了系统内核构造的句柄。继承自 MarshalByRefObject,所以可以跨进程和 domain 边界.
WaitHandle 有以下常用方法:
public class WaitHandleDemo
{
/// <summary>
/// 测试 WaitHandle.WaitAll(), 成功运行返回 true, 支持超时,当超时时,返回 false
/// WaitHandle.WaitAny(), 成功运行返回对应的 索引,支持超时,当超时时,返回 WaitHandle.WaitTimeout
/// </summary>
public void Test()
{
var waitHandleList = new WaitHandle[] { new AutoResetEvent(false), new AutoResetEvent(false) };
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[0]);
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[1]);
var timeout = WaitHandle.WaitAll(waitHandleList);
Console.WriteLine($"是否超时:{!timeout},WaitHandle.WaitAll() 结束");
Thread.Sleep(500);
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[0]);
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[1]);
timeout = WaitHandle.WaitAll(waitHandleList,1000);
Console.WriteLine($"是否超时:{!timeout},WaitHandle.WaitAll() 结束");
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[0]);
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[1]);
var index = WaitHandle.WaitAny(waitHandleList);
Console.WriteLine($"{index} 已经结束运行,WaitHandle.WaitAny() 结束");
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[0]);
ThreadPool.QueueUserWorkItem(DoWork, waitHandleList[1]);
index = WaitHandle.WaitAny(waitHandleList, 1000);
Console.WriteLine($"是否超时:{WaitHandle.WaitTimeout == index},WaitHandle.WaitAny() 结束");
void DoWork(object? state)
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 开始");
var r = new Random();
var interval = 1000 * r.Next(2, 10);
Thread.Sleep(interval);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 结束");
((AutoResetEvent)state).Set();
}
}
/// <summary>
/// 测试 WaitHandle.SignalAndWait(), 成功运行返回 true, 支持超时,当超时时,返回 false
/// </summary>
public void Test2()
{
var are = new AutoResetEvent(false);
var are2 = new AutoResetEvent(false);
foreach (var i in Enumerable.Range(1,5))
{
Console.WriteLine($"按下 Enter 启动线程 {i}");
Console.ReadLine();
var t = new Thread(DoWork)
{
Name = $"线程 {i}"
};
t.Start();
WaitHandle.SignalAndWait(are, are2); // 给 are 发信号,同时等待 are2
}
Console.WriteLine("全部线程运行结束");
void DoWork()
{
are.WaitOne();
Console.WriteLine($"{Thread.CurrentThread.Name} 开始");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.Name} 结束");
are2.Set();
}
}
}
EventWaitHandle、ManualResetEvent、AutoResetEvent 是内核同步构造,EventWaitHandle 由内核维护了一个 bool 变量,为 false 的时候阻塞线程,为 true 的时候释放线程。ManualResetEvent、AutoResetEvent 继承自 EventWaitHandle,所以拥有一样的行为,同时可以跨进程跨 domain 通信。 ManualResetEventSlim 并不继承自 EventWaitHandle,只是功能跟 ManualResetEvent、AutoResetEvent 一样的混合同步构造,使用用户构造和内核构造混合实现,遇到竞争的情况,会先自旋一下,还无法获取到资源,再使用内核构造阻塞线程,所以有更好的性能.
/// <summary>
/// 测试 EventWaitHandle 跟其他线程通信
/// </summary>
public void Test2()
{
EventWaitHandle ewh;
if (EventWaitHandle.TryOpenExisting("multi-process", out ewh))
{
Console.WriteLine("等待 EventWaitHandle");
ewh.WaitOne();
Console.WriteLine("结束运行");
}
else
{
ewh = new EventWaitHandle(false, EventResetMode.AutoReset, "multi-process");
while (true)
{
Console.WriteLine("按下 Enter 跟其他线程通讯");
Console.ReadLine();
ewh.Set();
}
}
}
/// <summary>
/// 测试 ManualResetEvent.Set() 和 ManualResetEvent.Reset()
/// </summary>
public void Test1()
{
var mre = new ManualResetEvent(false);
foreach (var i in Enumerable.Range(1, 3))
{
StartThread(i);
}
Thread.Sleep(500);
Console.WriteLine("按下 Enter 调用 Set(),释放所有线程");
Console.ReadLine();
mre.Set();
Thread.Sleep(500);
Console.WriteLine("ManualResetEvent 内部值为 true 时,不会阻塞线程。按下 Enter 启动一个新线程进行测试");
Console.ReadLine();
StartThread(4);
Thread.Sleep(500);
Console.WriteLine("按下 Enter 调用 Reset(),可以再次阻塞线程");
Console.ReadLine();
mre.Reset();
Thread.Sleep(500);
foreach (var i in Enumerable.Range(5, 2))
{
StartThread(i);
}
Thread.Sleep(500);
Console.WriteLine("按下 Enter 调用 Set(),释放所有线程,结束 demo");
Console.ReadLine();
mre.Set();
Thread.Sleep(500);
void StartThread(int i)
{
var t = new Thread(() =>
{
Console.WriteLine($"{Thread.CurrentThread.Name} 启动并调用 WaitOne()");
mre.WaitOne();
Console.WriteLine($"{Thread.CurrentThread.Name} 结束运行");
})
{
Name = $"线程_{i}"
};
t.Start();
}
}
public void Test()
{
var are = new AutoResetEvent(false);
Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(500);
Console.WriteLine("按下 Enter 释放一个线程");
Console.ReadLine();
are.Set();
}
});
foreach (var i in Enumerable.Range(1,5))
{
var t = new Thread(DoWork);
t.Name = $"线程 {i}";
t.Start();
}
void DoWork()
{
Console.WriteLine($"{Thread.CurrentThread.Name} 开始");
are.WaitOne();
Console.WriteLine($"{Thread.CurrentThread.Name} 结束");
}
}
Semaphore 是一个内核构造,由内核维护了一个 Int32 变量,为当值为 0 时,阻塞线程,调用 Semaphore.Release() 会把变量加 1,调用 WaitHandle.WaitOne() 会把变量减 1。 SemaphoreSlim 是一个混合构造,功能跟 Semaphore 一致,使用用户构造和内核构造混合实现,遇到竞争的情况,会先自旋一下,还无法获取到资源,再使用内核构造阻塞线程,所以有更好的性能.
/// <summary>
/// 测试 Semaphore
/// </summary>
public void Test4()
{
var pool = new Semaphore(1, 3); // 初始化计数 1,最大计数 3
foreach (var i in Enumerable.Range(1, 5))
{
var t = new Thread(DoWork);
t.Name = $"线程 {i}";
t.Start();
}
Thread.Sleep(500);
Console.WriteLine("按下 Enter 释放 3 个线程");
Console.ReadLine();
pool.Release(3); // 计数加3
Thread.Sleep(500);
Console.WriteLine("再按下 Enter 释放 1 个线程");
Console.ReadLine();
pool.Release(); // 计数加1
void DoWork()
{
Console.WriteLine($"{Thread.CurrentThread.Name} 开始");
pool.WaitOne(); // 计数减1
Console.WriteLine($"{Thread.CurrentThread.Name} 结束");
}
}
/// <summary>
/// 测试跟其他进程通讯
/// </summary>
public void Test5()
{
Semaphore pool;
if (Semaphore.TryOpenExisting("multi-process", out pool))
{
Console.WriteLine("等待 Semaphore");
pool.WaitOne();
Console.WriteLine("结束");
}
else
{
pool = new Semaphore(0, 1, "multi-process"); // 最大计数设置为 1,每次只解除一个阻塞。
while (true)
{
Console.WriteLine("按下 Enter 跟其他线程通讯");
Console.ReadLine();
pool.Release();
}
}
}
Mutex 是一个内核构造,经常用于进程同步(如保证只有程序只能有一个进程)。功能跟 AutoResetEvent(false) 和 Semaphore(0,1) 类似,每次只能阻塞一个线程或者进程。 Mutex 跟 EventWaitHandle 和 Semaphore 不一样的地方是,Mutex 要求线程一致(也就是获取和释放都必须在同一个线程),并且支持重入锁.
/// <summary>
/// Mutex 支持重入锁,支持线程一致
/// </summary>
public void Test()
{
var mutex = new Mutex(false);
var count = 0;
DoWork(mutex);
void DoWork(Mutex mutex)
{
try
{
mutex.WaitOne();
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 获取 Mutex");
Interlocked.Increment(ref count);
Thread.Sleep(1000);
if (Interlocked.CompareExchange(ref count, 3, 3) == 3)
{
return;
}
DoWork(mutex);
}
finally
{
mutex.ReleaseMutex(); // 调用几次 WaitOne() 就必须调用几次 ReleaseMutex(),并且调用 WaitOne() 和 ReleaseMutex() 必须在同一个线程。
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 释放 Mutex");
}
}
}
从上面可以看出,.Net 内核构造功能比用户构造强大得多,所以看起来似乎直接使用内核构造,而不使用用户模式构造更加明智。 但是用户构造会比内核构造快,所以在不常发生竞争或者性能敏感的场景下,使用用户构造会是一个更加优秀的做法。接下来用一个没有竞争的空方法测试一下快多少.
internal class PerformanceDemo
{
/// <summary>
/// 测试用户模式构造和内核模式构造,在锁没有发生竞争的情况下的性能差距
/// </summary>
public void Test()
{
var count = 1000 * 10000;
var spinLock = new SpinLock(false);
var are = new AutoResetEvent(true);
var pool = new Semaphore(1, 1);
var sw = Stopwatch.StartNew();
foreach (var _ in Enumerable.Range(0, count))
{
var lockTaken = false;
spinLock.Enter(ref lockTaken);
DoWork();
spinLock.Exit(lockTaken);
}
Console.WriteLine($"在没有竞争的场景下,执行一个空方法一千万次,SpinLock 耗时:{sw.ElapsedMilliseconds} ms");
sw.Restart();
foreach (var _ in Enumerable.Range(0, count))
{
are.WaitOne();
DoWork();
are.Set();
}
Console.WriteLine($"在没有竞争的场景下,执行一个空方法一千万次,AutoResetEvent 耗时:{sw.ElapsedMilliseconds} ms");
sw.Restart();
foreach (var _ in Enumerable.Range(0, count))
{
pool.WaitOne();
DoWork();
pool.Release();
}
Console.WriteLine($"在没有竞争的场景下,执行一个空方法一千万次,Semaphore 耗时:{sw.ElapsedMilliseconds} ms");
// 空方法
void DoWork()
{
}
}
}
// 输出:
// 在没有竞争的场景下,执行一个空方法一千万次,SpinLock 耗时:184 ms
// 在没有竞争的场景下,执行一个空方法一千万次,AutoResetEvent 耗时:5449 ms
// 在没有竞争的场景下,执行一个空方法一千万次,Semaphore 耗时:5366 ms
最终在我的机子上测试,在没有发生竞争的场景下,.NET 提供的用户构造性能是内核构造的 30 倍,所以性能差距还是非常大的.
用户构造在遇到竞争,在长时间获取不到资源的场景,会一直在 CPU 上自旋,既浪费 CPU 时间,又耽误其他线程执行,内核构造在操作系统的协调下,会把获取不到资源的线程阻塞,不会浪费 CPU 时间。 内核构造在没有竞争的场景下,性能会比用户构造差几十倍。 混合构造就是组合用户构造和内核构造的实现,遇到竞争的时候,先使用用户构造自旋一下,自旋一段时间还没获取到资源,就使用内核构造阻塞线程,这样就能结合两种构造的优点了。 .Net 提供了 ManualResetEventSlim、SemaphoreSlim、Monitor、lock 关键字、ReaderWriterLockSlim、CountDownEvent、Barrier 等混合构造,可以在不同的场景下使用.
通过这个例子可以了解一下是怎么组合内核构造和用户构造的.
/// <summary>
/// 一个简单的混合构造,组合 AutoResetEvent 和 Interlocked 实现
/// </summary>
internal class SimpleHybridLock : IDisposable
{
private int _waiter;
private AutoResetEvent _waiterLock = new(false);
public void Enter()
{
if (Interlocked.Increment(ref _waiter) == 1)
{
return;
}
_waiterLock.WaitOne();
}
public void Exit()
{
if (Interlocked.Decrement(ref _waiter) == 0)
{
return;
}
_waiterLock.Set();
}
public void Dispose()
{
_waiterLock.Dispose();
}
}
lock 关键字是最常使用的同步构造了,lock 可以锁定一个代码块,保证每次只有一个线程访问执行该代码块,lock 是基于 Montor 实现的,通过 try{...}finally{...} 把代码块包围起来.
/// <summary>
/// 测试 Monitor.Wait(object)、Monitor.Pulse(object)、Monitor.PulseAll(object)
/// 注意点:
/// 调用 Wait()、Pulse()、PulseAll() 也必须先调用 Enter() 获取锁,退出的时候也必须调用 Exit() 释放锁
/// </summary>
public void Test()
{
var lockObj = new object();
Task.Factory.StartNew(() =>
{
Thread.Sleep(500);
Console.WriteLine("按下 c 调用 Monitor.Pulse(object)");
if (Console.ReadKey().Key == ConsoleKey.C)
{
try
{
Monitor.Enter(lockObj);
Monitor.Pulse(lockObj);
}
finally
{
Monitor.Exit(lockObj);
}
}
Thread.Sleep(500);
if (Console.ReadKey().Key == ConsoleKey.C)
{
try
{
Monitor.Enter(lockObj);
Monitor.PulseAll(lockObj);
}
finally
{
Monitor.Exit(lockObj);
}
}
});
Parallel.Invoke(DoWork, DoWork, DoWork);
void DoWork()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 启动");
try
{
Monitor.Enter(lockObj);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 获得 Monitor");
Thread.Sleep(100);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 调用 Monitor.Wait()");
Monitor.Wait(lockObj);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 重新获得 Monitor");
}
finally
{
Monitor.Exit(lockObj);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 释放 Monitor");
}
}
}
/// <summary>
/// 测试 Monitor.Enter(字符串)
/// 因为字符串会被留用,所以会导致不同线程间互斥访问。
/// </summary>
public void Test2()
{
var mre = new ManualResetEventSlim(false);
Task.Run(() =>
{
Console.WriteLine("按下 c 启动");
if (Console.ReadKey().Key == ConsoleKey.C)
{
mre.Set();
}
});
Parallel.Invoke(DoWork, DoWork, DoWork);
void DoWork()
{
mre.Wait();
try
{
Monitor.Enter("1");
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 进入同步代码块");
Thread.Sleep(1000);
}
finally
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 退出同步代码块");
Monitor.Exit("1");
}
}
}
/// <summary>
/// 测试 Monitor.Enter(值类型)
/// 因为 Monitor.Enter(object) 参数是 object,所以值类型必须装箱,那样其实就会有问题了。
/// 值类型在堆栈上,没有引用,引用类型在堆上,有引用,所以装箱就是在堆上新建一个实例,然后复制栈上值的内容,拆箱就是把堆上实例的值,复制到栈上。
/// </summary>
public void Test3()
{
var mre = new ManualResetEventSlim(false);
var i = 1;
//Object o = i;
Task.Run(() =>
{
Console.WriteLine("按下 c 启动");
if (Console.ReadKey().Key == ConsoleKey.C)
{
mre.Set();
}
});
Parallel.Invoke(DoWork, DoWork, DoWork);
void DoWork()
{
mre.Wait();
object o = i;
try
{
Monitor.Enter(o);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 进入同步代码块");
Thread.Sleep(1000);
}
finally
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 退出同步代码块");
Monitor.Exit(o);
}
}
}
CountdownEvent 是一个混合构造,经常用于 fork/join 等场景,就是等待多个并行任务完成,再执行下一个任务。CountdownEvent 内部会维护一个计数,当计数为 0 时,解除线程的阻塞.
public void Test2()
{
var queue = new ConcurrentQueue<int>(Enumerable.Range(1, 100));
var cde = new CountdownEvent(queue.Count);
var doWork = new Action(() =>
{
while (queue.TryDequeue(out var result))
{
Thread.Sleep(100);
Console.WriteLine(result);
cde.Signal();
}
});
var _ = Task.Run(doWork); // fork
var _2 = Task.Run(doWork); // fork
var complete = new Action(() =>
{
cde.Wait(); // join
Console.WriteLine($"queue Count {queue.Count}");
});
var t = Task.Run(complete);
var t2 = Task.Run(complete);
Task.WaitAll(t, t2);
Console.WriteLine($"CountdownEvent 重新初始化");
cde.Reset(2); // 调用 Reset() 将 cde 重新初始化
cde.AddCount(10); // 调用 AddCount() cde 内部计数 + 1
var cts = new CancellationTokenSource(1000); // 测试超时机制
try
{
cde.Wait(cts.Token);
}
catch (Exception e)
{
Console.WriteLine(e);
}
cde.Dispose();
}
Barrier 是一个混合构造,可以通过 participantCount 来指定一个数值,同时会维护一个内部数值 total,每次调用 SignalAndWait() 的时候,阻塞调用线程,同时把total 加 1,等到 total == participantCount,调用 postPhaseAction,通过 postPhaseAction 来确定汇总每个线程的数据,并且执行下个阶段的工作。 Barrier 适合一种特殊场景,把一个大任务拆分成多个小任务,然后每个小任务又会分阶段执行。像是 Parallel 的 Plus 版,如果任务步骤很多,用 Parallel 来分拆很麻烦,可以考虑用 Barrier.
public class BarrierDemo
{
public void Test()
{
var words = new string[] { "山", "飞", "千", "鸟", "绝" };
var words2 = new string[] { "人", "灭", "径", "万", "踪" };
var solution = "千山鸟飞绝,万径人踪灭";
bool success = false;
var barrier = new Barrier(2, b =>
{
var sb = new StringBuilder();
sb.Append(string.Concat(words));
sb.Append(',');
sb.Append(string.Concat(words2));
Console.WriteLine(sb.ToString());
//Thread.Sleep(1000);
if (string.CompareOrdinal(solution, sb.ToString()) == 0)
{
success = true;
Console.WriteLine($"已完成");
}
Console.WriteLine($"当前阶段数:{b.CurrentPhaseNumber}");
});
var t = Task.Run(() => DoWork(words));
var t2 = Task.Run(() => DoWork(words2));
Console.ReadLine();
void DoWork(string[] words)
{
while (!success)
{
var r = new Random();
for (int i = 0; i < words.Length; i++)
{
var swapIndex = r.Next(i, words.Length);
(words[swapIndex], words[i]) = (words[i], words[swapIndex]);
}
barrier.SignalAndWait();
}
}
}
}
ReaderWriterLockSlim 是一个混合构造。一般场景中在读取数据的时候,不会涉及到数据的修改,所以可以并发读取,在修改数据的时候,才会涉及到数据的修改,所以应该互斥修改。其他同步构造无论读取还是修改数据都是锁定的,所以 .Net 提供了一个读写锁 ReaderWriterLockSlim。 ReaderWriterLockSlim 的逻辑如下:
/// <summary>
/// ReaderWriterLockerSlim 用法
/// </summary>
internal class Transaction2
{
private DateTime _timeLastTrans;
public DateTime TimeLastTrans
{
get
{
_lock.EnterReadLock();
Thread.Sleep(1000);
var t = _timeLastTrans;
Console.WriteLine($"调用 ReadLock {Thread.CurrentThread.ManagedThreadId}");
_lock.ExitReadLock();
return t;
}
}
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
public void PerformTransaction()
{
_lock.EnterWriteLock();
_timeLastTrans = DateTime.Now;
Console.WriteLine($"调用 WriteLock {Thread.CurrentThread.ManagedThreadId}");
_lock.ExitWriteLock();
}
public void Test()
{
PerformTransaction();
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(TimeLastTrans));
PerformTransaction();
Thread.Sleep(500); // 就算睡眠500ms,在锁释放后,依旧先进行读操作,读完才有写操作。
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(TimeLastTrans));
}
}
回顾了一下知识,总结了一下,发现自己又学到不少。下次回顾一下 Task 的知识。 源码 https://github.com/yijidao/blog/tree/master/TPL/ThreadDemo/ThreadDemo3 。
最后此篇关于C#线程同步查漏补缺的文章就讲到这里了,如果你想了解更多关于C#线程同步查漏补缺的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在实现 IMAP 客户端,但 IMAP 邮箱同步出现问题。 首先,可以从 IMAP 服务器获取新邮件,但我不知道如何从邮箱中查找已删除的邮件。 我是否应该从服务器获取所有消息并将其与本地数据进行比
我研究线程同步。当我有这个例子时: class A { public synchronized void methodA(){ } public synchronized void met
嗨,我做了一个扩展线程的东西,它添加了一个包含 IP 的对象。然后我创建了该线程的两个实例并启动它们。他们使用相同的列表。 我现在想使用 Synchronized 来阻止并发更新问题。但它不起作用,我
我正在尝试使用 FTP 定期将小数据文件从程序上传到服务器。用户从使用 javascript XMLHttpRequest 函数读取数据的网页访问数据。这一切似乎都有效,但我正在努力解决由 FTP 和
我不知道如何同步下一个代码: javascript: (function() { var s2 = document.createElement('script'); s2.src =
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 7 年前。 Improve this qu
一 点睛 1 Message 在基于 Message 的系统中,每一个 Event 也可以被称为 Message,Message 是对 Event 更高一个层级的抽象,每一个 Message 都有一个
一 点睛 1 Message 在基于 Message 的系统中,每一个 Event 也可以被称为 Message,Message 是对 Event 更高一个层级的抽象,每一个 Message 都有一个
目标:我所追求的是每次在数据库中添加某些内容时(在 $.ajax 到 Submit_to_db.php 之后),从数据库获取数据并刷新 main.php(通过 draw_polygon 更明显)。 所
我有一个重复动画,需要与其他一些 transient 动画同步。重复动画是一条在屏幕上移动 4 秒的扫描线。当它经过下面的图像时,这些图像需要“闪烁”。 闪烁的图像可以根据用户的意愿来来去去和移动。它
我有 b 个块,每个块有 t 个线程。 我可以用 __syncthreads() 同步特定块中的线程。例如 __global__ void aFunction() { for(i=0;i #
我正在使用azure表查询来检索分配给用户的所有错误实体。 此外,我更改了实体的属性以声明该实体处于处理模式。 处理完实体后,我将从表中删除该实体。 当我进行并行测试时,可能会发生查询期间,一个实体已
我想知道 SQLite 是如何实现它的。它基于文件锁定吗?当然,并不是每个访问它的用户都锁定了整个数据库;那效率极低。它是基于多个文件还是仅基于一个大文件? 如果有人能够简要概述一下 sqlite 中
我想post到php,当id EmpAgree1时,然后它的post变量EmpAgree=1;当id为EmpAgree2时,则后置变量EmpAgree=2等。但只是读取i的最后一个值,为什么?以及如何
CUBLAS 文档提到我们在读取标量结果之前需要同步: “此外,少数返回标量结果的函数,例如 amax()、amin、asum()、rotg()、rotmg()、dot() 和 nrm2(),通过引用
我知道下面的代码中缺少一些内容,我的问题是关于 RemoteImplementation 中的同步机制。我还了解到该网站和其他网站上有几个关于 RMI 和同步的问题;我在这里寻找明确的确认/矛盾。 我
我不太确定如何解决这个问题......所以我可能需要几次尝试才能正确回答这个问题。我有一个用于缓存方法结果的注释。我的代码目前是一个私有(private)分支,但我正在处理的部分从这里开始: http
我对 Java 非常失望,因为它不允许以下代码尽可能地并发移动。当没有同步时,两个线程会更频繁地切换,但是当尝试访问同步方法时,在第二个线程获得锁之前以及在第一个线程获得锁之前再次花费太长时间(比如
过去几周我一直在研究java多线程。我了解了synchronized,并理解synchronized避免了多个线程同时访问相同的属性。我编写此代码是为了在同一线程中运行两个线程。 val gate =
我有一个关于 Java 同步的简单问题。 请假设以下代码: public class Test { private String address; private int age;
我是一名优秀的程序员,十分优秀!