2010-04-12 16 views
7

Esiste un modo per implementare processi leggeri in stile Erlang in .NET?Processi leggeri in stile Erlang in .NET

Ho trovato alcuni progetti che implementano il modello di messaggistica di Erlang (modello degli attori). Ad esempio, Axum. Ma non ho trovato nulla su implementazione processi leggeri. Intendo più processi eseguiti in un contesto di un singolo thread OS o processo OS.

risposta

10

Penso che F # MailboxProcessor è ciò che stai cercando Alexey. Utilizzando il MailboxProcessor è possibile definire decine di migliaia di agenti all'interno di un processo .NET in modo molto simile a quello che è possibile generare decine di migliaia di processi leggeri in Erlang.

Questo MSDN post di Don Syme è un'ottima introduzione.

Se vieni su .NET da uno sfondo Erlang, tieni a mente che ti mancherà un sacco di gadget OTP (supervisori, trasparenza della posizione, mnesia, ...).

+0

Utile, presupponendo che l'OP non sia focalizzato su una particolare lingua. –

+0

Non è generalmente sicuro interpretare qualsiasi riferimento ad Axum come "Woohoo! Va tutto bene, piccola!" :) –

-2

Questo non ha senso. "Processo multiplo eseguito nel contesto di un singolo thread OS o processo OS" è logicamente inconcludente. Si tratta fondamentalmente di una cosa a livello di applicazione logica, che puoi facilmente riprodurre in .NET. Ma non esiste una cosa come un "processo all'interno di un processo" a livello di OS.

+2

"processo all'interno di un processo" - Win32 ha una fibra. – W55tKQbuRu28Q4xv

+2

Ma le fibre sono auto-programmate - FILETTATURE (e non hanno senso nella maggior parte dei casi). Un processo è definito come dotato di una barriera di memoria, a cui manca una fibra. Quindi, una fibra non è affatto un processo all'interno di un processo. – TomTom

+2

Chiaramente non hai guardato il design runtime di Erlang. Sì, è possibile implementare i processi di tipo Erlang a livello di applicazione, ma è probabile che equivalga al multitasking cooperativo, una tecnica che non ho visto utilizzare dai miei giorni DOS/Win16. Se si ha supporto nel runtime, è possibile ottenere questo effetto con il multitasking preventivo, ma sarebbe qualcosa che MS dovrebbe aggiungere a .NET. –

7

Il CLR può essere ospitato ed espone meccanismi per l'host per implementare la propria astrazione di attività. In teoria, possono essere thread, fibre, LWP - qualsiasi cosa purché l'host implementa lo necessaryinterfaces.

L'operazione corretta è un po 'difficile. MS ha avuto la possibilità di ospitare il CLR in modalità Fibra di SQL Server.

All'ultimo momento, c'erano alcuni bug di stress, quindi hanno scollegato la spina in base a Joe Duffy e Dino Vhieland (chi ha eseguito uno series about writing a custom CLR host that implements its own task abstraction - with fibers - on his blog).
In questo momento manca un po 'di impianto idraulico - ICLRTask::SwitchOut() - e anche se ci si aggira, gli stessi bug che hanno colpito MS durante l'iterazione dello stress test probabilmente infestano anche l'anima avventurosa.

Supponiamo per un momento che tutti i problemi siano in qualche modo risolti e che l'intero runtime sia preparato per funzionare su fibre, LWP, qualunque sia. C'è ancora il problema di P/Invoke che potrebbe potenzialmente chiamare in operazioni di blocco. Questo tipo di pianificazione è difficile da fare senza il supporto del kernel.

Questo è indirizzato nelle versioni a 64 bit di Windows 7 e Windows 2008 Server R2. Ora è User-Mode Scheduling che può restituire il controllo a in modalità utente - in contrapposizione a modalità kernel - scheduler se una chiamata si blocca nel kernel. Inoltre, questi thread pianificati in modalità utente sono thread reali con il proprio TLS. Questi sono grandi miglioramenti e fanno perdere molti dei mal di testa in modalità fibra.

Al momento, UMS is utilized nello Concurrency Runtime ovvero available for C++ and is part of the C Runtime Library (CRT).
Quest'ultimo significa che è possibile utilizzarlo immediatamente con Visual Studio 2010.

+0

Fa parte di questo se ne limiti l'utilizzo a F # o ad un altro linguaggio funzionale, in modo da evitare alcune delle difficoltà con un processo di pasticciamento con la memoria di un altro? –

+0

@Warren: qui la gestione delle attività non si preoccupa di impedire una scrittura thread/fibra/LWP nella memoria di un'altra. Dopotutto, è un comportamento intenzionale: lo fai ogni volta che avvii un programma multi-thread: tutti i thread accedono e condividono la memoria del processo. –

+0

@Warren: ... con ciò detto, evitare o ridurre al minimo lo stato condiviso è uno dei modi migliori per aumentare il parallelismo. Può essere fatto anche in C#, anche se la lingua non lo impone o lo incoraggia specificamente. –

4

Hai dato un'occhiata a retlang? Ho letto solo su di esso, ma non ho fatto nulla con esso, ancora ...

1

Il processo di Erlang è come il metodo di esecuzione parallelo, ma la variabile di erlang può essere associata solo una volta, quindi è thread-safe che non è in C#.

quindi avete bisogno di due cose in C#, thread sicuro e parallelo.

C# ha System.Threading.Task, è possibile eseguire molte attività in VM. C# vm pianificherà queste attività in diversi thread di lavoro.

Ma l'attività non è thread-safe, è necessario creare una classe chiamata Actor e inserire lo stato privato nell'attore.

L'attore ha un System.Threading.SynchronizationContext e molti metodi asincroni, come questo.

class Actor { 
    public SynchronizationContext _context; 

    private int value1; 
    private Dictionary<> xxx; 
    private List<> xxx; 


    public async Task Method1() { 
     await _context; 

     doSomething(); 
    } 

} 

Quando gli altri attori chiamano il metodo asincrono in questa Attore, creerà un compito, e il compito sarà pianificata da VM.

È inoltre necessario implementare un SynchronizationContext attendibile e thread-safe.

Questo è un contesto thread-safe.

public class ActorSynchronizationContext : SynchronizationContext 
{ 
    private readonly SynchronizationContext _subContext; 
    private readonly ConcurrentQueue<Action> _pending = new ConcurrentQueue<Action>(); 
    private int _pendingCount; 

    public ActorSynchronizationContext(SynchronizationContext context = null) 
    { 
     this._subContext = context ?? new SynchronizationContext(); 
    } 

    public override void Post(SendOrPostCallback d, object state) 
    { 
     if (d == null) { 
      throw new ArgumentNullException("SendOrPostCallback"); 
     } 
     _pending.Enqueue(() => d(state)); 
     if (Interlocked.Increment(ref _pendingCount) == 1) 
     { 
      try 
      { 
       _subContext.Post(Consume, null); 
      } 
      catch (Exception exp) 
      { 
       LogHelper.LogUnhandleException(exp.ToString()); 
      } 
     } 
    } 

    private void Consume(object state) 
    { 
     var surroundContext = Current; 
     SetSynchronizationContext(this); 
     do 
     { 
      Action a; 
      _pending.TryDequeue(out a); 
      try 
      { 
       a.Invoke(); 
      } 
      catch (Exception exp) 
      { 
       //Debug.LogError(exp.ToString()); 
       LogHelper.LogUnhandleException(exp.ToString()); 
      } 
     } while (Interlocked.Decrement(ref _pendingCount) > 0); 
     SetSynchronizationContext(surroundContext); 
    } 

    public override void Send(SendOrPostCallback d, object state) 
    { 
     throw new NotSupportedException(); 
    } 
    public override SynchronizationContext CreateCopy() 
    { 
     return this; 
    } 
} 

fanno SynchroniztionContext awaitable

public static class SynchroniztionContextExtensions 
{ 
    public static SynchronizationContextAwaiter GetAwaiter (this SynchronizationContext context) 
    { 
     if(context == null) throw new ArgumentNullException("context"); 
     return new SynchronizationContextAwaiter(context); 
    } 
} 

Awaiter per SynchronizationContext

public sealed class SynchronizationContextAwaiter : INotifyCompletion 
{ 
    private readonly SynchronizationContext _context; 
    public SynchronizationContextAwaiter(SynchronizationContext context) 
    { 
     if(context == null) throw new ArgumentNullException("context"); 
     _context = context; 
    } 
    public bool IsCompleted { 
     get 
     { 
      //已经在当前上下文里面了,就不需要再次切换上下文 
      return SynchronizationContext.Current == _context; 
     } 
    } 

    /// <summary> 
    /// 将Action 任务调度到 _context 控制的线程里面去执行 
    /// 
    /// var temp = e.GetAwaiter(); 
    /// </summary> 
    /// <param name="action">Action.</param> 
    public void OnCompleted(Action action) { 
     _context.Post(x=>action(), null); 
    } 
    public void GetResult(){} 
} 

poi si ottiene una classe attore con compito e thread-safe che, come processo in Erlang.