Esistono alternative all'utilizzo di un tipo facoltativo in un ambiente con multithreading?

3

Sto facendo una coda MPMC in C ++ e vorrei scoprire quale sarebbe la migliore interfaccia per un metodo try_dequeue (sono not preoccupato per la sua implementazione). Mi piacerebbe fornire un metodo che non blocchi se la coda è vuota (come fa il mio metodo dequeue() ), ma in qualche modo notifica questo al chiamante.

Anche se fornisco un metodo bool empty() const , questo purtroppo non va bene perché per una coda multithreading fare qualcosa sulla falsariga di if(!queue.empty()) queue.dequeue(); non fornisce garanzie.

Quello che attualmente ho è T try_dequeue() , che genera un'eccezione se la coda è vuota. Questo sembra offrire l'interfaccia più intuitiva, ma una coda vuota non è certo una situazione eccezionale, e sembra un po 'strano usare le eccezioni per il controllo del flusso.

Un'altra opzione sarebbe bool try_dequeue(T* t) , che scriverebbe a t solo se la coda non era vuota e restituisce se è stato scritto t . Dopo aver usato il metodo di eccezione sopra, questo sembra probabilmente meno intrusivo, ma richiederebbe che T sia costruttivamente predefinito (o in qualche modo banalmente costruibile, quindi il chiamante può fare T t; queue.try_dequeue(&t); ) e assegnabile.

Non vorrei forzare quei requisiti su T , quindi forse un approccio di tipo Java come optional<T> try_dequeue() è in ordine. Uno dei problemi minori con questo è che dovrei implementare il mio (non posso usare boost). Forse, ancora più importante, rende l'interfaccia gonfia, nel senso che ora sto trascinando un altro tipo in cui sarebbe (si spera) evitabile, e, proprio come la situazione con% co_de di STL, ci impone di copiare una coppia , quindi "spiega" i suoi argomenti, costringe l'utente a un lungo e rituale rituale che deve essere ripetuto ogni volta che usa la funzione.

Forse mi preoccupo troppo dell'usabilità di set::insert , dato che non ha nessuno dei problemi più seri che gli altri due approcci fanno, ma mi chiedo, quale sarebbe l'approccio migliore Qui? È il optional<T> uno?

Modifica : mi è sembrato che ci sia anche un po 'di un'opzione di% co_de outlandish che sembra davvero carina (infatti questa avrà in realtà l'interfaccia più minimale che puoi avere, dove non c'è bisogno di alcuna logica esterna che imposti un optional locale sul valore restituito). Purtroppo preferirei evitare l'overhead dovuto chiamando un bool try_dequeue(std::function<void(T&)>) effettivo, e l'uso di una funzione template T non è un'opzione, poiché si tratta di una classe derivata (da un'interfaccia generica std::function ).

    
posta VF1 01.09.2014 - 07:19
fonte

2 risposte

2

Finché l'attività non consiste nel progettare una lib generica al 100% per ogni caso, preferisco l'approccio bool try_dequeue(T* t) o anche il% co_de solo per i tipi "T" sotto i tuoi team. % (poiché bool try_dequeue(T& t) non deve mai essere NULL). Con questo design, l'utilizzo sarà in genere simile a questo:

  T t;
  if(try_dequeue(t))
  {
     // do something  with t;
  }

e l'implementazione sarà simile a questa:

  bool try_dequeue(Type &t)
  {
      // ... make things thread-safe
      // ... return false if queue is empty
      // ... find index to your storage array
      t=queueStorage[foundIndex];
      // ... remove element at "foundIndex" from "queueStorage"
      return true;
  }

Questo impone alcuni requisiti sul tuo tipo T* t : avrà bisogno di un "semplice" (probabilmente senza parametri) ctor, e ha bisogno di un "operatore di assegnazione delle copie". Un costruttore di copie non sarà sufficiente. Per molti tipi di mondo reale, questi requisiti sono facili da soddisfare (specialmente quando sono sotto il tuo controllo). Nota che, secondo la regola di tre , la maggior parte delle volte è una buona idea implementare non solo un copione definito dall'utente, da solo, quindi se il tuo attuale tipo T ha solo un copistintore, considera di aggiungere anche un operatore di assegnazione copia.

    
risposta data 01.09.2014 - 22:49
fonte
2

Utilizzo del processo di eliminazione.

Assunzione: la coda è basata sul valore. Lo stesso di std::vector , ecc.

Assunzione - try_dequeue rimuoverà il valore dalla coda se il valore è disponibile e restituirà il valore al chiamante. Tecnicamente, l'essere basato sul valore significa che il valore viene copiato o spostato nel chiamante.

Il tipo di ritorno non può essere un puntatore o un riferimento. La coda ha solo valori. Una volta rimosso il valore dalla coda, non è possibile mantenere un puntatore o un riferimento. Pertanto un valore deve essere restituito.

Hai posizionato tre potenziali soluzioni in ordine inverso di preferenza.

  1. lanciare un'eccezione. ( ick )
  2. return std::pair<T, bool> i.e. come std :: set
  3. return optional<T>

Gli ultimi due sono abbastanza sintatticamente simili. Alcune funzioni di supporto potrebbero rendere l'opzione 2 più tollerabile.

    
risposta data 02.09.2014 - 01:00
fonte

Leggi altre domande sui tag