2012-12-20 10 views
7

Sto scrivendo una libreria C che deve fork() durante l'inizializzazione. Pertanto, voglio affermare() che il codice dell'applicazione (che è al di fuori del mio controllo) chiama il mio codice di inizializzazione della libreria da un singolo contesto filettato (per evitare il ben noto problema "threads and fork don't mix"). Una volta che la mia libreria è stata inizializzata, è thread-safe (e si aspetta che il codice a livello di applicazione possa creare thread). Mi preoccupo solo di supportare i pthreads.pthreads: come far valere il codice viene eseguito in un contesto singolo thread

Sembra impossibile contare il numero di thread nello spazio del processo corrente utilizzando pthreads. Infatti, anche googletest implementa solo GetThreadCount() su Mac OS e QNX.

Dato che non riesco a contare i thread, è in qualche modo possibile che posso invece affermare un singolo contesto thread?

Chiarimento: Se possibile, vorrei evitare di utilizzare "/ proc" (non portabile), una dipendenza di libreria addizionale (come libproc) e wrapper pthread_create in stile LD_PRELOAD.

Chiarimento n. 2: Nel mio caso è necessario utilizzare più processi poiché gli addetti alla mia biblioteca sono relativamente pesanti (utilizzando il webkit) e potrebbero bloccarsi. Tuttavia, voglio che il processo originale sopravviva agli arresti anomali dei lavoratori.

+0

So che la sua non è portatile, ma è un inizio: Su Linux, c'è [/ proc/self/stat] (http: //www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html). – ArjunShankar

+0

Che ne dite di "if (pthread_self()! = NULL) ..."? Scusate, non è utile se volete verificare che non ci siano ALTRI thread rispetto al primo, naturalmente. Scusate. Continuerò a pensare ... –

+1

Su ulteriori indagini, non sono sicuro che ci sia un modo semplice per risolvere questo problema. Forse basta "documentarlo chiaramente e lasciare che l'utente soffra se non sta leggendo i documenti?" –

risposta

2

Si potrebbe contrassegnare la funzione di inizializzazione libreria da eseguire prima dell'applicazione main(). Ad esempio, utilizzando GCC,

static void my_lib_init(void) __attribute__((constructor)); 

static void my_lib_init(void) 
{ 
    /* ... */ 
} 

Un'altra opzione è quella di utilizzare posix_spawn() alla tavola ed eseguire i processi di lavoro come, binari zonali separati.

A cura di aggiungere:

Mi sembra che se si vuole stabilire se il processo ha già creato (attuali kernel-based, le discussioni), si dovrà fare affidamento su codice specifico sistema operativo.

Nel caso Linux, la determinazione è semplice e sicura anche per altri sistemi operativi. Se non è possibile determinare il numero di thread utilizzati dal processo corrente, la funzione restituirà -1:

#include <unistd.h> 
#include <sys/types.h> 
#include <dirent.h> 
#include <errno.h> 

int count_threads_linux(void) 
{ 
    DIR   *dir; 
    struct dirent *ent; 
    int   count = 0; 

    dir = opendir("/proc/self/task/"); 
    if (!dir) 
     return -1; 

    while (1) { 

     errno = 0; 
     ent = readdir(dir); 
     if (!ent) 
      break; 

     if (ent->d_name[0] != '.') 
      count++; 
    } 

    if (errno) { 
     const int saved_errno = errno; 
     closedir(dir); 
     errno = saved_errno; 
     return -1; 
    } 

    if (closedir(dir)) 
     return -1; 

    return count; 
} 

Ci sono alcuni casi (come chroot senza /proc/) qualora tale controllo non riesce nemmeno in Linux, così il -1 il valore restituito deve sempre essere considerato come sconosciuto anziché errore (anche se errno indicherà il vero motivo dell'errore).

Guardando le pagine man di FreeBSD, mi chiedo se le informazioni corrispondenti siano disponibili.

Infine:

piuttosto che cercare rilevare il caso problematico, ho seriamente si consiglia fork() e exec() (o posix_spawn()) i processi di schiavi, utilizzando solo async-signal-safe funzioni (vedi man 7 signal) nel processo figlio (prima di exec()), evitando così le complicazioni della forcella() - filo. È ancora possibile creare segmenti di memoria condivisa, coppie di socket, ecc. Prima di eseguire la foratura(). L'unico inconveniente che riesco a vedere è che devi usare binari separati per i lavoratori asserviti. Il che, vista la tua descrizione, non mi sembra un inconveniente.

+0

il trucco init/constructor non preverrà il problema di @ahochhaus se la libreria è caricata con 'dlopen()' – ydroneaud

+0

@ydroneaud: pensavo che dire "esegui prima di main()" lo renderebbe ovvio. Non è come se il codice potesse tornare indietro nel tempo .. –

+0

Leggere "/ proc" non è l'ideale, ma sono d'accordo che l'unico modo per contare i thread è specifico del sistema operativo, quindi questo tipo di soluzione è vicino al meglio che possiamo ottenere. Grazie @Nominal Animal. – ahochhaus

1

Se si invia un segnale SIGINFO al processo di controllo tty, il processo dovrebbe descrivere lo stato dei thread. Dalla descrizione dovrebbe essere possibile dedurre se sono stati creati dei thread.

Potrebbe essere necessario sviluppare una piccola utility richiamata tramite popen per leggere l'output nella libreria.

Aggiunto codice di esempio Fri Dec 21 14:45

Eseguire un semplice programma che crea cinque fili. Discussioni fondamentalmente dormono. Prima che il programma esca, invia un segnale SIGINFO per ottenere lo stato dei thread.

openbsd> cat a.c 
#include <unistd.h> 
#include <pthread.h> 

#define THREADS 5 

void foo(void); 

int 
main() 
{  
    pthread_t thr[THREADS]; 
    int j; 

    for (j = 0; j < THREADS; j++) { 
     pthread_create(&thr[j], NULL, (void *)foo, NULL); 
    } 

    sleep(200);        

    return(0); 
}    

void 
foo() 
{ 
    sleep(100); 
}    
openbsd> gcc a.c -pthread 
openbsd> a.out & 
[1] 1234 
openbsd> kill -SIGINFO 1234 
0x8bb0e000 sleep_wait 15 -c---W---f 0000   
0x8bb0e800 sleep_wait 15 -c---W---f 0000   
0x8bb0e400 sleep_wait 15 -c---W---f 0000   
0x7cd3d800 sleep_wait 15 -c---W---f 0000   
0x7cd3d400 sleep_wait 15 -c---W---f 0000   
0x7cd3d000 sleep_wait 15 -c---W---f 0000 main 
+1

avete qualche riferimento a questo? – ydroneaud

+0

'man pthreads' sul mio sistema (OpenBSD) ce l'ha. Dovrebbe essere conforme all'interfaccia thread POSIX 1003.1c. –

+0

c'è un esempio? – ydroneaud

-1

È possibile utilizzare pthread_once() per garantire che nessun altro thread stia facendo la stessa cosa: in questo modo non ci si deve preoccupare che più thread chiamino la funzione di inizializzazione, solo uno verrà realmente eseguito.

Fare in modo che la funzione di inizializzazione pubblica esegua un'inizializzazione privata tramite pthread_once().

static pthread_once_t my_initialisation_once = PTHREAD_ONCE_INIT; 

static void my_initialisation(void) 
{ 
    /* do something */ 
} 

int lib_initialisation(void) 
{ 
    pthread_once(&my_initialisation_conce, my_initialisation); 

    return 0; 
} 

Un altro esempio può essere trovato here.

Link

+1

ydroneaud, grazie - questa è un'idea interessante. Tuttavia, come meglio posso dire, questo non garantisce che il codice dell'applicazione non abbia già generato un thread diverso (che non entrerà mai nel mio codice libreria ma potrebbe comunque causare mutex deadlock) prima di inizializzare la libreria. – ahochhaus

+0

@ahochhaus se si desidera proteggere da un utente che non chiama la funzione init prima di un'altra funzione, * solo * chiama la funzione init in tutte le altre funzioni: 'pthread_once (& my_initialisation_conce, my_initialisation);' – ydroneaud

+0

@ahochhaus e si può ancora usare una variabile globale atomica impostata solo al termine dell'inizializzazione – ydroneaud