## Actor模型 ##
actor模型是什么可以自行查阅一下相关资料；actor模型在一定程度可以说是解决并发的最佳方案。

GeekServer的actor可以简单理解为一个线程(其实用的是线程池)，一个actor的逻辑只需保证在自己的队列里面执行即保证可线程安全，无需关心锁的问题。GeekServer的基础就是actor，一切皆可为actor。GeekServer的actor模型构建于强大的TPL DataFlow之上，底层使用内置线程池。GeekServer同时使用异步变成（async/await），让逻辑代码更加清晰明了，符合人类的思维方式。

## Actor死锁 ##
Actor模型本身是存在死锁的情况，且不容易被发现。GeekServer内部可检测环路死锁(即:A->B->C->A)，并采用调用链重入机制消除环路死锁（类似与线程的可重入性）。  
还有另外一种情况（多路死锁），比如有2个actor，A和B，一段逻辑A调用B，同时有另外一段逻辑发起了B调用A，就会出现A等B，B再等A，此时发生死锁。同理 [A->B->C，C->D->A] [A->B->C，B->C->A],这样的调用路径都可能会存在死锁。  
这种死锁无法解决，而且不确定，因为它和调用时间节点有关系，只能从设计上去规避。  
```csharp
class ActorA
{
	Task A1()
	{
        await Task.Delay(10); 
		var b = GetActorB();
		return b.SendAsync(b.B1);
	}
	
	Task A2()
	{
		var b = GetActorB();
		return b.SendAsync(b.B1);
	}
}

class ActorB
{
	Task B1()
	{
		await Task.Delay(5);
		var a = GetActorA();
		return a.SendAsync(a.A2);
	}
}

Task Call()
{
	var a = GetActorA();
	return a.SendAsync(a.A1);//这里就会触发死锁
}
```

如果得到打印日志【执行超时】很有可能就是触发了死锁。由于Actor设定了超时时间，在断点调试的时候需要手动将超时时间改长，默认10秒，在BaseActor.TIME_OUT修改。**（或者用一个Debug模式的宏来控制）**

## 多路死锁解决方案 ##
由以上的分析可以看出，多路死锁其实是由于**Await**引起的，如果所有调用都不Await则永远不会有死锁。  
这里为大家罗列了几种解决方案：(在GeekServer中可以通过ActorLimit来定制自己的检测规则)  

1. **跨Actor调用，不能Await**  
  优点：规则简单，统一，绝对不会发生死锁  
  缺点：失去了异步语法的优势，所有需要跨Actor获取返回值的，只能使用回调，代码结构散乱，书写代码不方便。 

2. **为Actor分配等级，只允许低等级await调用高等级（如：Role->Server 而Server不能等待调用Role, 推荐使用此方案）**  
  案例：公民可以去政府部门排队等待办理业务员，但是政府部门不可能去等待某个人去处理完自己的私事，再帮下一个人办理业务，顶多发短信或者打电话通知某个人做什么事情。  
  在方案1的基础上，给了使用者更多的异步调用空间。  

3. **提供一个注册接口，注册哪些Actor可以Await调用哪些Actor，并在注册时候进行检测看是否可能存在死锁**    
  这是方案2的一个更加灵活的变种。    	

4. **允许交错执行** [了解更多](https://blog.csdn.net/uddiqpl/article/details/86294520)    
  如果你的某个组件不会操作数据或者对操作顺序不敏感，交错执行是很有用的。GeekServer中的FuncComponent比较符合这个特点，但不绝对，仍需要开发人员自行判断。 （Orleans中有此方案）  

5. **允许存在多路死锁的风险，由开发人员保证不会触发死锁**  
  优点：规则简单，统一，书写代码很方便，全程异步。  
  缺点：有可能发生死锁，对编码人员能力要求较高

6. **超时规则**  
  如果发生多路死锁，选择一条调用路径并终止，保证其中一条调用路径正确。（Orleans中有此方案）  
  优缺点和方案5一样。  

7. **使用线程安全容器以及lock等**   
  但这与无锁化设计的理念冲突
