[Linux C] A brief introduction to threads

The concept of thread

basic concept

Process : refers to an application program running in memory. Each process has an independent memory space. An application program can run multiple processes at the same time; a process is also an execution process of a program , is the basic unit of a system running a program; running a program in the system is the process of a process from creation, operation to death.

Thread : A thread is an execution unit in a process, responsible for the execution of programs in the current process, and there is at least one thread in a process. There can be multiple threads in a process, and this application can also be called a multi-threaded program.

In short : After a program runs, there is at least one process, and a process can contain multiple threads; "process - the smallest unit of resource allocation, thread - the smallest unit of program execution"

when to use threads

1. It is necessary to create and destroy priority threads frequently; because it is very expensive for a process to create and destroy a process

2. The thread switching speed is fast, so when a large amount of calculation is required, threads are used when switching frequently, and time-consuming operations use threads to improve the response of the application

3. Because the efficiency of the CPU system using threads is more dominant, it may be developed to use processes for multi-machine distribution, and use threads for multi-core distribution.

4. Threads are used in parallel operations, such as concurrent threads on the server side of the C/S architecture to respond to user requests

5. When you need more stability and security, it is suitable to choose a process; when you need speed, it is better to choose a thread

View the shell command of the thread

ps axm
insert image description here

ps ax -L
insert image description here

thread-related functions

insert image description here

basic function

1. Thread creation

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号

When pthread_create returns successfully, the memory location pointed to by tidp is set to the thread ID of the newly created thread. The attr parameter is used to customize various thread attributes. It can be set to NULL temporarily to create a thread with default attributes.

The newly created thread starts running from the address of the start_rtn function, which has only one untyped pointer parameter arg. If more than one parameter needs to be passed to the start_rtn function, then these parameters need to be put into a structure, and then the address of this structure is passed in as the arg parameter.

2. Thread exits

A single thread can exit in three ways, stopping its flow of control without terminating the entire process:

1) The thread just returns from the startup routine, and the return value is the exit code of the thread (that is, the program that has completely executed the thread).

2) Threads can be canceled by other threads in the same process ( pthread_cancel ).

3) The thread calls pthread_exit; (that is, it executes the function somewhere in the program):

#include <pthread.h>
int pthread_exit(void *rval_ptr);

rval_ptr is an untyped pointer, similar to the single parameter passed to the startup routine. Other threads in the process can access this pointer by calling the pthread_join function.

3. Thread waiting

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号

A thread calling this function will block until the specified thread calls pthread_exit, returns from the startup routine, or is canceled. rval_ptr will contain the return code if the routine just returned from its startup routine. If the thread is canceled, the memory unit specified by rval_ptr is set to PTHREAD_CANCELED.

A thread can be automatically put into a detached state by calling pthread_join so that resources can be restored. If the thread is already in the detached state, the call to pthread_join will fail with EINVAL.

If you are not interested in the return value of the thread, you can set rval_ptr to NULL. In this case, calling the pthread_join function will wait for the specified thread to terminate, but will not obtain the thread's termination status.

Example 1 : Use two threads to print 1, 2, 3 respectively

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

static void* func(void *p)
{
    
    
	int i = 0;
	puts("This is thread1");
	while(i < 3)
	{
    
    
		printf("thread1:%d\n",i++);
		sleep(1);
	}
	pthread_exit(NULL);	
}


int main()
{
    
    
	pthread_t tid;	
	int err;
	int cnt = 0;
	printf("Begin\n");
	err = pthread_create(&tid,NULL,func,NULL);
	if (err)
	{
    
    
		fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
		exit(1);
	}
	while(cnt<3)
	{
    
    
		sleep(1);
		printf("main:%d\n",cnt++);
	}
	pthread_join(tid,NULL);
	printf("End\n");	
	exit(0);
}


Execution result : You can see that two threads are randomly cross-executed

Begin
This is thread1
thread1:0
main:0
thread1:1
main:1
thread1:2
main:2
End

Mutex related functions

Mutual exclusion : Due to the existence of shared data between threads, when multiple threads operate on shared data concurrently (mainly write operations), if not managed, data inconsistency may occur. Mutual exclusion means that a shared data can only be used by one thread at a time, thus ensuring the consistency of the shared data.

Mutex (mutex) : It is essentially a lock, which locks the mutex before accessing shared resources, and releases the lock on the mutex after the access is completed. After locking the mutex, any other thread that tries to lock the mutex again will be blocked until the current thread releases the mutex. If multiple threads are blocked when the mutex is released, all blocked threads on the mutex will become runnable. The first thread to become runnable can lock the mutex, and other threads will You will see that the mutex is still locked, and you can only go back and wait for it to become available again. In this way, only one thread can run forward at a time.

Mutex variables are represented by the pthread_mutex_t data type. Before using the mutex variable, it must be initialized. It can be set to the constant PTHREAD_MUTEX_INITIALIZER (only for statically allocated mutexes), or it can be initialized by calling the pthread_mutex_init function. If the mutex is dynamically allocated (for example by calling the malloc function), then it is necessary to call pthread_mutex_destroy before releasing the memory.

1. Create and destroy a mutex

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号

To initialize a mutex with default attributes, simply set attr to NULL.

2. Lock and unlock

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex);	
int pthread_mutex_trylock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号

If the thread has been locked by other threads before locking the mutex with pthread_mutex_lock, it will block;

​ If you don't want to be blocked, it can use pthread_mutex_trylock to try to lock the mutex. If the mutex is unlocked when pthread_mutex_trylock is called, then pthread_mutex_trylock will lock the mutex without blocking and return 0, otherwise pthread_mutex_trylock will fail, unable to lock the mutex, and return EBUSY.

Example 2 : Using a mutex, when one thread operates a global variable g_data, other threads cannot operate the variable

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

int g_data = 0;
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;		//初始化互斥量


static void* func(void *p)
{
    
    
	int cnt = 0;	//初始化计数变量
	puts("This is thread1");
	//pthread_mutex_lock(&mutex);		//给互斥量加锁,避免其他线程操作g_data变量
	while(cnt < 3)
	{
    
    
		cnt++;
		printf("thread1:%d\n",g_data++);
		sleep(1);
	}
	//pthread_mutex_unlock(&mutex);	//解锁
	pthread_exit(NULL);	
}


int main()
{
    
    
	pthread_t tid;	
	int err;
	int cnt = 0;
	printf("Begin\n");
	err = pthread_create(&tid,NULL,func,NULL);
	if (err)
	{
    
    
		fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
		exit(1);
	}
	
	//pthread_mutex_lock(&mutex);		//给互斥量加锁,避免其他线程操作g_data变量
	while(cnt<3)
	{
    
    
		sleep(1);
		cnt++;
		printf("main:%d\n",g_data++);
	}
	//pthread_mutex_unlock(&mutex);	//解锁

	pthread_join(tid,NULL);		//等待线程退出
	//pthread_mutex_destroy(&mutex);	//销毁互斥锁
	printf("End\n");	
	exit(0);
}

Execution result : You can see that the two threads cross and add 1 to the g_data variable

[Rio@VM-8-17-centos pratise]$ ./pratise2
Begin
This is thread1
thread1:0
main:1
thread1:2
main:3
thread1:4
main:5
End

Restore the commented-out code of the above code, that is, the code with the mutex added, and the execution structure is as follows

[Rio@VM-8-17-centos pratise]$ ./pratise2
Begin
This is thread1
main:0
main:1
main:2
thread1:3
thread1:4
thread1:5
End

It can be seen that when the main thread operates the variable g_data, the thread1 thread is blocked and cannot operate on g_data;

Condition variable related functions

Condition variables are used to wait for threads instead of locking. Condition variables are usually used with mutexes. The reason why condition variables are used with mutexes is mainly because an obvious feature of mutexes is that they have only two states: locked and unlocked, and condition variables can be used by allowing threads to block and wait for another thread to send a signal To make up for the lack of mutexes, so mutexes and condition variables are usually used together

The condition variable must be initialized before it is used. The condition variable represented by the pthread_cond_t data type can be initialized in two ways. The constant PTHREAD_COND_INITIALIZER can be assigned to the statically allocated condition variable, but if the condition variable is dynamically allocated, the pthread_cond_destroy function can be used to destroy the condition Variables are deinitialized.

1. Create and destroy condition variables

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号

The attr parameter of the pthread_cont_init function can be set to NULL unless it is desired to create a condition variable with non-default attributes.

2. wait

​ Using pthread_cond_wait is not a kind of busy waiting to reduce CPU consumption

#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, cond struct timespec *restrict timeout);
// 返回:若成功返回0,否则返回错误编号

Function role:

1. Blocking and waiting condition variable cond (refer to 1) is satisfied
2. Release the mutex that has been mastered (unlock the mutex) is equivalent to pthread_mutex_unlock(&mutex);
the above two steps are an atomic operation.

3. When awakened and the pthread_cond_wait function returns, unblock and re-apply for the mutex pthread_mutex_lock(&mutex);

The pthread_cond_timedwait function works similarly to the pthread_cond_wait function, except that there is one more timeout. timeout specifies the waiting time, which is specified by the timespec structure.

3. Trigger

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号

These two functions can be used to notify a thread that a condition has been met. The pthread_cond_signal function will wake up a certain thread waiting for the condition, and the pthread_cond_broadcast function will wake up all processes waiting for the condition.

Note that the thread must be signaled after changing the state of the condition.

Example 3 : On the basis of Example 2, it is hoped that the thread1 thread will perform the g_data+1 operation first, then it can be realized by using the condition variable

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

int g_data = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;		//初始化互斥量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

static void* func(void *p)
{
    
    
	int cnt = 0;	//初始化计数变量
	puts("This is thread1");
	pthread_mutex_lock(&mutex);		//给互斥量加锁,避免其他线程操作g_data变量
	while(cnt < 3)
	{
    
    
		cnt++;
		printf("thread1:%d\n",g_data++);
		sleep(1);
	}
	pthread_cond_signal(&cond);		//唤醒等待的线程
	pthread_mutex_unlock(&mutex);	//解锁
	pthread_exit(NULL);	
}


int main()
{
    
    
	pthread_t tid;	
	int err;
	int cnt = 0;
	printf("Begin\n");
	err = pthread_create(&tid,NULL,func,NULL);
	if (err)
	{
    
    
		fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
		exit(1);
	}	
	pthread_mutex_lock(&mutex);		//给互斥量加锁,避免其他线程操作g_data变量
	pthread_cond_wait(&cond,&mutex);  //阻塞,等待被唤醒
	while(cnt<3)
	{
    
    
		cnt++;
		printf("main:%d\n",g_data++);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);	//解锁

	pthread_join(tid,NULL);		//等待线程退出
	pthread_mutex_destroy(&mutex);	//销毁互斥锁
	pthread_cond_destroy(&cond);
	printf("End\n");	
	exit(0);
}

Results of the:

[Rio@VM-8-17-centos pratise]$ ./pratise2
Begin
This is thread1
thread1:0
thread1:1
thread1:2
main:3
main:4
main:5
End

Guess you like

Origin blog.csdn.net/weixin_44517500/article/details/128274039