Linux multithreaded programming

Create and end threads

pthread_create

#include <pthread.h>

/* Create a new thread, starting with execution of START-ROUTINE
   getting passed ARG.  Creation attributed come from ATTR.  The new
   handle is stored in *NEWTHREAD.  */
extern int pthread_create (pthread_t *__restrict __newthread,
			   const pthread_attr_t *__restrict __attr,
			   void *(*__start_routine) (void *),
			   void *__restrict __arg) __THROWNL __nonnull ((1, 3));

_ newthread is the identifier of the new thread, through which subsequent pthread * functions reference the new thread. Its type pthread_t is defined as follows:

#include <bits/pthreadtypes.h>

/* Thread identifiers.  The structure of the attribute type is not
   exposed on purpose.  */
typedef unsigned long int pthread_t;

The attr parameter is used to set the properties of the new thread. Pass it NULL to use the default thread property. start_ The routine and arg parameters specify the number of threads that the new thread will run and their parameters, respectively.

pthread_ 0 is returned when create succeeds and error code is returned when create fails. The number of threads that a user can open cannot exceed the limit of RLIMIT NPROC soft resources. In addition, the total number of threads that can be created by all users on the system must not exceed the value defined by the / proc / sys / kermnel / threads Max kernel parameter.

pthread_exit

Once the thread is created, the kernel can schedule the kernel thread to execute start_ The routine function counts the function pointed to by the pointer. At the end of the thread function, it is best to call the following functions to ensure safe and clean exit:

/* Terminate calling thread.

   The registered cleanup handlers are called via exception handling
   so we cannot mark this function with __THROW.*/
extern void pthread_exit (void *__retval) __attribute__ ((__noreturn__));

pthread_ The exit function passes its exit information to the recycler of the thread through the retval parameter. It will not return to the caller after execution, and it will never fail.

pthread_join

All threads in a process can call pthread_join function to recycle other threads (provided that the target thread is recyclable, see later), that is, wait for other threads to end, which is similar to the wait and waitpid system calls of the recycling process. pthread_ The definition of join is as follows:

/* Make calling thread wait for termination of the thread TH.  The
   exit status of the thread is stored in *THREAD_RETURN, if THREAD_RETURN
   is not NULL.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int pthread_join (pthread_t __th, void **__thread_return);

__ The target identifier of the thread is th__ thread_ The return parameter is the exit information returned by the target thread. This function blocks until the end of the recycled thread. The function returns 0 on success and error code on failure. The possible error codes are shown in the table.

pthread_cancel

Sometimes we want to terminate abnormally - a thread, that is, cancel the thread, which is realized by the following function:

/* Cancel THREAD immediately or at the next possibility.  */
extern int pthread_cancel (pthread_t __th);

__ The th parameter is the identifier of the target thread. The function returns 0 on success and error code on failure. However, the target thread receiving the cancellation request can decide whether to allow cancellation and how to cancel, which are completed by the following two functions:

/* Set cancelability state of current thread to STATE, returning old
   state in *OLDSTATE if OLDSTATE is not NULL.  */
extern int pthread_setcancelstate (int __state, int *__oldstate);

/* Set cancellation state of current thread to TYPE, returning the old
   type in *OLDTYPE if OLDTYPE is not NULL.  */
extern int pthread_setcanceltype (int __type, int *__oldtype);

The first parameter of these two functions is used to set the cancellation status (whether cancellation is allowed) and cancellation type (how to cancel) of the thread respectively, and the second parameter records the original cancellation status and cancellation type of the thread respectively. The state parameter has two optional values:

  • PTHREAD_CANCEL_ENABLE to allow the thread to be cancelled. It is the default cancellation state when a thread is created.
  • PTHREAD_CANCEL_DISABLE to disable the thread from being canceled. In this case, if a thread receives a cancellation request, it will suspend the request until the thread is allowed to be cancelled.

The type parameter also has two optional values:

  • PTHREAD_CANCEL_ASYNCHRONOUS, the thread can be cancelled at any time. It will cause the target thread that receives the cancellation request to take immediate action.
  • PTHREAD_CANCEL_DEFERRED, which allows the target thread to delay action until it calls one of the following so-called cancellation point functions: pthread_join, pthread_testcancel, pthread_cond_wait,pthread_cond_timedwait,sem_wait and sigwait. According to POSIX standard, other system calls that may be blocked, such as read and wait, can also become cancellation points. But for security reasons, we'd better call pthread_ in code that might be canceled. Testcancel function to set the cancellation point.

pthread_setcancelstate and pthread_setcanceltype returns 0 when successful, and an error code when failed.

Thread properties

#  define __SIZEOF_PTHREAD_ATTR_T 56

union pthread_attr_t
{
  char __size[__SIZEOF_PTHREAD_ATTR_T];
  long int __align;
};
#ifndef __have_pthread_attr_t
typedef union pthread_attr_t pthread_attr_t;
# define __have_pthread_attr_t 1
#endif
extern int pthread_attr_init (pthread_attr_t *__attr) __THROW __nonnull ((1));

/* Destroy thread attribute *ATTR.  */
extern int pthread_attr_destroy (pthread_attr_t *__attr)
     __THROW __nonnull ((1));

/* Get detach state attribute.  */
extern int pthread_attr_getdetachstate (const pthread_attr_t *__attr,
					int *__detachstate)
     __THROW __nonnull ((1, 2));

/* Set detach state attribute.  */
extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,
					int __detachstate)
     __THROW __nonnull ((1));


/* Get the size of the guard area created for stack overflow protection.  */
extern int pthread_attr_getguardsize (const pthread_attr_t *__attr,
				      size_t *__guardsize)
     __THROW __nonnull ((1, 2));

/* Set the size of the guard area created for stack overflow protection.  */
extern int pthread_attr_setguardsize (pthread_attr_t *__attr,
				      size_t __guardsize)
     __THROW __nonnull ((1));


/* Return in *PARAM the scheduling parameters of *ATTR.  */
extern int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr,
				       struct sched_param *__restrict __param)
     __THROW __nonnull ((1, 2));

/* Set scheduling parameters (priority, etc) in *ATTR according to PARAM.  */
extern int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr,
				       const struct sched_param *__restrict
				       __param) __THROW __nonnull ((1, 2));

/* Return in *POLICY the scheduling policy of *ATTR.  */
extern int pthread_attr_getschedpolicy (const pthread_attr_t *__restrict
					__attr, int *__restrict __policy)
     __THROW __nonnull ((1, 2));

/* Set scheduling policy in *ATTR according to POLICY.  */
extern int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy)
     __THROW __nonnull ((1));

/* Return in *INHERIT the scheduling inheritance mode of *ATTR.  */
extern int pthread_attr_getinheritsched (const pthread_attr_t *__restrict
					 __attr, int *__restrict __inherit)
     __THROW __nonnull ((1, 2));

/* Set scheduling inheritance mode in *ATTR according to INHERIT.  */
extern int pthread_attr_setinheritsched (pthread_attr_t *__attr,
					 int __inherit)
     __THROW __nonnull ((1));


/* Return in *SCOPE the scheduling contention scope of *ATTR.  */
extern int pthread_attr_getscope (const pthread_attr_t *__restrict __attr,
				  int *__restrict __scope)
     __THROW __nonnull ((1, 2));

/* Set scheduling contention scope in *ATTR according to SCOPE.  */
extern int pthread_attr_setscope (pthread_attr_t *__attr, int __scope)
     __THROW __nonnull ((1));

/* Return the previously set address for the stack.  */
extern int pthread_attr_getstackaddr (const pthread_attr_t *__restrict
				      __attr, void **__restrict __stackaddr)
     __THROW __nonnull ((1, 2)) __attribute_deprecated__;

/* Set the starting address of the stack of the thread to be created.
   Depending on whether the stack grows up or down the value must either
   be higher or lower than all the address in the memory block.  The
   minimal size of the block must be PTHREAD_STACK_MIN.  */
extern int pthread_attr_setstackaddr (pthread_attr_t *__attr,
				      void *__stackaddr)
     __THROW __nonnull ((1)) __attribute_deprecated__;

/* Return the currently used minimal stack size.  */
extern int pthread_attr_getstacksize (const pthread_attr_t *__restrict
				      __attr, size_t *__restrict __stacksize)
     __THROW __nonnull ((1, 2));

/* Add information about the minimum stack size needed for the thread
   to be started.  This size must never be less than PTHREAD_STACK_MIN
   and must also not exceed the system limits.  */
extern int pthread_attr_setstacksize (pthread_attr_t *__attr,
				      size_t __stacksize)
     __THROW __nonnull ((1));

POSIX semaphore

#include <semaphore.h>

/* Initialize semaphore object SEM to VALUE.  If PSHARED then share it
   with other processes.  */
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
  __THROW __nonnull ((1));

/* Free resources associated with semaphore object SEM.  */
extern int sem_destroy (sem_t *__sem) __THROW __nonnull ((1));

/* Wait for SEM being posted.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int sem_wait (sem_t *__sem) __nonnull ((1));

/* Test whether SEM is posted.  */
extern int sem_trywait (sem_t *__sem) __THROWNL __nonnull ((1));

/* Post SEM.  */
extern int sem_post (sem_t *__sem) __THROWNL __nonnull ((1));

The first parameter sem of these functions points to the semaphore being manipulated.

  • sem_ The init function initializes an unnamed semaphore (the POSIX semaphore API supports named semaphores). The pshared parameter specifies the type of semaphore. If its value is 0, it means that this semaphore is a local semaphore of the current process. Otherwise, this semaphore can be shared among multiple processes. The value parameter specifies the initial value of the semaphore. In addition, initializing a semaphore that has been initialized will lead to unexpected results.
  • sem_ The destroy function is used to destroy semaphores to free up the kernel resources they occupy. Destroying - a semaphore that is being waited for by another thread will result in unexpected results.
  • sem_ The wait function subtracts the value of the semaphore by 1 in an atomic operation. If the value of semaphore is 0, sem_wait will be blocked until the semaphore has a non-zero value.
  • sem_trywait and SEM_ The wait function is similar, but it always returns immediately, regardless of whether the semaphore being manipulated has a non-zero value, which is equivalent to SEM_ Non blocking version of wait. When the value of semaphore is not 0, sem_trywait performs a minus 1 operation on the semaphore. When the value of the semaphore is 0, it will return - 1 and set errno to EAGAIN.
  • sem_ The post function adds 1 to the value of the semaphore in an atomic operation. When the semaphore value is greater than 0, others are calling SEM_ The thread waiting for the semaphore will be awakened.

The above functions return 0 on success and - 1 on failure and set errno.

mutex

Mutexes (also known as mutexes) can be used to protect critical code segments to ensure exclusive access, which is a bit like a binary semaphore. When entering the key code segment, we need to obtain the mutex and lock it, which is equivalent to the P operation of binary semaphores; When leaving the key code segment, we need to unlock the mutex to wake up other threads waiting for the mutex, which is equivalent to the V operation of binary semaphores.

/* Initialize a mutex.  */
extern int pthread_mutex_init (pthread_mutex_t *__mutex,
			       const pthread_mutexattr_t *__mutexattr)
     __THROW __nonnull ((1));

/* Destroy a mutex.  */
extern int pthread_mutex_destroy (pthread_mutex_t *__mutex)
     __THROW __nonnull ((1));

/* Try locking a mutex.  */
extern int pthread_mutex_trylock (pthread_mutex_t *__mutex)
     __THROWNL __nonnull ((1));

/* Lock a mutex.  */
extern int pthread_mutex_lock (pthread_mutex_t *__mutex)
     __THROWNL __nonnull ((1));

//The above functions return 0 when successful and error code when failed.

The first parameter mutex of these functions points to the target mutex to be operated on. The type of mutex is pthread_mutex_t structure.

  • pthread_ pthread_ mutex_ The init function initializes the mutex. The mutexattr parameter specifies the properties of the mutex. If it is set to NULL, the default property is used. In addition to this function, we can also initialize a mutex in the following way: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; Macro PTHREAD_MUTEX_INITIALIZER actually just initializes each field of the mutex to 0.
  • pthread_ mutex_ The destroy function is used to destroy the mutex to release the kernel resources it occupies. Destroying a locked mutex will lead to unexpected consequences.
  • pthread_ mutex_ The lock function locks a mutex by atomic operation. Pthread if the target mutex is already locked_ mutex_ The lock call blocks until the owner of the mutex unlocks it.
  • pthread_mutex_trylock and pthread_ mutex_ The lock function is similar, but it always returns immediately, regardless of whether the mutex being operated has been locked or not, which is equivalent to pthread_ mutex_ Non blocking version of lock. Pthread when the target mutex is not locked_ mutex_ Trylock performs a locking operation on the mutex. When the mutex has been locked, pthread_mutex_trylock will return the error code EBUSY. Note that the pthread discussed here_ mutex_ Lock and pthread_mutex_trylock's behavior is aimed at ordinary locks. As we will see later, the two locking functions behave differently for other types of locks.
  • pthread_ mutex_ The unlock function unlocks a mutex by atomic operation. If there are other threads waiting for the mutex at this time, one of these threads will get it.

Mutex attribute

/* Data structures for mutex handling.  The structure of the attribute
   type is not exposed on purpose.  */
typedef union
{
  char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
  int __align;
} pthread_mutexattr_t;

/* Initialize mutex attribute object ATTR with default attributes
   (kind is PTHREAD_MUTEX_TIMED_NP).  */
extern int pthread_mutexattr_init (pthread_mutexattr_t *__attr)
     __THROW __nonnull ((1));

/* Destroy mutex attribute object ATTR.  */
extern int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr)
     __THROW __nonnull ((1));

/* Get the process-shared flag of the mutex attribute ATTR.  */
extern int pthread_mutexattr_getpshared (const pthread_mutexattr_t *
					 __restrict __attr,
					 int *__restrict __pshared)
     __THROW __nonnull ((1, 2));

/* Set the process-shared flag of the mutex attribute ATTR.  */
extern int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr,
					 int __pshared)
     __THROW __nonnull ((1));

#if defined __USE_UNIX98 || defined __USE_XOPEN2K8
/* Return in *KIND the mutex kind attribute in *ATTR.  */
extern int pthread_mutexattr_gettype (const pthread_mutexattr_t *__restrict
				      __attr, int *__restrict __kind)
     __THROW __nonnull ((1, 2));

/* Set the mutex kind attribute in *ATTR to KIND (either PTHREAD_MUTEX_NORMAL,
   PTHREAD_MUTEX_RECURSIVE, PTHREAD_MUTEX_ERRORCHECK, or
   PTHREAD_MUTEX_DEFAULT).  */
extern int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind)
     __THROW __nonnull ((1));
#endif

Two common attributes of mutex: pshared and type. The mutex property pshared specifies whether mutexes are allowed to be shared across processes. There are two optional values:

  • PTHREAD_PROCESS_SHARED. Mutexes can be shared across processes.
  • PTHREAD_PROCESS_PRIVATE. Mutexes can only be shared by threads that belong to the same process as the initialization thread of the lock.

The mutex attribute type specifies the type of mutex. Linux supports the following four types of mutexes:

  • PTHREAD_MUTEX_NORMAL, ordinary lock. This is the default type of mutex. When a thread locks an ordinary lock, other threads requesting the lock will form a waiting queue and obtain it according to priority after the lock is unlocked. This lock type ensures the fairness of resource allocation. But this kind of lock is also easy to cause problems: if a thread locks an ordinary lock that has been locked again, it will cause deadlock; Unlocking an ordinary lock that has been locked by other threads, or unlocking an unlocked ordinary lock again, will lead to unexpected consequences.
  • PTHREAD_ MUTEX_ Error check, error check lock. If a thread locks an error detection lock that has been locked again, the locking operation returns EDEADLK. Unlock an error detection lock that has been locked by other threads, or unlock an unlocked error detection lock again, and the unlocking operation returns to EPERM.
  • PTHREAD_MUTEX_RECURSIVE, nested lock. This lock allows a thread to lock it multiple times before releasing it without deadlock. However, if other threads want to obtain this lock, the owner of the current lock must perform the corresponding number of unlocking operations. Unlock a nested lock that has been locked by other threads, or unlock a nested lock that has been unlocked again, and the unlocking operation returns to EPERM.
  • PTHREAD_MUTEX_DEFAULT, the default lock. If a thread locks a default lock that has been locked again, or unlocks a default lock that has been locked by other threads, or unlocks an unlocked default lock again, it will lead to unexpected consequences. This lock may be mapped to one of the above three locks when implemented.

Conditional variable

If mutexes are used to synchronize threads' access to shared data, condition variables are the values used to synchronize shared data between threads. Condition variable provides a notification mechanism between threads: when a shared data reaches a certain value, wake up the thread waiting for the shared data. The correlation functions of conditional variables are mainly as follows:

typedef union
{
  struct __pthread_cond_s __data;
  char __size[__SIZEOF_PTHREAD_COND_T];
  __extension__ long long int __align;
} pthread_cond_t;

/* Initialize condition variable COND using attributes ATTR, or use
   the default values if later is NULL.  */
extern int pthread_cond_init (pthread_cond_t *__restrict __cond,
			      const pthread_condattr_t *__restrict __cond_attr)
     __THROW __nonnull ((1));

/* Destroy condition variable COND.  */
extern int pthread_cond_destroy (pthread_cond_t *__cond)
     __THROW __nonnull ((1));

/* Wake up one thread waiting for condition variable COND.  */
extern int pthread_cond_signal (pthread_cond_t *__cond)
     __THROWNL __nonnull ((1));

/* Wake up all threads waiting for condition variables COND.  */
extern int pthread_cond_broadcast (pthread_cond_t *__cond)
     __THROWNL __nonnull ((1));

/* Wait for condition variable COND to be signaled or broadcast.
   MUTEX is assumed to be locked before.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
			      pthread_mutex_t *__restrict __mutex)
     __nonnull ((1, 2));
     
//The above functions return 0 when successful and error code when failed.

The first parameter cond of these functions points to the target condition variable to be operated on. The type of condition variable is pthread_cond_t structure.

  • pthread_ cond_ The init function initializes condition variables. cond_ The attr parameter specifies the attributes of the condition variable. If it is set to NULL, the default property is used. There are few attributes of condition variables, and they are similar to the attribute type of mutex, so we won't repeat them. Except pthread_ cond_ In addition to init, we can also initialize a condition variable in the following way: pthread_cond_t cond = PTHREAD_COND_INITIALIZER; Macro PTHREAD_COND_INITIALIZER actually just initializes each field of the condition variable to 0.
  • pthread_ cond_ The destroy function is used to destroy condition variables to free up the kernel resources they occupy. Destroying a waiting condition variable will fail and return EBUSY.
  • pthread_ cond_ The broadcast function wakes up all threads waiting for the target condition variable by broadcasting. pthread_ cond_ The signal function is used to wake up a thread waiting for the target condition variable. Which thread will be awakened depends on the priority of the thread and the scheduling strategy. Sometimes we may want to wake up a specified thread, but pthread does not provide a solution to this requirement. However, we can indirectly realize this requirement: define a global variable that can uniquely represent the target thread, set the variable as the target thread before waking up the thread of the waiting condition variable, and then wake up all threads of the waiting condition variable by broadcasting. After these threads are awakened, check the variable to determine whether they are awakened, If yes, execute the subsequent code. If not, return and continue to wait.
  • pthread_ cond_ The wait function is used to wait for the target condition variable. Mutex parameter is a mutex used to protect condition variables to ensure pthread_ cond_ Atomicity of wait operation. When calling pthread_ cond_ Before waiting, you must ensure that mutex has been locked, otherwise it will lead to unexpected results. pthread_ cond_ When the wait function is executed, first put the calling thread into the waiting queue of the condition variable, and then unlock the mutex. It can be seen from pthread_ cond_ Pthread is the time between the start of wait execution and the time when its calling thread is put into the waiting queue of the condition variable_ cond_ Signal and pthread_ cond_ Functions such as broadcast do not modify condition variables. In other words, pthread_ cond_ The wait number does not miss any change in the target condition variable. When pthread_ cond_ When the wait number returns successfully, the mutex will be locked again.

Reentrant function

If a lie number can be called by multiple threads at the same time without race conditions, we call it thread safe, or it is a repeatable function. Only a small number of Linux library functions are non human, such as INET_ The ntoa function, as well as the getservbyname and getservbyport functions. The reason why these library functions are not human is mainly because they use static variables internally. However, Linux provides corresponding reentrant versions for many library functions that cannot be duplicated. The function names of these reentrant versions are added at the end of the original function names_ r. For example, the reentrant function corresponding to the function localtime is localtime_ r. When calling library functions in multithreaded programs, it is necessary to use its heavy version, otherwise it may lead to unexpected results.

Threads and processes

Consider this question: if a thread of a multithreaded program calls the fork function, will the newly created child process automatically create the same number of threads as the parent process? The answer is "no", as we expected. A child process has only one execution thread, which is a complete copy of the thread that called fork. And the child process will automatically inherit the state of the mutex (condition variable is similar) in the parent process. In other words, the mutex that has been locked in the parent process is also locked in the child process. This raises a problem: the child process may not know the specific state (locked or unlocked) of the mutex inherited from the parent process. The mutex may be locked, but it is not locked by the thread calling the fork function, but by other threads. If this is the case, the child process will cause deadlock if it performs the locking operation on the mutex again.

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>

pthread_mutex_t mutex;

void *another(void *arg)
{
    printf("in child thread, lock the mutex\n");
    pthread_mutex_lock(&mutex);
    sleep(5);
    pthread_mutex_unlock(&mutex);
}

void prepare()
{
    pthread_mutex_lock(&mutex);
}

void infork()
{
    pthread_mutex_unlock(&mutex);
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_t id;
    pthread_create(&id, NULL, another, NULL);
    //pthread_atfork( prepare, infork, infork );
    sleep(1);
    int pid = fork();
    if (pid < 0)
    {
        pthread_join(id, NULL);
        pthread_mutex_destroy(&mutex);
        return 1;
    }
    else if (pid == 0)
    {
        printf("I anm in the child, want to get the lock\n");
        /*The child process inherits the mutex state from the parent process. The mutex is locked because the child thread pthread of the parent process_ mutex_ Lock. Therefore, the following sentence will always block the lock operation, although logically it should not be blocked.*/
        pthread_mutex_lock(&mutex);
        printf("I can not run to here, oop...\n");
        pthread_mutex_unlock(&mutex);
        exit(0);
    }
    else
    {
        pthread_mutex_unlock(&mutex);
        wait(NULL);
    }
    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}

However, pthread provides a special function pthread_ atfork to ensure that both parent and child processes have a clear lock state after fork calls. This function is defined as follows:

/* Install handlers to be called when a new process is created with FORK.
   The PREPARE handler is called in the parent process just before performing
   FORK. The PARENT handler is called in the parent process just after FORK.
   The CHILD handler is called in the child process.  Each of the three
   handlers can be NULL, meaning that no handler needs to be called at that
   point.
   PTHREAD_ATFORK can be called several times, in which case the PREPARE
   handlers are called in LIFO order (last added with PTHREAD_ATFORK,
   first called before FORK), and the PARENT and CHILD handlers are called
   in FIFO (first added, first called).  */

extern int pthread_atfork (void (*__prepare) (void),
			   void (*__parent) (void),
			   void (*__child) (void)) __THROW;
			   
//The function returns 0 on success and error code on failure.

This function will create three fork handles to help us clean up the state of the mutex. The prepare handle will be executed before the fork call creates the child process. It can be used to lock mutexes in all parent processes. The parent handle is executed in the parent process after the fork call creates the child process and before the fork returns. Its function is to release all mutexes locked in the prepare handle. The child handle is executed in the child process before the fork returns. Like the parent handle, the child handle is also used to release all mutexes locked in the prepare handle.

Threads and signals

/* Modify the signal mask for the calling thread.  The arguments have
   the same meaning as for sigprocmask(2). */
extern int pthread_sigmask (int __how,
			    const __sigset_t *__restrict __newmask,
			    __sigset_t *__restrict __oldmask)__THROW;

//0 is returned for success and error code is returned for failure

Since all threads in the process share the signal of the process, the thread library will decide which specific thread to send the signal to according to the thread mask. Therefore, if we set the signal mask separately in each sub thread, it is easy to cause logic errors. In addition, all threads share signal processing functions. That is, when we set the signal processing function of a signal in one thread, it will override the signal processing function set by other threads for the same - signal. Both of these points indicate that we should define a special thread to process all signals. This can be achieved through the following two steps:

  1. Pthread is called before the main thread creates other child threads_ Sigmask to set the signal mask, and all newly created sub threads will automatically inherit the signal mask. After doing so, virtually all threads will not respond to the masked signal.
  2. The following function is called in a thread to wait for the signal and process it:
# ifdef __USE_POSIX199506
/* Select any of pending signals from SET or wait for any to arrive.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int sigwait (const sigset_t *__restrict __set, int *__restrict __sig)
     __nonnull ((1, 2));
# endif /* Use POSIX 1995.  */

The set parameter specifies the set of signals to wait for. We can simply specify it as the signal mask created in step 1, indicating that we are waiting for all masked signals in this thread. The integer pointed to by the parameter sig is used to store the signal value returned by the function. Sigwait returns 0 when it succeeds, and an error code when it fails. Once sigwait returns correctly, we can process the received signal. Obviously, if we use sigwait, we should no longer set the signal processing function for the signal. This is because when the program receives a signal, only one of the two can work.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

/* Simple error handling functions */

#define handle_error_en(en, msg) \
    do                           \
    {                            \
        errno = en;              \
        perror(msg);             \
        exit(EXIT_FAILURE);      \
    } while (0)

static void *
sig_thread(void *arg)
{
    printf("yyyyy, thread id is: %ld\n", pthread_self());
    sigset_t aset;
    int s, sig;
    sigemptyset(&aset);
    sigaddset(&aset, SIGQUIT);
    sigaddset(&aset, SIGUSR1);
    //s = pthread_sigmask(SIG_BLOCK, &aset, NULL);
    sigset_t *set = (sigset_t *)arg;

    for (;;)
    {
        s = sigwait(set, &sig);
        if (s != 0)
            handle_error_en(s, "sigwait");
        printf("Signal handling thread got signal %d\n", sig);
    }
}

static void handler(int arg)
{
    printf("xxxxx, thread id is: %ld\n", pthread_self());
}

int main(int argc, char *argv[])
{
    pthread_t thread;
    sigset_t set;
    int s;

    /* Block SIGINT; other threads created by main() will inherit
 *               a copy of the signal mask. */

    signal(SIGQUIT, handler);
    //           if (s != 0)
    //             handle_error_en(s, "pthread_sigmask");

    s = pthread_create(&thread, NULL, &sig_thread, (void *)&set);
    sigemptyset(&set);
    sigaddset(&set, SIGQUIT);
    sigaddset(&set, SIGUSR1);
    //s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_create");
    printf("sub thread with id: %ld\n", thread);
    /* Main thread carries on to create other threads and/or do
 *               other work */

    pause(); /* Dummy pause so we can test program */
}

Finally, pthread also provides the following methods, so that we can explicitly send a signal to the specified thread:

/* Send signal SIGNO to the given thread. */
extern int pthread_kill (pthread_t __threadid, int __signo) __THROW;

The thread parameter specifies the target thread and the sig parameter specifies the signal to be sent. Pthread if sig is 0_ Kill does not send a signal, but it still performs error checking. We can use this method to detect whether the target thread exists. pthread_ 0 is returned when kill succeeds, and the error code is returned when kill fails.

Keywords: Linux socket Multithreading server

Added by benreisner on Thu, 03 Feb 2022 20:09:46 +0200