Linux&Apue(0.3.0):多线程基本知识&多线程编程

(一)多线程基本知识

为什么要学习线程呢?之前我们不是学了进程吗?通过创建进程一样可以实现多个程序的并发运行。那么我们学习线程的意义在于什么?
意义
假设,在我们完成相关任务的不同代码间需要交换数据时候。
如果采用多进程并发方式:
①进程创建比线程创建花费时间多
②进程之间通信比较麻烦
采用多线程方式:
①共享全局变量,线程间数据交换会比较高效。

(1) 什么是线程?多线程?

线程:是操作系统能够进行运算调度的最小单位,是进程的一条执行路径,在Unix系统下也被称为轻量级进程(轻量进程更多指内核线程,而把用户线程称为线程)。所有的线程都是在同一进程空间运行,这也意味着多条线程将共享该进程中的全部系统资源。
多线程:一个进程可以有很多线程,每条线程并行执行不同的任务。
Linux&Apue(0.3.0):多线程基本知识&多线程编程_第1张图片

(2)多线程中的主线程&子线程

就像我们多进程一样,我们有父进程,有子进程。那么多线程也一样,有主线程,有子线程等。

2.1 主线程

主线程:一个进程创建后,会首先生成一个缺省的线程,称为主线程。

2.2 子线程

子线程:由主线程调用pthread_create()创建的线程。
特点
①有自己的入口函数,该函数由用户在创建的时候指定。
②每个线程都有自己的线程ID,可以通过pthread_self()函数获取。

2.3 主线程与子线程的关系

主线程和子线程的默认关系:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。
产生的问题
①当进程结束或僵死后,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态。(这就像工厂要关门了,结果发现里面还有工人,他们还没有完成任务。这时工厂如果要关门,只有把它们赶走后才能关门)
②线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。(这就像工厂要关门了,催促工人赶紧走,但是有些工人急急忙忙走后,但运行的机器没有被关闭)
所以,我们需要提前设置线程属性去解决这些问题。

2.3.1 可会合

可会合:主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。(简单来说:就是上面提到的,工厂需要确保工人和机器都关闭了再关门)

2.3.2 相分离

相分离:表示子线程无需和主线程会合,也就是相分离的。这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。(简答来说:工厂虽然关门了,但是让工人们完成自己任务后,自己从后门出去(释放系统资源))

(3) 多线程中常用函数

3.1 pthread_create()函数

pthread_create()函数:创建一个线程。

#include 		//头文件包含
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void*arg);
参数 功能
*thread 指向线程标识符的指针 (返回线程ID)
pthread_attr_t*attr 设置线程属性。
start_routine 线程运行函数的起始地址
arg 运行函数的参数

详解:void *(*start_routine) (void *)
定义函数指针: start_routine
返回值:void *(一个指针)
参数:void *(一个指针)
pthread_attr_t定义

typedef struct
{
	int detachstate; 					//线程的分离状态
	int schedpolicy; 					//线程调度策略
	struct sched_param schedparam; 		//线程的调度参数
	int inheritsched; 					//线程的继承性
	int scope; 							//线程的作用域
	size_t guardsize; 					//线程栈末尾的警戒缓冲区大小
	int stackaddr_set;
	void * stackaddr; 					//线程栈的位置
	size_t stacksize; 					//线程栈的大小
}pthread_attr_t;

3.1.1 pthread_attr_init ()函数

pthread_attr_init ()函数:初始化一个线程对象的属性。
注意:一般调用了pthread_attr_init()函数初始化线程属性后,要用pthread_attr_destroy()函数对其去初始化

int pthread_attr_init(pthread_attr_t *attr);

参数:要初始化线程属性结构的指针

3.1.2 pthread_attr_destroy()函数

pthread_attr_destroy()函数:销毁一个目标结构,并且使它在重新初始化之前不能重新使用。

int pthread_attr_destroy(pthread_attr_t *attr);

参数:要删除的线程属性结构体指针

3.1.3 pthread_attr_setstacksize()函数

int pthread_attr_setstacksize()函数:设置线程堆栈大小

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

默认堆栈大小: 8388608; 堆栈最小:16384 。单位:字节

3.1.4 pthread_attr_setdetachstate()函数

pthread_attr_setdetachstate()函数:设置线程关系是可会合还是相分离(默认:可会合)

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);		//设置为相分离

3.2 pthread_join()函数

pthread_join()函数:等待一个线程的结束,线程间同步的操作。
注意:一个线程不能被多个线程等待,也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返回ESRCH 错误。

#include		//头文件包含
 int pthread_join(pthread_t thread, void **retval);
参数 功能
thread 线程ID
retval 用户定义的指针,用来存储被等待线程的返回值

示例:

pthread_join(tid, NULL);

3.3 pthread_exit()函数

pthread_exit()函数:终止调用它的线程并返回一个指向某个对象的指针。

void pthread_exit(void* retval);
pthread_exit()

(二)多线程编程

目的:用简单例子讲解多线程创建基本使用,并且了解子线程共享主线程的数据。

(1) 设置线程属性

#include 
#include 
#include 
#include 
#include 
#include 

void *thread_worker1(void *args);
void *thread_worker2(void *args);
int main(int argc, char **argv)
{
	int shared_var = 1000;			//导入线程的参数
	pthread_t tid;					//线程ID
	pthread_attr_t thread_attr;		//线程属性

	//一、设置线程属性
	//初始化线程属性
	if(pthread_attr_init(&thread_attr))
	{
 		printf("pthread_attr_init() failure: %s\n", strerror(errno));
 		return -1;
	}

	//设置线程堆栈大小
	if( pthread_attr_setstacksize(&thread_attr, 120*1024) )
       	{
 		printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
 		return -2;
	}
	//设置线程关系:分离还是会合  默认是会合,这里设置是分离
	if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) )
	{
	 	printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
	 	return -3;
	}

(2) 创建子线程,并用while让主线程不断工作

	//二、创建线程
	//线程1
	pthread_create(&tid, &thread_attr, thread_worker1, &shared_var);	//线程ID 线程属性
	printf("Thread worker1 tid[%ld] created ok\n", tid);
	//线程2
	pthread_create(&tid, NULL, thread_worker2, &shared_var);
	printf("Thread worker2 tid[%ld] created ok\n", tid);
	//销毁线程
	pthread_attr_destroy(&thread_attr);
	
	//等待线程2退出再执行下面代码
	pthread_join(tid, NULL);

	//主线程工作
	while(1)
	{
		printf("Main/Control thread shared_var: %d\n", shared_var);
		 sleep(10);
	}
}

简析:
①调用pthread_attr_destroy():对其去除初始化(pthread_attr_init)
②调用pthread_join():因为worker1设置了相分离的关系,所以并不会返回主线程。所以,这里pthread_join()函数等待的是worker2结束。

(3) 子线程运行函数

void *thread_worker1(void *args)
{
    int *ptr = (int *)args;
	if( !args )
	{
		printf("%s() get invalid arguments\n", __FUNCTION__);
		pthread_exit(NULL);	//若用exit会退出进程
	}

	printf("Thread workder 1 [%ld] start running...\n", pthread_self());
	//子线程工作
	while(1)
	{
		printf("+++: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
		*ptr += 1;
		sleep(2);
		printf("+++: %s after sleep shared_var: %d\n", __FUNCTION__, *ptr);
	}
	printf("Thread workder 1 exit...\n");
	return NULL;
}
void *thread_worker2(void *args)
{
	int *ptr = (int *)args;
	if( !args )
	{
		printf("%s() get invalid arguments\n", __FUNCTION__);
		pthread_exit(NULL);
	}
	//子线程工作
	printf("Thread workder 2 [%ld] start running...\n", pthread_self());
	while(1)
	{
		printf("---: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
		*ptr += 1;
		sleep(2);
		printf("---: %s after sleep shared_var: %d\n", __FUNCTION__, *ptr);
	}
	printf("Thread workder 2 exit...\n");

	return NULL;
}

简析:
①这里__FUNCTION__作用是:获取函数名。

(4) 编译代码并执行程序

gcc thread.c -o thread -lpthread
./thread

(5) 结果显示

Linux&Apue(0.3.0):多线程基本知识&多线程编程_第2张图片

你可能感兴趣的:(Linux&Apue(0.3.0):多线程基本知识&多线程编程)