thread specific data

    Thread-specific data, also known as thread-private data, is a mechanism for storing and querying data related to a particular thread. Before assigning thread-specific data, you need to create a key associated with that data, which is used to gain access to the thread-specific data. A key is created using the function pthread_key_create, and pthread_key_delete can be used for all threads to disassociate a key from a thread-specific data value.
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
int pthread_key_delete(pthread_key_t key);
                    /* The return values ​​of the two functions: if successful, return 0; otherwise, return the error number */

    The created key is stored in the memory location pointed to by keyp, and the key is available to all threads in the process, but each thread associates the key with a different thread-specific data address. When creating a new key, each thread's data address is set to null. In addition to creating the key, pthread_key_create can also associate an optional destructor for the key. When the thread exits, if the data address has been set to a non-null value, then the destructor will be called with the data address as its only argument. When the thread calls pthread_exit or returns, the destructor is called; when the thread cancels, the destructor is called only after the final cleanup handler returns. Threads usually use malloc to allocate memory for thread-specific data, so destructors often call the free function to free the allocated memory to avoid memory leaks. Threads can assign multiple keys to thread-specific data, and each key can have a destructor associated with it. When a thread exits, destructors for thread-specific data are called in the order defined in the operating system implementation. When all destructors are called, the system checks to see if there are still non-null thread-specific data values ​​associated with the key. If any, the destructor is called again. This process repeats until all of the threads' keys are null thread-specific data values, or until PTHREAD_DESTRUCTOR_ITERATIONS attempts have been made.
    Calling pthread_key_delete does not activate the destructor associated with the key, and freeing memory for any thread-specific data values ​​associated with the key requires additional steps in the application.
    To ensure that the allocated keys do not change due to contention during the initialization phase, the function pthread_once can be used. Once a key is created, the key can be associated with thread-specific data through the pthread_setspecific function, and the address of the thread-specific data can be obtained through the pthread_getspecific function.
#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));

int pthread_setspecific(pthread_key_t key, const void *value);
                    /* The return values ​​of the two functions: if successful, return 0; otherwise, return the error number */
void *pthread_getspecific(pthread_key_t key);
                    /* Return value: thread specific data value; if no value is associated with this key, return NULL */

    The initflag here must be a non-local variable (such as a global variable or static variable) and must be initialized to PTHREAD_ONCE_INIT. Even if each thread calls pthread_once, the system guarantees that the initialization routine initfn is called only once, which avoids conflicts when creating keys.
    Given below is a hypothetical implementation of getenv that uses thread-specific data to maintain each thread's copy of the data buffer for the respective return string.
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

static pthread_key_t	key;
static pthread_once_t	init_done = PTHREAD_ONCE_INIT;
static pthread_mutex_t	env_mutex = PTHREAD_MUTEX_INITIALIZER;

static void key_init(void){
	pthread_key_create(&key, free);
}

#define MAXSTRINGSZ	4096
extern char **environ;

char *getenv(const char *name){
	pthread_once(&init_done, key_init);
	pthread_mutex_lock(&env_mutex);
	char *env_buf = NULL;
	if((env_buf=(char *)pthread_getspecific(key)) == NULL){
		if((env_buf=malloc(MAXSTRINGSZ)) == NULL){
			pthread_mutex_unlock(&env_mutex);
			return NULL;
		}
		pthread_setspecific(key, env_buf);
	}
	int i = 0, len = strlen(name);
	for(; environ[i] != NULL; i++){
		if(strncmp(name, environ[i], len)==0 && environ[i][len]=='='){
			strncpy(env_buf, &environ[i][len+1], MAXSTRINGSZ-1);
			pthread_mutex_unlock(&env_mutex);
			return env_buf;
		}
	}
	pthread_mutex_unlock(&env_mutex);
	return NULL;
}

    Here pthread_once is used to ensure that only one key is created for thread-specific data that will be used. If pthread_getspecific returns a null pointer, you need to allocate the memory buffer first, and then associate the key with the memory buffer, otherwise use the returned memory buffer. For destructors, use free to free memory previously allocated by malloc. The destructor will only be called if the thread-specific data value is non-null.
    Note that compared to the getenv_r version implemented in thread reentrancy, although this version of genenv also uses a mutex to ensure thread safety, it is not async signal safe. For a signal handler, even with a recursive mutex, this version is unlikely to be reentrant because it calls the malloc function, which is not inherently async-signal safe.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326058440&siteId=291194637