Linux multithreaded programming

Posted by MCrosbie on Fri, 28 Jan 2022 01:45:13 +0100

linux multithreaded programming

1, Advantages of multithreading

As a multitasking and concurrent working mode, multithreaded program has the following advantages:

  1. Improve application response. This is especially meaningful for the program of graphical interface. When an operation takes a long time, the whole system will wait for the operation. At this time, the program will not respond to the operation of keyboard, mouse and menu. This embarrassing situation can be avoided by using multithreading technology to put the time-consuming operation into a new thread.
  2. Make the multi CPU system more efficient. The operating system will ensure that different threads run on different CPUs when the number of threads is not greater than the number of CPUs.
  3. Improve program structure. A long and complex process can be divided into multiple threads and become several independent or semi independent running parts. Such a program will be conducive to understanding and modification.

2, Process and thread

  1. Multithreading is a very "frugal" way of multitasking. Under Linux system, starting a new process must allocate its independent address space and establish many data tables to maintain its code segments, stack segments and data segments, which is an "expensive" multitasking mode of work. Multiple threads running in a process use the same address space and share most of the data with each other. The space spent on starting a thread is far less than that spent on starting a process. Moreover, the time required for switching between threads is far less than that required for switching between processes. According to statistics, generally speaking, the cost of a process is about 30 times that of a thread. Of course, this data may be quite different in specific systems.
  2. The second reason for using multithreading is the convenient communication mechanism between threads. For different processes, they have independent data space. Data transmission can only be carried out through communication. This method is not only time-consuming, but also very inconvenient. Threads are not. Because threads in the same process share data space, the data of one thread can be directly used by other threads, which is not only fast, but also convenient. Of course, data sharing also brings some other problems. Some variables cannot be modified by two threads at the same time, and the data declared as static in some subroutines is more likely to bring a disastrous blow to multithreaded programs. These are the most important points to pay attention to when writing multithreaded programs.
  3. The process has its own independent address space, while the thread has its own independent stack and local variables, but there is no independent address space. After a process crashes, other processes will not be affected, and the consequence of a thread crash is that the whole process crashes. Therefore, multi process programs are more robust than multi-threaded programs, but switching between processes consumes huge resources and is inefficient. For some concurrent operations that require simultaneous sharing of certain variables, only threads can be used, not processes.

3, Linux multithreading API

Basic concepts of Linux multithreading: thread, mutex, condition

1. Thread

① Thread creation

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

Return: 0 is returned successfully, otherwise the error number is returned

Parameters:①New thread id	②Thread properties( NULL (default attribute)	③Action function	④Parameters passed to the action function (multiple parameters are passed using the structure)

② Thread exit

#include <pthread.h>

void pthread_exit(void *retval);

No return value
 Parameter: used to store the information of the current thread. Other threads can call pthread_join()Access this pointer

③ Thread waiting

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

Return: 0 returned successfully	Failure returns the error number
 Parameters: ①Waiting thread id		②visit pthread_exit() retval Pointer to pointer

④ Thread separation

Thread separation means that when a thread is set to the separation state, its resources will be automatically recycled by the system when the thread ends,
It does not need to be modified in other threads pthread_join()Operation. If it is not disengaged, it will not be pthread_join Operation,
Then the process will become a zombie thread after it ends. Too many zombie processes occupy too many system resources, which will lead to the failure of thread creation.
#include <pthread.h>

int pthread_detach(pthread_t thread);

Return: success returns 0, failure returns error number
 Usually used for threads that break themselves off

⑤ Thread ID acquisition and comparison

#include <pthread.h>

pthread_t pthread_self(void);

Return: 100%Thread returned successfully id
In the migration operation, we can't simply put threads ID It is treated as an integer because different systems treat threads differently ID The definition of may be different
#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

Return: successful return of non-zero number	Failure returned 0

2. Mutex

Mutex is essentially A lock, which turns access to shared resources into mutex operations. After thread A is locked, other threads that want to lock the mutex are blocked until thread A is unlocked before they become runnable. The first thread that becomes runnable can lock the mutex, and the remaining threads continue to remain blocked. In this way, only one thread can run forward at A time.

① Create and destroy locks

#include <pthread.h>

pthread_mutex_t *restrict mutex;//Create lock
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);//Initialization lock

Parameters: ①Lock variable 	②The lock's attribute (default attribute initialization mutex is set to NULL)

int pthread_mutex_destroy(pthread_mutex_t *mutex);
     
Return: if successful, return 0; otherwise, return the error number

② Lock and unlock

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);	//If the mutex is locked by another thread, EBUSY will be returned immediately after failure

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Return: if successful, return 0; otherwise, return the error number

3. Conditions

A mechanism that allows a thread to sleep and wait for a condition. If the condition is met, it wakes up the thread to detect whether the condition is really met. The whole process is not waiting for the parameter value set by the condition variable, but the condition defined by the application. The condition variable is just a mechanism to wake up the waiting thread. When the condition variable is used together with the mutex, the thread is allowed to wait for a specific condition in a non competitive way

① Create and destroy condition variables

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);//Dynamic initialization condition variable
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //Static initialization of condition variables using macros
int pthread_cond_destroy(pthread_cond_t *cond);	

Parameters: ①Conditional variable	②Conditional variable attribute (default attribute) NULL)
Return: if successful, return 0; otherwise, return the error number

② Wait

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);	//abstime is used to specify the time

Return: if successful, return 0; otherwise, return the error number

③ Trigger and broadcast

Both trigger and broadcast notify that other thread conditions have been met. The difference is that trigger is one-to-one and broadcast is one to many
Note that the trigger or broadcast must be carried out after the conditions are met, otherwise it is invalid

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

Return: if successful, return 0; otherwise, return the error number

4, Examples

① demo1 create a thread

#include <stdio.h>
#include <pthread.h>

void *func1(void *num)
{
        static int ret = 100;

        printf("func1Id = %ld   num = %d\n", pthread_self(), *((int*)num));
        pthread_exit((void*)(&ret));
}

int main()
{
        int ret = 0;
        pthread_t t1;
        int num = 100;
        int *pret = NULL;

        ret = pthread_create(&t1, NULL, func1, (void*)(&num));
        if (!ret) {
                printf("mainId = %ld  func1 create success\n", pthread_self());
        }
        pthread_join(t1, (void **)(&pret));
        printf("ret of t1 = %d\n", (*(int*)pret));

        return 0;
}

Operation results

mainId = 140685628561152  func1 create success
func1Id = 140685620274944   num = 100
ret of t1 = 100

② demo2 uses mutex

#include <stdio.h>
#include <pthread.h>

int data = 0;
pthread_mutex_t mutex1;

void *func1(void *arg)
{
        int n = 3;

        pthread_mutex_lock(&mutex1);
        while(n--) {
                printf("func1: data = %d\n", data++);
                sleep(1);
        }
        pthread_mutex_unlock(&mutex1);
}

void *func2(void *arg)
{
        int n = 3;

        pthread_mutex_lock(&mutex1);
        while(n--) {
                        printf("func2: data = %d\n", data++);
                        sleep(1);
        }
        pthread_mutex_unlock(&mutex1);
}

int main()
{
        int ret = 0;
        pthread_t t1;
        pthread_t t2;

        pthread_mutex_init(&mutex1, NULL);
        pthread_create(&t1, NULL, func1, NULL);
        pthread_create(&t2, NULL, func2, NULL);

        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_mutex_destroy(&mutex1);

        return 0;
}

Operation results

It can be seen that thread 2 starts running after thread 1 exits after execution, which indicates that mutex is used. Shared resources can only be accessed by one thread and can only be accessed by other threads after completion. If mutex is not used, the access to shared resources is determined by the scheduling mechanism (shared resources are accessed alternately between lines)

func1: data = 0
func1: data = 1
func1: data = 2
func2: data = 3
func2: data = 4
func2: data = 5

③ demo3 service conditions

Condition data = 3, thread 1 waits for thread 2. When the condition is met, thread 2 triggers thread 1 and thread 2 exits

#include <stdio.h>
#include <pthread.h>

int data = 0;
pthread_mutex_t mutex1;
pthread_cond_t  cond1;

void *func1(void *arg)
{
        int n = 3;

        pthread_cond_wait(&cond1, &mutex1);
        while(n--) {
                printf("func1: data = %d\n", data++);
                sleep(1);
        }
}

void *func2(void *arg)
{
        while(1) {
                printf("func2: data = %d\n", data++);
                sleep(1);
                if (data == 3) {
                        pthread_cond_signal(&cond1);
                        pthread_exit(NULL);
                }
        }
}

int main()
{
        int ret = 0;
        pthread_t t1;
        pthread_t t2;

        pthread_mutex_init(&mutex1, NULL);
        pthread_cond_init(&cond1, NULL);
        pthread_create(&t1, NULL, func1, NULL);
        pthread_create(&t2, NULL, func2, NULL);

        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_mutex_destroy(&mutex1);
        pthread_cond_destroy(&cond1);

        return 0;
}                                                                               

Operation results

func2: data = 0
func2: data = 1
func2: data = 2
func1: data = 3
func1: data = 4
func1: data = 5

Topics: Embedded system