Perché non riesco a controllare se un mutex è bloccato?

23

C ++ 14 sembra aver omesso un meccanismo per verificare se un std::mutex è bloccato o meno. Vedi questa domanda SO:

link

Ci sono molti modi per aggirare questo problema, ad es. usando;

std::mutex::try_lock()
std::unique_lock::owns_lock()

Ma nessuna di queste soluzioni è particolarmente soddisfacente.

try_lock() è autorizzato a restituire un falso negativo e ha un comportamento indefinito se il thread corrente ha bloccato il mutex. Ha anche effetti collaterali. owns_lock() richiede la costruzione di un unique_lock sopra il% originalestd::mutex.

Ovviamente potrei girare il mio, ma preferirei capire le motivazioni per l'interfaccia attuale.

La possibilità di controllare lo stato di un mutex (ad esempio std::mutex::is_locked() ) non mi sembra una richiesta esoterica, quindi sospetto che il Comitato Standard abbia deliberatamente omesso questa funzione piuttosto che una svista.

Perché?

Modifica: Ok, quindi forse questo caso d'uso non è così comune come mi aspettavo, quindi illustrerò il mio particolare scenario. Ho un algoritmo di apprendimento automatico distribuito su più thread. Ogni thread opera in modo asincrono e ritorna a un pool master una volta completato un problema di ottimizzazione.

Quindi blocca un mutex principale. Il thread deve quindi scegliere un nuovo genitore da cui muovere una prole, ma può scegliere solo da genitori che al momento non hanno figli che sono stati ottimizzati da altri thread. Ho quindi bisogno di eseguire una ricerca per trovare i genitori che non sono attualmente bloccati da un altro thread. Non vi è alcun rischio che lo stato del mutex cambi durante la ricerca, poiché il mutex del thread principale è bloccato. Ovviamente ci sono altre soluzioni (attualmente sto usando un flag booleano), ma ho pensato che il mutex offra una soluzione logica a questo problema, dato che esiste per la sincronizzazione inter-thread.

    
posta quant 11.09.2016 - 15:29
fonte

6 risposte

50

Posso vedere almeno due gravi problemi con l'operazione suggerita.

Il primo è già stato citato in un commento di @ gnasher729 :

You can't really reasonably check whether a mutex is locked, because one nanosecond after the check it can get unlocked or locked. So if you wrote if (mutex_is_locked ()) … then mutex_is_locked could return the correct result, but by the time the if is executed, it is wrong.

L'unico modo per essere sicuri che la proprietà "è attualmente bloccata" di un mutex non cambi è, beh, bloccarla da solo.

Il secondo problema che vedo è che, a meno che non blocchi un mutex, il thread non si sincronizzi con il thread che aveva precedentemente bloccato il mutex. Pertanto, non è nemmeno ben definito parlare di "prima" e "dopo" e se il mutex è bloccato o meno è come chiedere se il gatto di Schrödiger è attualmente vivo senza tentare di aprire la scatola.

Se ho capito bene, allora entrambi i problemi sarebbero discutibili nel tuo caso particolare grazie al mutex master bloccato. Ma questo non mi sembra un caso particolarmente comune, quindi penso che il comitato abbia fatto la cosa giusta non aggiungendo una funzione che potrebbe essere utile in scenari molto speciali e causare danni in tutti gli altri. (Nello spirito di: "Rendi le interfacce facili da usare correttamente e difficile da usare in modo errato.")

E se posso dire, penso che il setup che hai attualmente non sia il più elegante e possa essere refactored per evitare del tutto il problema. Ad esempio, invece del thread principale che controlla tutti i potenziali genitori per uno che non è attualmente bloccato, perché non mantenere una coda di genitori pronti? Se un thread vuole ottimizzare un altro, apre il successivo dalla coda e non appena ha nuovi genitori, li aggiunge alla coda. In questo modo, non hai nemmeno bisogno del thread principale come coordinatore.

    
risposta data 11.09.2016 - 16:22
fonte
8

Sembra che si stiano utilizzando i mutex secondari per non bloccare l'accesso a un problema di ottimizzazione, ma per determinare se un problema di ottimizzazione viene ottimizzato in questo momento o meno.

Questo è completamente inutile. Avrei una lista di problemi che necessitano di ottimizzazione, un elenco di problemi in fase di ottimizzazione in questo momento e un elenco di problemi che sono stati ottimizzati. (Non prendi letteralmente "elenco", prendilo per indicare "qualsiasi struttura dati appropriata).

Le operazioni di aggiunta di un nuovo problema all'elenco di problemi non ottimizzati, o lo spostamento di un problema da una lista alla successiva, verrebbero eseguite sotto la protezione del singolo mutex "principale".

    
risposta data 11.09.2016 - 20:27
fonte
1

Oltre ai due motivi indicati nella risposta di 5gon12eder sopra, vorrei aggiungere che non è né necessario né desiderabile.

Se hai già in mano un mutex, allora è meglio sapere che lo stai tenendo! Non hai bisogno di chiedere. Proprio come se possedessi un blocco di memoria o qualsiasi altra risorsa, dovresti sapere esattamente se ne possiedi o meno e quando è opportuno rilasciare / eliminare la risorsa.
Se questo non è il tuo caso, il tuo programma è progettato male e ti stai dirigendo verso i problemi.

Se è necessario accedere alla risorsa condivisa protetta dal mutex e non si è già in possesso del mutex, è necessario acquisire il mutex. Non c'è altra opzione, altrimenti la logica del programma non è corretta.
Potresti trovare il blocco accettabile o inaccettabile, in entrambi i casi lock() o try_lock() daranno il comportamento che desideri. Tutto quello che devi sapere, in modo positivo e senza dubbio, è se hai acquisito con successo il mutex (il valore di ritorno di try_lock ti dice). È irrilevante indipendentemente dal fatto che qualcun altro lo detenga o da un errore spuria.

In tutti gli altri casi, senza mezzi termini, non sono affari tuoi. Non hai bisogno di sapere, e non dovresti sapere, o fare ipotesi (per i problemi di tempestività e sincronizzazione menzionati nell'altra domanda).

    
risposta data 11.09.2016 - 18:34
fonte
1

Come altri hanno detto, non c'è alcun caso d'uso in cui is_locked su un mutex è di alcun beneficio, ecco perché la funzione non esiste.

Il caso in cui stai riscontrando un problema è incredibilmente comune, fondamentalmente è quello che fanno i thread di lavoro, che sono una delle più comuni, se non la implementazione dei thread.

Hai una mensola con 10 scatole. Hai 4 lavoratori che lavorano con queste scatole. Come si assicura che i 4 lavoratori lavorino su scatole diverse? Il primo lavoratore prende una scatola dallo scaffale prima di iniziare a lavorarci. Il secondo lavoratore vede 9 scatole sullo scaffale.

Non ci sono mutex per bloccare le caselle, quindi non è necessario vedere lo stato del mutex immaginario sulla scatola e abusare di un mutex come booleano è sbagliato. Il mutex blocca lo scaffale.

    
risposta data 12.09.2016 - 20:46
fonte
1

Potresti voler utilizzare atomic_flag con l'ordine di memoria predefinito. Non ha razze di dati e non genera mai eccezioni come il mutex fa con più chiamate di sblocco (e abortisce in modo incontrollabile, potrei aggiungere ...). In alternativa, c'è atomic (es. Atomico [bool] o atomico [int] (con parentesi triangolari, non [] )), che ha funzioni piacevoli come load e compare_exchange_strong.

    
risposta data 20.02.2017 - 00:35
fonte
0

Voglio aggiungere un caso d'uso per questo: Permetterebbe ad una funzione interna di garantire come precondizione / asserzione che il chiamante stia effettivamente tenendo il blocco.

Per le classi con molte di queste funzioni interne, e possibilmente molte funzioni pubbliche che le chiamano, potrebbe garantire che qualcuno aggiungesse un'altra funzione pubblica che chiamava quella interna effettivamente acquisito il blocco.

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

};
    
risposta data 14.08.2018 - 19:14
fonte

Leggi altre domande sui tag