Linux应用编程(五):多线程编程

一:线程概念

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

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

二:线程API

1、线程标识

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

#include 

pthread_t pthread_self(void);

2、线程创建

#include 

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 
void pthread_exit(void *retval);

 

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

#include 
int pthread_join(pthread_t thread, void **retval);
成功:返回0

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

#include 
int pthread_tryjoin_np(pthread_t thread, void **retval);
成功:返回0

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

#include 
int pthread_cancel(pthread_t thread);
成功:返回0

4、示例

#define _GNU_SOURCE 
#include 
#include 
#include 
#include 

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 
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
成功:返回0

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

2、销毁互斥锁

#include 
int pthread_mutex_destory(pthread_mutex_t *mutex);
成功:返回0

3、互斥锁加锁/解锁

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

#include 
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

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

#include 
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功:返回0

4、示例

#define _GNU_SOURCE 
#include 
#include 
#include 
#include 

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 
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 
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
成功:返回0

 

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

#include 
int sem_trywait(sem_t *sem);
成功:返回0

3、信号量销毁

#include 
int sem_destory(sem_t *sem);
成功:返回0

4、示例

#define _GNU_SOURCE 
#include 
#include 
#include 
#include 
#include 

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

 

 

 

 

你可能感兴趣的:(Linux应用开发)