Linux system programming-multi-threaded programming

1. Introduce:

  • A typical UNIX/Linux process can be seen as having only one thread of control: a process can only do one thing at a time. With multiple threads of control, the process can be designed to do more than one thing at the same time during program design, and each thread handles independent tasks.

  • A process is an instance of a program when it is executed, and it is the basic unit responsible for allocating system resources (CPU time, memory, etc.). In a thread-oriented system, the process itself is not a basic unit of operation, but a container for threads. The program itself is only a description of instructions, data and its organization, and the process is the real running instance of the program (those instructions and data).

  • Thread is the smallest unit that the operating system can perform operation scheduling. It is included in the process and is the actual operating unit in the process. A thread refers to a single sequential control flow in a process. Multiple threads can be concurrent in a process, and each thread executes different tasks in parallel. The thread contains the necessary information to represent the execution environment in the process, including the thread ID of the thread in the process, a set of register values, stack, scheduling priority and strategy, signal mask, errno constant, and thread private data. All information of the process is shared by all threads of the process, including executable program text, program global memory and heap memory, stack, and file descriptors. In Unix and Unix-like operating systems, threads are also called lightweight processes, but lightweight processes are more referred to as kernel threads, and user threads are called threads. .

  • A process has an independent address space. After a process crashes, it will not affect other processes in the protected mode, and a thread is just a different execution path in a process. The thread has its own stack and local variables, but the thread does not have a separate address space. The death of a thread means the death of the entire process. Therefore, a multi-process program is stronger than a multi-threaded program, but it consumes more resources when switching between processes. Large, the efficiency is worse. But for some concurrent operations that require simultaneous execution and share certain variables, only threads can be used, not processes.

Reasons for using threads:

  • From the above, we know the difference between processes and threads. In fact, these differences are the reason why we use threads. In general: the process has an independent address space, and the thread does not have a separate address space (threads in the same process share the address space of the process).

  • We know that under the Linux system, to start a new process, it must be allocated an independent address space, and numerous data tables are established to maintain its code segment, stack segment and data segment. This is a kind of "expensive" multitasking. Way of working. And multiple threads running in a process, they use the same address space with each other, share most of the data, the space spent to start a thread is much less than the space spent to start a process, and the threads switch between each other The time required is far less than the time required to switch between processes. According to statistics, in general, the cost of a process is about 30 times the cost of a thread. Of course, this data may be quite different on a specific system.

  • The second reason for using multithreading is the convenient communication mechanism between threads. For different processes, they have independent data spaces, and data transmission can only be done through communication. This method is not only time-consuming, but also very inconvenient. This is not the case with threads. Because the data space is shared between threads in the same process, 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 other problems. Some variables cannot be modified by two threads at the same time, and data declared as static in some subprograms is more likely to bring catastrophic damage to multithreaded programs. It is the most important thing to pay attention to when writing multithreaded programs.

In addition to the advantages mentioned above, multi-threaded programs, as a multi-tasking and concurrent work method, certainly have the following advantages:

  • Improve application response. This is especially meaningful for programs with graphical interfaces. When an operation takes a long time, the entire system will wait for this operation. At this time, the program will not respond to keyboard, mouse, and menu operations. Using multithreading technology will take a long time. The operation (time consuming) is placed in a new thread to avoid this embarrassing situation.
  • Make the multi-CPU system more effective. The operating system will ensure that when the number of threads is not greater than the number of CPUs, different threads run on different CPUs.
  • 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 facilitate understanding and modification.

This article is partly taken from (A Preliminary Study of Linux Multithreaded Programming) .

2. Summary:

Multi-threaded development has mature pthread library support on the Linux platform. The most basic concepts involved in multi-threaded development mainly include three points: threads, mutual exclusion locks, and conditions. Among them, thread operations are divided into three types: thread creation, exit, and waiting. Mutex includes 4 operations, namely creation, destruction, locking and unlocking. There are 5 types of conditional operations: create, destroy, trigger, broadcast and wait. Some other thread extension concepts, such as semaphores, can be encapsulated through the basic operations of the three basic elements above.
Insert picture description here

3. Thread:

#include <pthread.h>

线程创建:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void*), void *restrict arg);
//返回值:若成功返回 0,否则返回错误编号
// tidp 参数:当 pthread_create 成功返回时,由 tidp 指向的内存单元被设置为新创建线程的线程 ID
// attr 参数:attr 参数用于定制各种不同的线程属性,暂可以把它设置为 NULL,以创建默认属性的线程
/* 新创建的线程从 start_rtn 函数的地址开始运行,该函数只有一个无类型指针参数 arg 。如果需要向 start_rtn 函数传递的参数不止一
个,那么需要把这些参数放到一个结构体中,然后把这个结构体的地址作为 arg 参数传入 */



线程退出:
/*单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:1、线程只是从启动例程中返回,返回值是线程的退出码
																	 2、线程可以被同一进程中的其他线程取消
																	 3、线程调用 pthread_exit					 */
int pthread_exit(void *rval_ptr);
/* rval_ptr 参数:退出时,把 static 定义的数据或全局变量放入 rval_ptr,作为线程退出的返回值,进程中的其他线程可以通过
调用 pthread_join 函数访问到这个数据 */



等待线程:
int pthread_join(pthread_t thread, void **rval_ptr);
// join 可以回收 exit 的返回状态
//返回值:若成功返回 0,否则返回错误编号
// thread 参数:等待的线程的 ID
//调用这个函数的线程将一直阻塞,直到指定的线程调用 pthread_exit 、从启动例程中返回或者被取消

/* 如果例程只是从它的启动例程返回,rval_ptr 将包含返回码。如果线程被取消,由 rval_ptr 指定的内存单元就置为 PTHREAD_CANCELED
可以通过调用 pthread_join 自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join 调用就会失败,
返回 EINVAL 如果对线程的返回值不感兴趣,可以把 rval_ptr 置为 NULL			*/



线程脱离:
int pthread_detach(pthread_t thread);
//把指定的线程转变为脱离状态,某个线程被 join 后,调用 detach 可脱离 join
//返回值:若成功返回 0,否则返回错误编号
// thread 参数:调用线程的 ID
//本函数通常由想让自己脱离的线程使用:pthread_detach(pthread_self());



线程自身 ID 获取:
pthread_t pthread_self(void);
//获取线程自身的线程 ID
//返回值:调用该函数的线程的 ID



线程 ID 比较:
int pthread_equal(pthread_t tid1, pthread_t tid2);
//比较是否属于同一线程
//返回值:若相等则返回非 0 值,否则返回 0
/*对于线程 ID 比较,为了可移植操作,我们不能简单地把线程 ID 当作整数来处理,因为不同系统对线程 ID 的定义可能不一样,所以我们
用到该函数 */

4. Mutex lock:

  • A mutex 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 threads that try 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 that becomes 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.

  • In the design, it is necessary to stipulate that all threads must comply with the same data access rules. Only in this way can the mutual exclusion mechanism work properly. The operating system does not serialize data access. If one of the threads is allowed to access the shared resource without obtaining the lock, even if the other threads acquire the lock before using the shared resource, there will still be data inconsistencies.

  • 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 mutex), or it can be initialized by calling the pthread_mutex_init function. If the mutex is allocated dynamically (for example, by calling the malloc function), then pthread_mutex_destroy needs to be called before the memory is released.

#include <pthread.h>

创建互斥锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);		//动态初始化
//返回值:若成功返回 0,否则返回错误编号
//参数 restrict mutex:互斥量,定义 pthread_mutex_t 型全局变量
//参数 restrict attr:要用默认的属性初始化互斥量,则把 attr 设置为 NULL
pthread_mutex_t mutex/*互斥量名*/= PTHREAD_MUTEX_INITIALIZER;					//静态初始化



销毁互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//返回值:若成功返回 0,否则返回错误编号
//参数 restrict mutex:需要销毁的互斥量,定义的 pthread_mutex_t 型全局变量



添加互斥锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
//返回值:若成功返回 0,否则返回错误编号



解除互斥锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//返回值:若成功返回 0,否则返回错误编号



尝试互斥锁:
int pthread_mutex_trylock(pthread_mutex_t mutex);
//如果线程不希望被阻塞,它可以使用 pthread_mutex_trylock 尝试对互斥量进行加锁
/*如果调用 pthread_mutex_trylock 时互斥量处于未锁住状态,那么 pthread_mutex_trylock 将锁住互斥量,不会出现阻塞并返回 0,否
则 pthread_mutex_trylock就会失败,不能锁住互斥量,而返回 EBUSY		*/

5. Condition variables:

  • Condition variables are another synchronization mechanism available to threads. Condition variables provide a meeting place for multiple threads. When a condition variable is used with a mutex, it allows threads to wait for a specific condition to occur in a non-competitive manner.

  • The condition itself is protected by a mutex. The thread must first lock the mutex before changing the condition state, and other threads will not be aware of this change before obtaining the mutex, because the mutex must be locked before the condition can be calculated.

  • The condition variable must be initialized before use. 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 determine the condition. Variables are deinitialized.

#include <pthread.h>

创建条件变量:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);			//动态初始化
//返回值:若成功返回0,否则返回错误编号
// restrict cond 参数:条件变量,定义一个 pthread_cond_t 型全局变量
// attr 参数:除非需要创建一个非默认属性的条件变量,否则可以设置为NULL
pthread_cond_t cond/*条件变量名*/= PTHREAD_COND_INITIALIZER;				//静态初始化



销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond);
//返回值:若成功返回0,否则返回错误编号
// restrict cond 参数:需要销毁的条件变量,定义的 pthread_cond_t 型全局变量



等待条件变量:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
//返回值:若成功返回0,否则返回错误编号
//与互斥量绑定,避免竞争
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
/* pthread_cond_timedwait 函数的工作方式与 pthread_cond_wait 数类似,只是多了一个 timeout。timeout 指定了等待的时间,它是
通过 timespec 结构指定 */



触发条件变量:
int pthread_cond_signal(pthread_cond_t *cond);
// pthread_cond_signal 函数将唤醒等待该条件的某个线程
//返回值:若成功返回0,否则返回错误编号



广播条件变量:
int pthread_cond_broadcast(pthread_cond_t *cond);
// pthread_cond_broadcast 函数将唤醒等待该条件的所有进程
//返回值:若成功返回0,否则返回错误编号

6. Example:

! ! Note: Use -lpthread link when compiling
Thread ID is used unsigned long Types of

(1)

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

void *func1(void *arg)
{
    
    
	static int ret = 888;

	printf("t1: %ld thread is creat\n",(unsigned long)pthread_self());		//获取自身线程 ID
	printf("t1: num = %d\n",*((int *)arg));
	
	pthread_exit((void *)&ret);						//线程退出
}

void *func2(void *arg)
{
    
    
	static char *p = "lcx handsome!";

	printf("t2: %ld thread is creat\n",(unsigned long)pthread_self());		//获取自身线程 ID
	printf("t2: num = %d\n",*((int *)arg));
	
	pthread_exit((void *)p);						//线程退出
}

void main()
{
    
    

	int ret1,ret2;
	int num = 100;
	pthread_t t1;
	pthread_t t2;

	int *pret1;
	char *pret2;
	
	ret1 = pthread_create(&t1,NULL,func1,(void *)&num);				//创建线程1
	if(ret1 == 0){
    
    
		printf("main: thread t1 creat success\n");
		printf("main: %ld \n",(unsigned long)pthread_self());		//获取自身线程 ID
	}

	ret2 = pthread_create(&t2,NULL,func2,(void *)&num);				//创建线程2
	if(ret2 == 0){
    
    
		printf("main: thread t2 creat success\n");
		printf("main: %ld \n",(unsigned long)pthread_self());		//获取自身线程 ID
	}
	pthread_join(t1,(void **)&pret1);								//等待线程
	pthread_join(t2,(void **)&pret2);								//等待线程

	printf("t1 quit: %d\n",*pret1);
	printf("t2 quit: %s\n",pret2);
}

operation result:
Insert picture description here
The running order of the three threads is not necessarily

(2)

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

pthread_mutex_t mutex;

void *func1(void *arg)
{
    
    
	pthread_mutex_lock(&mutex);							//添加互斥锁

	printf("t1: %ld thread is creat\n",(unsigned long)pthread_self());		//获取自身线程 ID
	printf("t1: num = %d\n",*((int *)arg));

	pthread_mutex_unlock(&mutex);						//解除互斥锁
	
}

void *func2(void *arg)
{
    
    
	pthread_mutex_lock(&mutex);							//添加互斥锁

	printf("t2: %ld thread is creat\n",(unsigned long)pthread_self());		//获取自身线程 ID
	printf("t2: num = %d\n",*((int *)arg));
	
	pthread_mutex_unlock(&mutex);						//解除互斥锁
}

void main()
{
    
    

	int ret1,ret2;
	int num = 100;
	pthread_t t1;
	pthread_t t2;

	pthread_mutex_init(&mutex,NULL);							//创建互斥锁

	ret1 = pthread_create(&t1,NULL,func1,(void*)&num);			//创建线程1
	if(ret1 == 0){
    
    
		printf("main: thread t1 creat success\n");
	}

	ret2 = pthread_create(&t2,NULL,func2,(void*)&num);			//创建线程2
	if(ret2 == 0){
    
    
		printf("main: thread t2 creat success\n");
	}

	printf("main: %ld \n",(unsigned long)pthread_self());		//获取自身线程 ID
	sleep(1);

	pthread_mutex_destroy(&mutex);								//销毁互斥锁
}

operation result:
Insert picture description here

(3)

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

int g_data = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;

void *func1(void *arg)
{
    
    
	while(1){
    
    
		pthread_cond_wait(&cond,&mutex);					//等待条件变量
		printf("----------t1 run----------\n");

		printf("t1:%d\n",g_data);
		g_data = 0;
	}
}

void *func2(void *arg)
{
    
    

	while(1){
    
    
		printf("t2:%d\n",g_data);
		pthread_mutex_lock(&mutex);							//添加互斥锁
		g_data++;

		if(g_data == 3){
    
    
			pthread_cond_signal(&cond);						//触发条件变量
		}

		pthread_mutex_unlock(&mutex);						//解除互斥锁
		sleep(1);
	}
}

void main()
{
    
    
	int ret1,ret2;
	int num = 100;
	pthread_t t1;
	pthread_t t2;

	pthread_mutex_init(&mutex,NULL);							//创建互斥锁
	pthread_cond_init(&cond,NULL);								//创建条件变量

	ret1 = pthread_create(&t1,NULL,func1,(void*)&num);			//创建线程1
	ret2 = pthread_create(&t2,NULL,func2,(void*)&num);			//创建线程2

	pthread_join(t1,NULL);										//等待线程
	pthread_join(t2,NULL);										//等待线程
	
	pthread_mutex_destroy(&mutex);								//销毁互斥锁
	pthread_cond_destroy(&cond);								//销毁条件变量
}

Running result:
Insert picture description here
loop

Guess you like

Origin blog.csdn.net/lcx1837/article/details/113917651