Application Programming in Linux Environment (4): Multithreaded Programming

One: thread concept

       Thread, the smallest unit that the operating system can schedule. A typical UNIX process can be regarded as only one thread of control: a process can only do one thing at a certain time, and with multiple threads of control, each thread can handle its own tasks independently at a certain time. This method has many advantages:

  • By assigning separate processing threads for each event type, the code for handling asynchronous events can be simplified. Each thread can use the synchronous programming mode when processing events. The synchronous programming mode is much simpler than the asynchronous programming mode.
  • The difference with the process is that multiple threads can automatically access the same storage address space and file descriptors.
  • Some problems can be decomposed into multi-threaded execution to improve the throughput of the entire program.
  • Interactive programs can also improve response time by using multiple threads.

Two: Thread API

1. Thread ID

The thread ID is represented by the pthread_t data type. In the thread, you can get its own thread ID by calling the pthread_self function

#include <pthread.h>

pthread_t pthread_self(void);

2. Thread creation

#include <pthread.h>

int pthread_create(pthread_t *thread, 
                    const pthread_attr_t *attr,
                    void *(*start_routine) (void *), 
                    void *arg);
返回值:成功,返回0;否则返回错误编号

3. Thread termination

A single thread can exit in 3 ways:

  • The thread can simply return from the startup routine, and the return value is the thread's exit code.
  • A thread can be cancelled by other threads in the same process.
  • Thread calls pthread_exit

This function is a thread exit function. When exiting, you can pass a void* type of data to the main thread. If you choose not to send data, you can fill the parameter with NULL.

Other threads in the process can also access this pointer by calling the pthread_join function.

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

 

This function is a thread recovery function, and the default state is blocking state, until the thread is successfully recovered and blocked. The first parameter is the tid number of the thread to be recycled, and the second parameter is the data sent by the thread after the thread is recycled.

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
成功:返回0

This function is a non-blocking mode recycling function. It judges whether to recycle the thread by the return value. If the recycle is successful, it returns 0. The remaining parameters are the same as pthread_join.

#include <pthread.h>
int pthread_tryjoin_np(pthread_t thread, void **retval);
成功:返回0

A thread can also request cancellation of other threads in the same process by calling the thread_cancel function.

#include <pthread.h>
int pthread_cancel(pthread_t thread);
成功:返回0

4. Example

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

void *fun1(void *arg)
{
	printf("Pthread:1 come!\n");
	while(1){
		sleep(1);
	}
}

void *fun2(void *arg)
{
	printf("Pthread:2 come!\n");
	pthread_cancel((pthread_t )(long)arg);
	pthread_exit(NULL);
}

int main()
{
	int ret,i,flag = 0;
	void *Tmp = NULL;
	pthread_t tid[2];
	ret = pthread_create(&tid[0],NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);
	ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	while(1){
		for(i = 0;i <2;i++){
			if(pthread_tryjoin_np(tid[i],NULL) == 0){
				printf("Pthread : %d exit !\n",i+1);
				flag++;	
			}
		}
		if(flag >= 2) break;
	}
	return 0;
}

Three: thread synchronization control

       Multithreading has the problem of competition for critical resources. In order to solve this problem, threads introduce mutual exclusion locks to solve critical resource access. The protection of critical resources by locking the resources can only be operated by a single thread. After the operation is completed, the remaining threads can obtain the right to operate.

1. Initialize the mutex

#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
成功:返回0

To initialize a mutex, you can also call a macro to quickly initialize:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

2. Destroy the mutex

#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);
成功:返回0

3. Mutex lock/unlock

If the mutex is locked, the calling thread will block until the mutex is unlocked.

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

If the thread does not want to be blocked, you can use the following function to try to lock the mutex.

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功:返回0

4. Example

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

pthread_mutex_t mutex;

int Num = 0;

void *fun1(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(Num < 3){
		Num++;
		printf("%s:Num = %d\n",__FUNCTION__,Num);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

void *fun2(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(Num > -3){
		Num--;
		printf("%s:Num = %d\n",__FUNCTION__,Num);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t tid1,tid2;
	ret = pthread_mutex_init(&mutex,NULL);
	if(ret != 0){
		perror("pthread_mutex_init");
		return -1;
	}
	ret = pthread_create(&tid1,NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2,NULL,fun2,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

Four: thread execution order control

       Mutex locks solve the problem of thread critical resource access, but the problem of thread execution order has not been solved, so the concept of semaphore is introduced, and the execution order of threads is controlled by semaphore.

1. Semaphore initialization

This function can initialize a semaphore, the first parameter is passed in the address of type sem_t, the second parameter is passed in 0 to represent thread control, otherwise it is process control, the third parameter represents the initial value of the semaphore, and 0 represents blocking. 1 means running. After the semaphore is initialized, it will return 0 if the execution is successful.

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
成功:返回0

2. Semaphore P/V operation (blocking)

          The function of sem_wait is to detect whether the specified semaphore has resources available. If no resources are available, it will block the waiting. If resources are available, it will automatically execute the "sem-1" operation. The so-called "sem-1" is consistent with the value of the third parameter in the above initialization function, and it will return 0 if it is successfully executed.

          The sem_post function will release the resources of the specified semaphore and perform the "sem+1" operation.

Through the above two functions, the so-called PV operation can be completed, that is, the application and release of the semaphore, and the control of the thread execution sequence is completed.

#include <pthread.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
成功:返回0

 

Non-blocking semaphore application resource function:

#include <pthread.h>
int sem_trywait(sem_t *sem);
成功:返回0

3. Semaphore destruction

#include <pthread.h>
int sem_destory(sem_t *sem);
成功:返回0

4. Example

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>

sem_t sem1,sem2,sem3;//申请的三个信号量变量

void *fun1(void *arg)
{
	sem_wait(&sem1);//因sem1本身有资源,所以不被阻塞 获取后sem1-1 下次会会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem2);// 使得sem2获取到资源
	pthread_exit(NULL);
}

void *fun2(void *arg)
{
	sem_wait(&sem2);//因sem2在初始化时无资源会被阻塞,直至14行代码执行 不被阻塞 sem2-1 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem3);// 使得sem3获取到资源
	pthread_exit(NULL);
}

void *fun3(void *arg)
{
	sem_wait(&sem3);//因sem3在初始化时无资源会被阻塞,直至22行代码执行 不被阻塞 sem3-1 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem1);// 使得sem1获取到资源
	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t tid1,tid2,tid3;
	ret = sem_init(&sem1,0,1);  //初始化信号量1 并且赋予其资源
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = sem_init(&sem2,0,0); //初始化信号量2 让其阻塞
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = sem_init(&sem3,0,0); //初始化信号3 让其阻塞
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程1
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程2
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid3,NULL,fun3,NULL);//创建线程3
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	/*回收线程资源*/
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_join(tid3,NULL);

	/*销毁信号量*/
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	sem_destroy(&sem3);

	return 0;
}

Five: thread attributes

The second parameter (pthread_attr_t *attr) of the pthread_create() function represents the attributes of the thread. In the previous example, the value is set to NULL, that is, the default attributes are used, and multiple attributes of the thread can be changed. These attributes mainly include binding attributes, separation attributes, stack address, stack size, and priority. The default attributes of the system are non-binding, non-separation, a default 1M stack, and the same priority as the parent process. The following first explains the basic concepts of binding attributes and separating attributes.

  • Binding attributes.

As mentioned earlier, Linux uses a "one-to-one" threading mechanism, that is, one user thread corresponds to one kernel thread. The binding attribute means that a user thread is fixedly assigned to a kernel thread, because the scheduling of CPU time slices is oriented to kernel threads (that is, lightweight processes), so threads with binding attributes can guarantee total There is a kernel thread corresponding to it. The corresponding unbound attribute means that the relationship between user threads and kernel threads is not always fixed, but is controlled by the system.

  • Binding attributes.

As mentioned earlier, Linux uses a "one-to-one" threading mechanism, that is, one user thread corresponds to one kernel thread. The binding attribute means that a user thread is fixedly assigned to a kernel thread, because the scheduling of CPU time slices is oriented to kernel threads (that is, lightweight processes), so threads with binding attributes can guarantee total There is a kernel thread corresponding to it. The corresponding unbound attribute means that the relationship between user threads and kernel threads is not always fixed, but is controlled by the system.

The settings of these attributes are done through specific functions. Usually the pthread_attr_init() function is called first to initialize, then the corresponding attribute setting function is called, and finally the pthread_attr_destroy() function is called to clean up and reclaim the assigned attribute structure pointer. The function to set the binding attribute is pthread_attr_setscope(), the function to set the thread detachment attribute is pthread_attr_setdetachstate(), and the related functions to set the thread priority are pthread_attr_getschedparam() (get thread priority) and pthread_attr_setschedparam() (set thread priority). After setting these properties, you can call the pthread_create() function to create a thread.

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr)
attr:线程属性结构指针
返回值: 成功0、 错误返回错误码


int pthread_attr_setscope(pthread_attr_t *attr, int scope)
attr:线程属性结构指针
scope:{
    PTHREAD_SCOPE_SYSTEM:绑定
    PTHREAD_SCOPE_PROCESS:非绑定 
}
返回值:成功0、错误-1


int pthread_attr_setscope(pthread_attr_t *attr, int detachstate)
attr:线程属性
detachstate:{
    PTHREAD_CREATE_DETACHED:分离
    PTHREAD _CREATE_JOINABLE:非分离
}
返回值:成功0、错误返回错误码

int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param)
attr:线程属性结构指针
param:线程优先级
返回值:成功:0、错误返回错误码

int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param)
attr:线程属性结构指针
param:线程优先级
返回值:成功0、错误返回错误码

Example: In order to avoid unnecessary complexity, a thread is created here. This thread has binding and detachment properties, and the main thread obtains the
end of the thread through a finish_flag flag variable , instead of calling the pthread_join() function.

/*thread_attr.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define REPEAT_NUMBER 3 /* 线程中的小任务数 */
#define DELAY_TIME_LEVELS 10.0 /* 小任务之间的最大时间间隔 */
int finish_flag = 0;

void *thrd_func(void *arg)
{
    int delay_time = 0;
    int count = 0;
    printf("Thread is starting\n");
    for (count = 0; count < REPEAT_NUMBER; count++)
    {
        delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
        sleep(delay_time);
        printf("\tThread : job %d delay = %d\n", count, delay_time);
    }
    printf("Thread finished\n");
    finish_flag = 1;
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t thread;
    pthread_attr_t attr;
    int no = 0, res;
    void * thrd_ret;
    srand(time(NULL));
    /* 初始化线程属性对象 */
    res = pthread_attr_init(&attr);
    if (res != 0)
    {
        printf("Create attribute failed\n");
        exit(res);
    }
    /* 设置线程绑定属性 */
    res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    /* 设置线程分离属性 */
    res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (res != 0)
    {
        printf("Setting attribute failed\n");
        exit(res);
    }
    res = pthread_create(&thread, &attr, thrd_func, NULL);
    if (res != 0)
    {
        printf("Create thread failed\n");
        exit(res);
    }
    /* 释放线程属性对象 */
    pthread_attr_destroy(&attr);
    printf("Create tread success\n");
    while(!finish_flag)
    {
        printf("Waiting for thread to finish...\n");
        sleep(2);
    }
    return 0;
}

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/107490102