014Linux线程及线程控制相关函数

linux 学习笔记

  • 线程和线程控制相关函数
    • 1. 线程概念
      • 1.1 ps -Lf pid 查看线程
      • 1.2 线程共享/非共享资源
      • 1.3 线程 优缺点
    • 2. 线程控制函数
      • 2.1 pthread_self 函数
        • demo 打印当前进程的线程ID(相当于一个线程的进程)
      • 2.2 pthread_create 函数
        • demo 创建一个线程
        • demo 循环创建线程
    • 3. 线程与共享
      • 3.1 demo 线程共享全部变量
      • 3.2 pthread_exit 函数
        • demo 线程退出 分 success 和 error两版本
      • 3.3 pthread_join 函数
        • demo 回收线程终止状态
      • 3.4 pthread_cancel 函数
        • demo 杀死线程
      • 3.5 pthread_detch 函数
        • demo 线程分离
    • 4. 线程属性
      • 4.1 Linux 2.2 线程属性
      • 4.2 线程属性初始化
      • 4.3 线程的分离状态
        • demo 修改线程分离属性
      • 4.4 使用线程的注意事项

线程和线程控制相关函数

1. 线程概念

在一个进程内部,有时不一定只有一个执行流,在多执行流下,多个执行流共享了进程的地址空间,我们把“一个程序内部的控制序列”叫做线程

在Linux下,其实没有真正意义上的线程概念,是用进程来模拟的。

Linux 的线程也叫 LWP (light weight process) 轻量级的进程,本质仍是进程(在Linux环境下)

进程和线程的区别
进程:独立地址空间,拥有PCB
线程:有独立的PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间

Linux下:
线程:最小的执行单位
进程;最小分配资源单位,可看成是只有一个线程的进程

1.1 ps -Lf pid 查看线程

查看线程
ps -Lf pid		// 查看该进程的线程号(区别于进程ID)

014Linux线程及线程控制相关函数_第1张图片

1.2 线程共享/非共享资源

线程共享资源

  1. 文件描述符
  2. 每种信号的处理方式(mask不共享)
  3. 当前工作目录
  4. 用户ID和组ID
  5. 内存地址空间(.text / .data/ .bss/ heap 共享库)

线程非共享资源
6. 线程id
7. 处理器现场和栈指针(内核栈)
8. 独立的栈空间(用户空间栈)
9. errno变量
10. 信号屏蔽字
11. 调度优先级

1.3 线程 优缺点

优点:1. 提高程序的并发性 2. 开销小 3. 数据通信、共享数据方便
缺点:1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好

优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大

2. 线程控制函数

2.1 pthread_self 函数

获取线程ID,其作用对应进程中 getpid() 函数

man pthread_self		// obtain ID of the calling thread

#include

pthread_t pthread_self(void);

返回值:
成功:0
失败:无!

线程ID:pthread_t 类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
线程ID是进程内部,表示标志。(两个进程间,线程ID允许相同)

demo 打印当前进程的线程ID(相当于一个线程的进程)

/* pthread_create.c */
#include
#include
#include
#include
#include
#include
#include

void sys_error(const char * str)
{
	perror(str);
	exit(1);
}

int main(int argc, char * argv [ ])
{
	pthread_t tid;

	tid = pthread_self();

	printf("main:pid = %d, tid = %lu\n", getpid(), tid);

	return 0;
}

注意:编译和链接的时候需要加 -lpthread
在这里插入图片描述

2.2 pthread_create 函数

创建一个新线程。 其作用,对应进程中fork()函数

man pthread_creat		// creat a new thread

#include

int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void *(*start_routine)(void*), void *arg);

返回值:
成功:0
失败:返回错误号

参数:

pthread_t 		相当 typedef unsigned long int		pthread_t

thread: 传出参数,保存系统为我们分配好的线程ID
attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数
start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
arg:线程主函数执行期间所要用到的参数

在一个线程中调用pthread_creat() 创建新的线程后,当前线程从 pthread_create() 返回继续往下执行,而新的线程所执行的代码由我们传给 pthread_create 的函数指针 start_routine 决定。start_routine 函数接收一个参数,是通过 pthread_creat 的arg参数传递给它的,该参数的类型为 void*,这个指针按什么类型解释由调用者自己定义。

demo 创建一个线程

/* pthread_create1.c */
#include
#include
#include
#include
#include
#include
#include

void sys_error(const char * str)
{
	perror(str);
	exit(1);
}

void* tfn(void * arg)
{
	printf("thread:pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, char * argv [ ])
{
	pthread_t tid;


	printf("main:pid = %d, tid = %lu\n", getpid(), pthread_self());

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if(ret != 0)
		sys_error("pthread_create error\n");

	sleep(10);

	return 0;
}

在这里插入图片描述

demo 循环创建线程

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

void sys_error(const char * str)
{
	perror(str);
	exit(1);
}
void *tfn(void* arg)
{
	int i = (int)arg;		// 强转
	sleep(i);

	printf("--I'm %dth thread:pid = %d, tid = %lu\n", i+1, getpid(), pthread_self());

	return NULL;
}

int main(int argc, char * argv [])
{
	int i;
	int ret;
	pthread_t tid;
	

	for (i = 0 ; i < 5 ; i++)
	{
		ret = pthread_create(&tid, NULL, tfn, (void *)i);	// 注意强转传的是i的值
		if (ret != 0)
		{
			sys_error("pthread_creat error\n");
		}
	}

	sleep(i);
	printf("main:I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());
}

如果上述参数传递地方传的是i的地址,则会发生线程程序内调用的是main函数里面的i,调用晚的话,会出现已经i++的情况。

014Linux线程及线程控制相关函数_第2张图片

3. 线程与共享

线程间共享全局变量
线程默认共享数据段、代码段等地址空间,常用的就是全局变量。而进程不共享全局变量,只能借助mmap。

3.1 demo 线程共享全部变量

/* glb_var.c */
#include
#include
#include
#include

int var = 100;

void *tfn(void* arg)
{
	var = 200;

	printf("thread, var = %d\n", var);

	return NULL;
}

int main(int argc, char * argv [ ])
{
	printf("At first var = %d\n", var);

	pthread_t tid;
	pthread_create(&tid, NULL, tfn, NULL);
	sleep(1);

	printf("after pthread_create, var = %d\n", var);

	return 0;
}

014Linux线程及线程控制相关函数_第3张图片

3.2 pthread_exit 函数

将单个线程退出

man pthread_exit		// terminate calling thread

#include

void pthread_exit(void* retval);

参数:
retval 表线程退出状态,通常传NULL

demo 线程退出 分 success 和 error两版本

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

void sys_error(const char * str)
{
	perror(str);
	exit(1);
}
void *tfn(void* arg)
{
	int i = (int)arg;
	sleep(i);

	if (i == 2)
		//exit(0);			// exit 是退出进程,所以不打印后续线程
		//return NULL;		// return NULL 能实现退出线程,返回到调用者上面取
		pthread_exit(NULL);	// 将当前线程退出
	printf("--I'm %dth thread:pid = %d, tid = %lu\n", i+1, getpid(), pthread_self());

	return NULL;
}

int main(int argc, char * argv [])
{
	int i;
	int ret;
	pthread_t tid;
	

	for (i = 0 ; i < 5 ; i++)
	{
		ret = pthread_create(&tid, NULL, tfn, (void *)i);
		if (ret != 0)
		{
			sys_error("pthread_creat error\n");
		}
	}

	//sleep(i);		// 防止主进程退出,线程未执行,所以睡一会等线程先执行
	printf("main:I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());
	
	//return 0;		// 不使用return,而使用 pthread_exit退出主线程
	pthread_exit(NULL);
}

错误版本:(exit 是退出进程,所以不打印后续线程)
014Linux线程及线程控制相关函数_第4张图片
正确版本:
014Linux线程及线程控制相关函数_第5张图片

注意点:

  1. exit是退出进程! 导致后续的线程都无法执行
  2. return 是指返回到调用者上面,在线程回调函数中可以实现类似退出线程的效果

3.3 pthread_join 函数

阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid()函数

man pthread_join		// join with a terminated thread

#include

int pthread_join(pthread_t thread, void** retval);

返回值:
成功:0
失败:错误号

参数:
thread:线程ID
retval:存储线程结束状态

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join 得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用 pthread_cancel 异常终止掉, retval所指向的单元里存放的是常数PTHREAD_CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数
  4. 如果对thread线程终止状态不感兴趣,可以传NULL给retval参数

demo 回收线程终止状态

/* pthread_join.c */
#include
#include
#include
#include
#include
#include

void sys_error(const char * str)
{
	perror(str);
	exit(1);
}

struct thd {
	int var;
	char str[256];
};

void* tfn(void* arg)
{
	struct thd* tval;
	tval = malloc(sizeof(tval));
	tval->var =100;
	strcpy(tval->str, "hello thread");
	
	return (void*)tval;
}

/*
void* tfn(void* arg)
{
	struct thd tval;	// 这里之间传不行!! 因为他是一个局部变量
	tval.var =100;
	strcpy(tval.str, "hello thread");
	
	return (void*)&tval;	// 局部变量的地址不可做返回值
}
*/
int main(int argc, char * argv [ ])
{
	pthread_t tid;
	struct thd retval;

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if(ret != 0)
		sys_error("pthread_create error\n");

	ret = pthread_join(tid, (void**)&retval);
	if(ret != 0)
		sys_error("pthread_join error\n");

	printf("child thread exit with var = %d, str= %s\n", retval->var, retval->str);

	pthread_exit(NULL);
}

在这里插入图片描述

3.4 pthread_cancel 函数

杀死(取消)线程 其作用对应进程中的kill

int pthread_cancel(pthread_t thread);

返回值:
成功:0
失败:错误号

demo 杀死线程

/* pthread_cancel.c */
#include
#include
#include
#include
#include
#include
#include
#include


void *tfn(void* arg)
{		

	while(1){
	printf("thread: pid = %d, tid =%lu\n",  getpid(), pthread_self());
	sleep(1);
	}
	return NULL;
}

int main(int argc, char * argv [])
{

	int ret;
	pthread_t tid;

	ret = pthread_create(&tid, NULL, tfn, NULL);	
	if (ret != 0)
	{
		fprintf(stderr, "pthread_creat error:%s\n",strerror(ret));
		exit(1);
	}
	
	printf("main:I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());

	sleep(5);
	
	ret = pthread_cancel(tid);
	if (ret != 0)
	{
		fprintf(stderr, "pthread_creat error:%s\n",strerror(ret));
		exit(1);
	}

	while(1);
	
	pthread_exit(NULL);
}

014Linux线程及线程控制相关函数_第6张图片

子线程如果没有达到取消点,那么pthread_cancel无效,我们可以在程序中,手动添加一个取消点,pthread_testcancel();

成功被pthread_cancel()杀死的线程,返回-1,使用pthread_join 回收

/* pthread_testcancel.c */
#include
#include
#include
#include

void* tfn1(void * arg)
{
	printf("thread 1 returning\n");

	return (void*)111;
}

void* tfn2(void * arg)
{
	printf("thread 2 exiting\n ");

	pthread_exit((void*)222);
}

void* tfn3(void * arg)
{
	while(1)
		pthread_testcancel();	// 添加取消点,进内核取消
	return (void*)6666;
}

int main(int argc, char * argv [ ])
{
	pthread_t tid;
	void* tret;

	pthread_create(&tid, NULL, tfn1, NULL);
	pthread_join(tid, &tret);
	printf("thread 1 exit code = %d\n", (int)tret);

	pthread_create(&tid, NULL, tfn2, NULL);
	pthread_join(tid, &tret);
	printf("thread 2 exit code = %d\n", (int)tret);

	pthread_create(&tid, NULL, tfn3, NULL);
	sleep(3);
	pthread_join(tid, &tret);
	printf("thread 3 exit code = %d\n", (int)tret);

	return 0;
}

014Linux线程及线程控制相关函数_第7张图片

3.5 pthread_detch 函数

实现线程分离

man pthread_detch			// detach a thread

#include

int pthread_detch(pthread_t thread);

参数:
thread:待分离的线程id

返回值:
成功:0
失败:errno

在线程中检查错误方法:

fprintf(stderr, "pthread_join error:%s\n", strerror(ret));

demo 线程分离

/* pthread_detach.c */
#include
#include
#include
#include
#include
#include
#include

void* tfn(void * arg)
{
	printf("thread:pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, char * argv [ ])
{
	pthread_t tid;


	printf("main:pid = %d, tid = %lu\n", getpid(), pthread_self());

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if(ret != 0)
	{
		fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
		exit(1);
	}

	ret = pthread_detach(tid);
	if (ret != 0)
	{
		fprintf(stderr, "pthread_detach error:%s\n", strerror(ret));
		exit(1);
	}

	sleep(1);

	ret = pthread_join(tid, NULL);
	if (ret != 0)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(1);
	}

	printf("main:pid = %d, tid = %lu\n", getpid(),pthread_self());

	pthread_exit((void*)0);

	return 0;
}

014Linux线程及线程控制相关函数_第8张图片

4. 线程属性

4.1 Linux 2.2 线程属性

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

主要结构体成员:
1.线程分离状态
2. 线程栈大小(默认平均分配)
3. 线程栈警戒缓冲区大小(位于栈末尾)

属性值不能直接设置,须使用相关的函数进行操作,初始化函数为 pthread_attr_init,这个函数必须在 pthread_create函数之前调用, 之后须用 pthread_attr_destroy 函数来释放资源。

线程属性主要包括:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detaches state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

4.2 线程属性初始化

注意:应先初始化线程属性,在调用 pthread_create() 创建线程

初始化线程属性:
int pthread_attr_init(pthread_attr_t* attr);
销毁线程属性所占的系统资源:
int pthread_attr_destroy(pthread_attr_t* attr);

返回值:
成功:0
失败:errno

4.3 线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己
非分离状态,线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态

设置线程分离属性:分离or不分离
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstste);

获取线程分离属性:
int pthread_attr_getdetachstate(pthread_attr_t* attr, int* detachstate);

参数:
attr:已初始化的线程属性
detachstate:
     PTHREAD_CREATE_DETACHED(分离线程)
     PTHREAD_CREATE_JOINABLE(非分离线程)

  1. 创建一个线程属性结构体
  2. 初始化线程属性
  3. 设置线程属性为分离态
  4. 借助修改后的 设置线程属性 创建为分离态的新线程
  5. 销毁线程属性

demo 修改线程分离属性

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

void* tfn(void * arg)
{
	printf("thread:pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, char * argv [ ])
{
	pthread_t tid;
	int ret;
	pthread_attr_t attr;

	ret = pthread_attr_init(&attr);
	if (ret != 0)
	{
		fprintf(stderr, "attr_init error:%s\n", strerror(ret));
		exit(1);
	}

	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	ret = printf("main:pid = %d, tid = %lu\n", getpid(), pthread_self());
	if(ret != 0)
	{
		fprintf(stderr, "pthread_attr_setdetachstate error:%s\n", strerror(ret));
		exit(1);
	}

	ret = pthread_create(&tid, &attr , tfn, NULL);
	if(ret != 0)
	{
		fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
		exit(1);
	}

	ret = pthread_attr_destroy(&attr);
	if (ret != 0)
	{
		fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
		exit(1);
	}


	ret = pthread_join(tid, NULL);
	if (ret != 0)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(1);
	}

	printf("main:pid = %d, tid = %lu\n", getpid(),pthread_self());

	pthread_exit(NULL);

}

在这里插入图片描述

4.4 使用线程的注意事项

  1. 主程序退出其他线程不退出,主线程应调用 pthread_exit

  2. 避免僵尸线程
    pthread_join
    pthreaad_detach
    pthread_creat 指定分离属性

  3. malloc 和 mmap 申请的内存可以被其他线程释放

  4. 应避免在多线程模型中调用 fork 除非,马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit

  5. 信号的复杂语义很难和多线程共存,应避免在多线程引入引号机制

你可能感兴趣的:(LINUX,linux,运维,服务器)