Learn Orleans 03 - 无锁

上一章,我们通过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 。

无锁就没有资源竞争,对于分布式系统中的重要性是不言而喻的。

猜你喜欢

转载自www.cnblogs.com/wswind/p/12563354.html