Linux环境应用编程(四):多线程编程

一:线程概念

       线程,操作系统所能调度的最小单位。典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事情,有了多个控制线程以后,某一时刻每个线程都可以独立处理自己的任务。这种方法有许多好处:

  • 通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程模式,同步编程模式要比异步编程模式简单的多。
  • 与进程不同的是多线程可以自动的访问相同的存储地址空间和文件描述符。
  • 有些问题可以分解为多线程执行从而提高整个程序的吞吐量。
  • 交互的程序同样可以通过使用多线程来改善响应时间。

二:线程API

1、线程标识

线程ID使用pthread_t数据类型来表示。在线程中可以通过调用pthread_self函数来获得自身的线程ID

#include <pthread.h>

pthread_t pthread_self(void);

2、线程创建

#include <pthread.h>

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

3、线程终止

单个线程可以通过3种方式退出:

  • 线程可以简单地从启动例程中返回,返回值是线程的退出码。
  • 线程可以被同一进程中的其他线程取消。
  • 线程调用pthread_exit

该函数为线程退出函数,在退出时候可以传递一个void*类型的数据带给主线程,若选择不传出数据,可将参数填充为NULL。

进程中其他线程也可以通过调用pthread_join函数访问到这个指针。

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

该函数为线程回收函数,默认状态为阻塞状态,直到成功回收线程后被冲开阻塞。第一个参数为要回收线程的tid号,第二个参数为线程回收后接受线程传出的数据。

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

该函数为非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回0,其余参数与pthread_join一致。

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

线程还可以通过调用平thread_cancel函数来请求取消同一进程中的其他线程。

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

4、示例

#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;
}

三:线程同步控制

       多线程存在临界资源的竞争问题,为了解决这个问题,线程引出了互斥锁来解决临界资源访问。通过对临界资源加锁来保护资源只能被单个线程操作,待操作结束后解锁,其余线程才可以获得操作权。

1、初始化互斥锁

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

初始化互斥锁还可以调用宏来快速初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

2、销毁互斥锁

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

3、互斥锁加锁/解锁

如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。

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

如果线程不希望被阻塞,可以使用以下函数尝试对互斥量进行加锁。

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

4、示例

#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;
}

四:线程执行顺序控制

       互斥锁解决了线程临界资源访问的问题,但是线程执行顺序的问题还没有得到解决,因此引入了信号量的概念,通过信号量来控制线程的执行顺序。

1、信号量初始化

该函数可以初始化一个信号量,第一个参数传入sem_t类型的地址,第二个参数传入0代表线程控制,否则为进程控制,第三个参数表示信号量的初始值,0代表阻塞,1代表运行。待初始化结束信号量后,若执行成功会返回0。

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

2、信号量P/V操作(阻塞)

          sem_wait函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行“sem-1”的操作。所谓的“sem-1”是与上述初始化函数中第三个参数值一致,成功执行会返回0。

          sem_post函数会释放指定信号量的资源,执行“sem+1”操作。

通过以上2个函数可以完成所谓的PV操作,即信号量的申请与释放,完成对线程执行顺序的控制。

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

非阻塞信号量申请资源函数:

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

3、信号量销毁

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

4、示例

#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;
}

五:线程属性

pthread_create()函数的第二个参数(pthread_attr_t *attr)表示线程的属性。在上一个实例中,将该值设为 NULL,也就是采用默认属性,线程的多项属性都是可以更改的。这些属性主要包括绑定属性、分离属性、堆栈地址、堆栈大小以及优先级。其中系统默认的属性为非绑定、非分离、缺省 1M 的堆栈以及与父进程同样级别的优先级。下面首先对绑定属性和分离属性的基本概念进行讲解。

  • 绑定属性。

前面已经提到,Linux 中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为 CPU 时间片的调度是面向内核线程(也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之对应的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。

  • 绑定属性。

前面已经提到,Linux 中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为 CPU 时间片的调度是面向内核线程(也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之对应的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。

这些属性的设置都是通过特定的函数来完成的,通常首先调用 pthread_attr_init()函数进行初始化,之后再调用相应的属性设置函数,最后调用 pthread_attr_destroy()函数对分配的属性结构指针进行清理和回收。设置绑定属性的函数为 pthread_attr_setscope(),设置线程分离属性的函数为 pthread_attr_setdetachstate(),设置线程优先级的相关函数为 pthread_attr_getschedparam()(获取线程优先级)和 pthread_attr_setschedparam()(设置线程优先级)。在设置完这些属性后,就可以调用 pthread_create()函数来创建线程了。

#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、错误返回错误码

示例:为了避免不必要的复杂性,这里就创建一个线程,这个线程具有绑定和分离属性,而且主线程通过一个 finish_flag 标志变量来获得线程
结束的消息,而并不调用 pthread_join()函数。

/*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;
}

猜你喜欢

转载自blog.csdn.net/qq_34968572/article/details/107490102
今日推荐