2010-10-05 8 views
5

Mentre eseguivo una modifica in una classe con una lunga storia, ero ostacolato da una particolare abitudine dell'architetto di avvolgere la sua sequenza va_start -> va_end in un mutex. Il changelog per quell'aggiunta (che è stata fatta circa 15 anni fa, e non rivista da allora) ha notato che era perché va_start et. non era tutto rientranti.rientranti va_start (ecc.)?

Non ero a conoscenza di problemi di questo tipo con va_start, poiché ho sempre pensato che fosse solo una macro per una matematica con stack-pointer. C'è qualcosa di cui non sono a conoscenza? Non voglio cambiare questo codice se ci saranno effetti collaterali.

In particolare, la funzione in questione assomiglia molto a questo:

void write(const char *format, ...) 
{ 
    mutex.Lock(); 
    va_list args; 
    va_start(args, format); 
    _write(format, args); 
    va_end(args); 
    mutex.Unlock(); 
} 

questo è chiamato da più thread.

+0

Se il comando _write blocca l'IO seriale a livello di byte o di livello buffer, è comunque possibile avere un blocco di livello superiore per rendere la chiamata write() più atomica. Non c'è niente di più fastidioso nell'avere 2 thread chiamate 'printf (" foo ")' e 'printf (" bar ")' e ottenendo '" fboaor "' sull'output, invece di '" foobar "' o '" barfoo "' . –

risposta

5

Per quanto riguarda il rientro seriale (es., se foo() utilizza va_start è sicuro per foo() chiamare bar() che utilizza anche va_start), la risposta è che va bene - finché l'istanza va_list non è la stessa. Lo standard dice,

Né va_start né va_copy macro deve essere invocato per reinizializzare ap senza un intervento intermedio della macro va_end per lo stesso ap.

Così, sei OK fino a quando una diversa va_list (di cui sopra come ap) viene utilizzato.

Se per rientranza si intende thread-safe (che presumo che lo sia, poiché sono coinvolti mutex), sarà necessario esaminare l'implementazione per le specifiche. Poiché lo standard C non parla di multi-threading, questo problema è davvero all'altezza dell'implementazione. Potrei immaginare che potrebbe essere difficile rendere sicuro il thread va_start su architetture strane o piccole, ma penso che se stai lavorando su una piattaforma mainstream moderna probabilmente non avrai problemi.

Sulle piattaforme più tradizionali fino a quando una diversa va_list argomento viene passato alla va_start macro si dovrebbe avere alcun problema con più thread che passano attraverso la 'stessa' va_start. E poiché l'argomento va_list è in genere sullo stack (e quindi thread diversi avranno istanze diverse) generalmente si tratta di diverse istanze dello va_list.

Penso che nel tuo esempio, i mutex non siano necessari per l'uso dei vararg. Tuttavia, se lo write(), avrebbe sicuramente senso che una chiamata write() venisse serializzata in modo da non avere più thread write() che si avvitavano reciprocamente.

+0

Informazioni sulla chiamata serializzata - Sì, un mutex appartiene qui, volevo solo metterlo a un livello più basso. Inizialmente era ad un livello più basso, ma questo commesso 15 anni fa lo spostò dove lo vedi, sostenendo che il mutex doveva proteggere anche la va_list, cosa che non credevo! – Nate

2

Bene, il modo in cui l'accesso all'argomento variabile è implementato in C rende piuttosto ovvio che gli oggetti va_list memorizzano alcuni stato interno. Ciò lo rende non rientranti, nel senso che chiamare va_start su un oggetto va_list annullerebbe l'effetto del precedente va_start. Ma ancora più precisamente, C esplicitamente proibisce invocando di nuovo,su un oggetto va_list prima di "chiudere" la sessione va_start precedentemente invocata con va_end.

Un oggetto va_list deve essere utilizzato in modalità "non sovrapposta": va_start...va_end. Successivamente è possibile eseguire un altro va_start sullo stesso oggetto va_list. Ma provare a sovrapporre alle sessioni va_start...va_end sullo stesso oggetto va_list non funzionerà.

P.S. In teoria, naturalmente, è possibile implementare uno stato interno basato su LIFO in qualsiasi iteratore basato su sessione. Cioè è teoricamente possibile consentire sessioni nidificate di va_start...va_end sullo stesso oggetto va_list (rendendolo così rientrante in tal senso). Ma le specifiche della libreria C non forniscono nulla di simile.

Nota che in C99 va_list gli oggetti sono copiabili da va_copy. Pertanto, se è necessario sfogliare lo stesso elenco di argomenti con più sessioni sovrapposte di va_start...va_end, è sempre possibile ottenerlo creando diverse copie indipendenti dell'originale va_list.

P.P.S. Osservando l'esempio di codice che hai fornito ... Non è assolutamente necessario alcun mutex in questo caso (per quanto riguarda l'integrità di va_list). E non è necessario un oggetto rientrante va_list. Il codice è perfettamente a posto senza mutex. Funzionerà bene in ambiente multi-thread. I macro dal gruppo va_... non funzionano sul "puntatore stack" effettivo. Al contrario, operano su un oggetto completo indipendente va_list che può essere utilizzato per iterare sui valori memorizzati nello stack. Puoi considerarlo come il tuo puntatore di stack locale privato. Ogni thread che richiama la tua funzione riceverà la sua copia di quello va_list iterando sul proprio stack. Non ci sarà conflitto tra i thread.

+1

Sì, due chiamate di va_start su una singola va_list annullerebbero tale elenco. Quindi, per una va_list statica (o globale, o un membro della classe ...), forse sarebbe invalidata, ma una chiamata futura alla stessa funzione creerebbe una nuova va_list. – Nate

+0

@Nate: Sì, ma non è necessario necessariamente un 'va_list' statico per eseguire il problema. Solitamente si presentano problemi come questo quando si opera sulla stessa va_list locale da una singola chiamata alla stessa funzione variadica. Non c'è niente di illegale in questo. – AnT

0

Ci sono alcune piattaforme dove va_list avrebbe problemi con il re-entrancy, ma su quella stessa piattaforma tutte le variabili locali hanno tali problemi. Sono curioso, però: qual è la funzione _write che si aspetta di fare? Se sta usando i parametri impostati prima di chiamare write, questo di per sé potrebbe causare problemi di threading a meno che (1) qualsiasi particolare istanza dell'oggetto che contiene _write sarà usata solo da un thread alla volta, o (2) tutti i thread che utilizzano un oggetto per _write richiedono gli stessi parametri di configurazione.

+0

_write sarà ancora protetto. Volevo solo spostare la protezione fino a quel livello e non dovermi preoccupare di tutti i puntatori va_list. – Nate