2014-09-03 13 views
5

Sono in procinto di eseguire il wrapping di una libreria C per alcune codifiche in un'interfaccia pipe, ma ho trovato alcune decisioni di progettazione che devono essere prese.Considerazioni sulla concorrenza tra i tubi e codice non-pipe

Dopo aver impostato la libreria C, manteniamo un contesto encoder. Con questo, possiamo o codificare, o modificare alcuni parametri (chiamiamo l'interfaccia Haskell a quest'ultima funzione tune :: Context -> Int -> IO()). Ci sono due parti alla mia domanda:

  1. La parte di codifica è facilmente avvolto in un Pipe Foo Bar IO(), ma vorrei anche per esporre tune. Poiché l'uso simultaneo del contesto di codifica deve essere protetto da blocco, è necessario prendere un blocco ad ogni iterazione nel tubo e proteggere tune con lo stesso blocco. Ma ora sento che sto forzando chiusure nascoste sull'utente. Sto abbaiando sull'albero sbagliato qui? Come si risolve questo tipo di situazione nell'ecosistema dei tubi? In il mio caso mi aspetto che la pipa di cui fa parte il mio codice specifico esegua sempre il proprio thread, con il tuning che si verifica contemporaneamente, ma non voglio forzare questo punto di vista su nessun utente. Altri pacchetti nell'ecosistema dei tubi non sembrano forzare i loro utenti.
  2. Un contesto di codifica che non è più utilizzato deve essere inizializzato correttamente. Come fa uno, nell'ecosistema dei tubi, a garantire che tali cose (in questo caso si eseguono azioni som IO) siano prese in considerazione quando la tubazione viene distrutta?

Un esempio concreto sarebbe avvolgendo una libreria di compressione, nel qual caso il sopra può essere:

  1. La forza di compressione è sintonizzabile. Prepariamo il tubo e scorre allegramente. Come si dovrebbe procedere nel modo migliore per consentire la modifica dell'impostazione della forza di compressione mentre il tubo continua a funzionare, supponendo che l'accesso simultaneo al contesto del codec di compressione debba essere serializzato?
  2. La libreria di compressione ha allocato un mucchio di memoria fuori dall'heap di Haskell quando è stato configurato, e avremo bisogno di chiamare alcune funzioni di libreria per ripulire questo problema quando il tubo viene rimosso.

Grazie ... questo potrebbe essere tutto ovvio, ma sono abbastanza nuovo per l'ecosistema dei tubi.

Edit: La lettura di questo dopo la pubblicazione, sono abbastanza sicuro che sia la domanda più vaga che abbia mai chiesto di venire qui. Ugh! Siamo spiacenti ;-)

+3

Avete controllato la libreria 'pipes-concorrenza'? Ha un [modulo tutorial] (http://hackage.haskell.org/package/pipes-concurrency-2.0.2/docs/Pipes-Concurrent-Tutorial.html) che potresti trovare utile. Darò questa risposta più da vicino più tardi, stasera. –

+0

Oh, ho completamente trascurato le pipe-concorrenza in qualche modo! Grazie, sig. Ragazzo Pipes! Non sono ancora sicuro che copra interamente il mio caso, ma ci penserò. – gspr

+0

@gspr I singoli tubi possono modificare il parametro "forza di compressione" o possono semplicemente leggerlo? Tutte le pipe devono condividere lo stesso parametro? – danidiaz

risposta

4

Per quanto riguarda (1), la soluzione generale è quello di cambiare l' tipo alle vostre Pipe:

Pipe (Either (Context, Int) Foo) Bar IO() 

In altre parole, si accetta entrambi gli ingressi e le Footune richieste, che si elabora internamente .

Quindi cerchiamo di allora supporre che si dispone di due concomitanti Producer s corrispondenti agli ingressi e alle richieste di messa a punto:

producer1 :: Producer Foo IO() 

producer2 :: Producer (Context, Int) IO() 

È possibile utilizzare pipes-concurrency per creare un buffer che entrambi alimentano, in questo modo:

example = do 
    (output, input) <- spawn Unbounded 
    -- input :: Input (Either (Context, Int) Foo) 
    -- output :: Output (Either (Context, Int) Foo) 

    let io1 = runEffect $ producer1 >-> Pipes.Prelude.map Right >-> toOutput output 
     io2 = runEffect $ producer2 >-> Pipes.Prelude.map Left >-> toOutput output 
    as <- mapM async [io1, io2] 
    runEffect (fromInput >-> yourPipe >-> someConsumer) 
    mapM_ wait as 

È possibile ottenere ulteriori informazioni sulla libreria pipes-concurrency leggendo this tutorial.

Forzando tutte le richieste di sintonia per passare attraverso lo stesso thread singolo Pipe è possibile garantire che non si dispone di accidentalmente due invocazioni concorrenti della funzione tune.

Riguardo a (2) esistono due modi per acquisire una risorsa utilizzando pipes. L'approccio più sofisticato consiste nell'utilizzare la libreria pipes-safe, che fornisce una funzione bracket che è possibile utilizzare all'interno di un Pipe, ma che probabilmente è eccessivo per il proprio scopo e esiste solo per l'acquisizione e il rilascio di più risorse nel corso della durata di una pipe. Una soluzione più semplice è solo di utilizzare la seguente with linguaggio di acquisire tubo:

withEncoder :: (Pipe Foo Bar IO() -> IO r) -> IO r 
withEncoder k = bracket acquire release $ \resource -> do 
    k (createPipeFromResource resource) 

Poi un utente sarebbe solo scrivere:

withEncoder $ \yourPipe -> do 
    runEffect (someProducer >-> yourPipe >-> someConsumer) 

Opzionalmente si può usare il pacchetto managed, che semplifica i tipi di bit e rende più facile acquisire più risorse. Puoi saperne di più leggendo lo this blog post of mine.

+0

Brillante! Hai compreso chiaramente i miei problemi attraverso la mia descrizione confusa! – gspr

+0

È perché ho già avuto questo stesso identico problema! :) –