1. Thread model

Threads are divided into kernel threads and user threads according to their runtime environment and scheduling identity. The kernel thread runs in the kernel space and is scheduled by the kernel user thread runs in the user space. When the kernel thread is scheduled by the thread library to acquire CPU usage, it loads and runs a user thread. A process can have M kernel threads and N user threads (M <= N). According to the value of M:N, the thread can be implemented in three ways:

  1. Full user-space implementation
  2. Full kernel scheduling
  3. Double scheduling

2. Linux thread library

The thread library of Linux is implemented in a 1:1 manner, that is, one kernel thread for each user thread. The default thread library on modern Linux is NPTL.

  1. The kernel thread is no longer a process, avoiding semantic problems caused by process emulation of threads
  2. Instead of managing threads, terminating threads and reclaiming thread stacks is done by the kernel
  3. Threads of a process can run on different cpus, taking full advantage of multiple cores
  4. Thread synchronization is done by the kernel, and threads between different entries and exits can share mutex

3. Create and end a thread

#include <pthread.h>
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);
Copy the code

Typedef unsigned long int pthread_t; typedef unsigned long int pthread_t; attr: NULL indicates the default start_routine and arg indicates that the function and parameter run by the new thread returns 0 on success and an error code on failure. The number of open threads cannot exceed RLIMIT_NPROC soft resource limit The total number of threads created by all users cannot exceed the value defined by the /proc/sys/kernel/threads_max kernel parameter

void pthread_exit(void* retval);
Copy the code

It is best to call the thread function at the end to ensure that the safe and clean exit retval argument is passed to the thread collector for exit information

int pthread_join(pthread_t thread, void** retval);
Copy the code

Any thread in the process can call pthead_JOIN to reclaim another thread (the target thread must be collectable), that is, waiting for the target thread to end, the calling thread will block until the target thread ends and returns 0 on success. The error code may be as follows: EDEADLK: EINVAL: the target thread is not recyclable. ESRCH: the target thread does not exist

int pthread_cancel(pthread_t thread);
Copy the code

Returns 0 on success, an error code on failure, and the target thread can decide whether to allow cancellation and how to do it, implemented by two functions:

int pthread_setcancelstate(int state, int* oldstate);
int pthread_setcanceltype(int type, int* oldtype);
Copy the code

Possible values are as follows: PTHREAD_CANCEL_ENABLE: allows a thread to be cancelled. PTHREAD_CANCEL_DISABLE: allows a thread to be cancelled by default. Forbidden threads are cancelled. If a thread receives a cancellation request, the request is suspended until the target thread is set to allow cancellation

PTHREAD_CANCEL_ASYNCHRONOUS: cancel immediately upon receipt of a cancellation request. PTHREAD_CANCEL_DEFERRED: delay cancellation until the cancellation point function is called. Pthread_join, pthread_testcancel, pthread_cond_wait, pthread_cond_timewait, sem_wait, and SIG_wait, It is recommended to call pthread_testCancel to set the cancellation to return 0 on success and an error code on failure

4. Thread properties

To be continued…

5. A semaphore

#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
Copy the code

Initializes an unnamed semaphore, pshared specifies the semaphore type, 0 represents the local semaphore of the process that could otherwise be shared between processes, and value represents the initial value of the semaphore. Initializing an alreadyinitialized semaphore yields unexpected results

int sem_destroy(sem_t* sem);
Copy the code

Destroying a semaphore, freeing up the kernel resources it occupies, and destroying a semaphore that is waiting on by another thread can lead to unexpected results

int sem_wait(sem_t* sem);
Copy the code

Atomic operation to reduce the semaphore value by 1, if the semaphore is 0, the call will block until the semaphore has a non-zero value

int sem_trywait(sem_t* sem);
Copy the code

When the semaphore is non-0, decrement the semaphore by 1. When the semaphore is 0, return -1 and set errno to the non-blocking version of eagain.sem_wait

int sem_post(sem_t* sem);
Copy the code

Atomic increments the semaphore by 1. When the semaphore is greater than 0, the thread that called sem_wait to wait for the semaphore is awakened

Returns 0 on success, -1 on failure and sets errno

6. The mutex

It is used to protect key code segments and ensure exclusive access. When entering the code segment, the mutex is acquired and locked, which is equivalent to the wait operation of the semaphore. When leaving the code segment, the mutex is unlocked and other threads waiting for the mutex are awakened, which is equivalent to the POST operation of the semaphore

#include <pthead.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr);
Copy the code

Mutex refers to the target mutex, this function initializes the mutex, and mutexattr specifies the attributes of the mutex, if NULL, the default

Pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; PTHREAD_MUTEX_INITIALIZER = PTHREAD_MUTEX_INITIALIZER;Copy the code
int pthread_mutex_destroy(pthread_mutex_t* mutex);
Copy the code

Destroying a mutex frees the kernel resources it occupies, and destroying an already locked mutex results in unexpected results

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
Copy the code

Atomic unlocking of a mutex. Trylock is the non-blocking version, and locking an already locked mutex returns an error code EBUSY. Zero is returned on success, and an error code is returned on failure

7. Mutex properties

To be continued…

Deadlock examples

An ordinary lock that has been locked is locked again (recursive call), resulting in deadlock. Two threads apply for two mutex in different order, resulting in deadlock

9. Conditional variables

The mutex is used to synchronize access to shared data, and the condition variable is used to synchronize the value of shared data. It provides a notification mechanism between threads: when a shared data reaches a certain value, the thread waiting for the shared data is woken up

#include <pthread.h>
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
Copy the code

Cond refers to the target condition variable and initializes the condition variable. Cond_attr specifies the attribute of the condition variable. NULL indicates the default attribute

Pthread_cond_t cond = PTHREAD_COND_INITIALIZER; PTHREAD_COND_INITIALIZER = PTHREAD_COND_INITIALIZER;Copy the code
int pthread_cond_destroy(pthread_cond_t* cond);
Copy the code

Destroying a condition variable frees occupied kernel resources. Destroying a waiting condition variable fails and EBUSY is returned

int pthread_cond_broadcast(pthread_cond_t* cond);
Copy the code

Broadcast to wake up all threads waiting for the target condition to be traversed

int pthread_cond_signal(pthread_cond_t* cond);
Copy the code

Depending on the priority and scheduling policy of the thread, there is currently no way to wake up the specified thread

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
Copy the code

To ensure atomization of the wait operation, ensure that the current thread holds the MUTEX and locks it before invoking wait. Otherwise, unexpected results may occur. During this process, broadcast and signal do not modify the condition variable, wait does not miss any changes to the target condition variable, and mutex is re-locked when the wait function returns successfully

All functions return 0 on success and an error code on failure

10. Thread synchronization encapsulation

To be continued…

Reentrant functions

If a function is called by more than one thread and no race condition occurs, it is called thread-safe or reentrant. Linux libraries provide reentrant versions of only a small number of functions that are not reentrant, such as localtime_r, the corresponding reentrant version of localtime

12. Threads and processes

Multithreaded program calls fork, the child process only has a thread of execution, it is a complete copy of the parent process called fork thread, and the child process will automatically inherit the parent process mutex, condition variable status

13. Threads and signals

Each thread can set the new number mask independently. The setting function in multi-threaded environment:

#include <pthread.h>
#include <signal.h>
int pthread_sigmask(int how, const sigset_t* newmask, sigset_t* oldmask);
Copy the code

Returns 0 on success, error code on failure the new number of the thread shared process. The thread library will issue the new number to the specific thread based on the thread mask. If each thread sets the signal mask separately, a logical error will result. All threads share the new number handler. When one thread sets a signal handler, it overwrites other threads’ handlers for the same signal, so we should define a special thread to handle all signals as follows:

  1. The main thread calls pthread_sigmask to set up the signal mask before creating other child threads. The new child thread automatically inherits the signal mask, so that no thread responds to the masked signal
  2. Call the following function on a thread to wait for signals and process them:
#include <signal.h>
int sigwait(const sigset_t* set, int* sig);
Copy the code

Sig: The signal value returned by the storage function sigWAIT returns 0 on success, and an error code is returned on failure

int pthread_kill(pthread_t thread, int sig);
Copy the code

Sig: If the signal to be sent is 0, the function will not send a signal, but will still perform error checking, using this method to detect whether there is a target program to return 0 on success, failure return error code