Learn Orleans 03 - 无锁
https://github.com/wswind/learn-orleans/tree/master/01.HelloWorld
上一章,我们通过Orleans完成了一个最简单的HelloWorld样例。实现了面向对象风格的RPC调用。
本章,我来讲解Actor的无锁机制。
源码下载地址:https://github.com/wswind/learn-orleans
首先我们来看一个经典的多线程例子
class Program
{
static int count = 0;
static void Main(string[] args)
{
var tasks = new List<Task>();
for (int i = 0; i < 100; i++)
{
var task = Task.Run(() => count++);
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(count);
}
}
我们建立了100个线程来执行count++,但是由于没有使用对象锁,程序并发执行会导致最终结果不正确:
>dotnet run
86
要处理这个问题也很简单,那就是使用线程锁:
static void Main(string[] args)
{
object locker = new object();
var tasks = new List<Task>();
for (int i = 0; i < 100; i++)
{
var task = Task.Run(() => {
lock(locker)
{
count++;
}
});
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(count);
}
首先由于资源发生了竞争,线程锁会影响多线程的运行效率。
其次,在分布式系统中,不同服务运行在不同机器的不同进程中,想锁就没那么容易了,需要通过其他更为复杂的架构锁(如Redis)来处理。
而Orleans面对这类问题,它是不用锁的。如我们在第一章中所说,Actor有一个邮箱,它会依次执行消息。因此我们可以理解Actor是单线程地在依次执行任务,因此也就不存在资源竞争问题。
为了实现这种Actor机制,Orleans其实在运行时中,封装了底层处理。开发者无需关心底层细节,只需要记住,Interface接口要求需要统一返回Task或泛型的Task<>。
我们修改上一章的代码,添加两个接口,来模拟上面的例子看在Orleans中会如何。
Task AddCount();
Task<int> GetCount();
并在Grain中实现
private int _count;
public Task AddCount()
{
_count++;
return Task.CompletedTask;
}
public Task<int> GetCount()
{
return Task.FromResult(_count);
}
在client调用时,通过多线程调用
var tasks = new List<Task>();
for(int i=0;i<100;i++)
{
var task = Task.Run(() => friend.AddCount());
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(await friend.GetCount());
可以得到返回结果仍旧是100 。
无锁就没有资源竞争,对于分布式系统中的重要性是不言而喻的。
本文采用 知识共享署名 4.0 国际许可协议 进行许可