thread reentrancy

    Threads are similar to signal handlers when they encounter reentrancy problems, in both cases it is possible for multiple threads of control to call the same function at the same time. A function is said to be thread-safe if it can be safely called by multiple threads at the same point in time. Among the functions defined by the Single UNIX Specification, the following are not guaranteed to be thread-safe (in addition, ctermid and tmpnam are not guaranteed to be thread-safe when passing a null pointer parameter).

    Operating systems that support thread-safe functions define the symbol _POSIX_THREAD_SAFE_FUNCTIONS in <unistd.h>. The application can also pass the _SC_PTHREAD_SAFE_FUNCTIONS parameter in the sysconf function to check whether thread-safe functions are supported at runtime. When an operating system implementation supports the feature of thread-safe functions, it provides alternative thread-safe versions of some non-thread-safe functions in POSIX.1. The following table lists thread-safe versions of these functions with "_r" appended to their original names to indicate that these versions are reentrant (many functions are not thread-safe because the data they return is stored in static In the memory buffer. By modifying the interface, requiring the caller to provide the buffer itself can make the function thread-safe).

    A function is thread-safe if it is reentrant for multiple threads, but that doesn't mean it is also reentrant for signal handlers. A function can be said to be async-signal-safe if it is safe for reentrancy to an async-signal handler , and the reentrant functions mentioned in the Signal Default Handling Actions and Reentrant Functions section are async-signal-safe.
    A reentrant version of getenv, getenv_r, is given below, which uses the pthread_once function to ensure that each process only calls the thread_init function once, regardless of how many threads are contending to call getenv_t at the same time.
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

pthread_mutex_t		env_mutex;
static pthread_once_t	initflag = PTHREAD_ONCE_INIT;

static void thread_init(void){
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
	pthread_mutex_init(&env_mutex, &attr);
	pthread_mutexattr_destroy(&attr);
}

extern char **environ;

int getenv_r(const char *name, char buf[], int buflen){
	// buf[0] = '\0';
	int len = strlen(name);
	pthread_once(&initflag, thread_init);
	pthread_mutex_lock(&env_mutex);
	int i = 0;
	for(; environ[i] != NULL; i++){
		if(strncmp(name, environ[i], len)==0 && environ[i][len]=='='){
			if(strlen(&environ[i][len+1]) < buflen){
				strcpy(buf, &environ[i][len+1]);
				pthread_mutex_unlock(&env_mutex);
				return 0;
			}else{
				pthread_mutex_unlock(&env_mutex);
				return ENOSPC;
			}
		}
	}
	pthread_mutex_unlock(&env_mutex);
	return ENOENT;
}

    Here, in order to make the function reentrant, its interface is changed, and the caller needs to provide its own buffer. To ensure thread safety, a mutex is used to protect the environment from being modified while searching for the requested character. Not only that, but a recursive mutex is used here to ensure that it is also reentrant to signal handlers. If a non-recursive mutex is used, there is a possibility of a deadlock when the thread reuses the mutex from a signal handler because it may have been locked in getenv_r.
    In addition to the functions in the above table, POSIX.1 also provides methods for managing FILE objects in a thread-safe manner. The lock associated with a given FILE object can be acquired using flockfile and ftrylockfile. The lock is recursive, which means that the lock can be acquired again while the lock is held.
#include <stdio.h>
int ftrylockfile(FILE *fp);
                      /* Return value: if successful, return 0; if the lock cannot be acquired, return a non-zero value*/
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

    If the standard I/O routines all acquire their respective locks, then there is a severe performance penalty when doing I/O one character at a time, as this requires acquiring and releasing locks for each character read and write operation Actions. To avoid this overhead, the following unlocked versions of the standard character-based I/O routines appear (though try not to use them unless surrounded by calls to the flockfile-like functions above, as they can lead to unpredictable results, Such as problems caused by multiple threads of control accessing data asynchronously).
#include <stdio.h>
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
   /* The return values ​​of the two functions: if successful, return the next character; if encountering the end of file or error, return EOF */
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
                /* The return value of the two functions: if successful, return c; otherwise, return EOF */

    Once the FILE object is locked, these functions can be called multiple times before releasing the lock, so that the total lock and unlock overhead can be amortized over multiple data reads and writes.

Guess you like

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