Linux程序设计-6.线程

6. 线程

什么是线程?
LWP: light weight process 轻量级的进程,本质仍是进程(Linux环境下)
进程:独立空间地址,拥有PCB
线程:也有PCB,但是没有独立的地址空间(共享)
区别:在于是否共享空间地址
Linux下:

线程:最小的执行单位
进程:最小的分配资源单位,可以看成是一个线程的进程

Linux内核线程实现原理:

  • 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone()
  • 从内核里看进程和线程是一样的,都有各自不同的PCB
  • 进程至少有一个线程
  • 线程可以看作寄存器和栈的集合
  • 在linux下,线程是最小的执行单位;进程是最小的资源分配单位

查看LWP(线程编号)号:ps -Lf pid查看指定线程的LWP号

线程共享资源

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

线程非共享资源

  • 线程ID
  • 处理器现场和栈指针(内核栈)
  • 独立的栈空间(用户空间栈)
  • errno变量
  • 信号屏蔽字
  • 调度优先级

获得错误码对应的错误信息:
char *strerror(int errnum);

线程的优点:

  • 提高并发性
  • 占用资源量小
  • 通信方便

缺点

  • 调试困难
  • 库函数不稳定
  • 对信号支持不好

有于Linux系统早期并没有线程,是在后期版本中加入,由于实现方法导致进程和线程差别不是很大

6.1 线程控制原语

6.1.1 获取线程IDpthread_self()

帮助手册:man pthread_self
包含头文件:

  • #include <pthread.h>

函数原型:

  • pthread_t pthread_self(void);

函数说明:

获取线程ID.其作用对应进程中的getpid()函数。
线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
线程ID是进程内部识别标志。(两个进程间,线程ID允许相同)
注意:不应使用全局变量pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self
编译和链接时需加上-lpthread

参数说明
return返回调用线程的ID

6.1.2 创建一个新线程pthread_create()

帮助手册:man pthread_create
包含头文件:

  • #include <pthread.h>

函数原型:

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

函数说明:

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

编译和链接时需加上-lpthread

参数说明
thread新线程ID(传出)
attr线程的属性信息(传入)
start_routine线程执行的函数
arg函数执行的参数
return成功:0
失败:errno

示例:创建一个线程:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>

void* thr(void *arg)
{
	printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
	return NULL;
}

int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	printf("I am a main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
	sleep(1);
	return 0;
}

运行结果:
创建一个线程

6.1.3 终止调用线程pthread_exit()

帮助手册:man pthread_exit
包含头文件:

  • #include <pthread.h>

函数原型:

  • void pthread_exit(void *retval);

编译和链接时需加上-lpthread

参数说明
retval退出值(传入)

6.1.4 回收线程pthread_join()

帮助手册:man pthread_join
包含头文件:

  • #include <pthread.h>

函数原型:

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

函数说明:

阻塞等待回收

编译和链接时需加上-lpthread

参数说明
thread创建线程时传出的thread
retval退出值(传出)
return成功:0
失败:errno
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* thr(void *arg)
{
	printf("I am a thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
	pthread_exit((void*)100);
}

int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	printf("I am a main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
	void *ret;
	pthread_join(tid, &ret);
	printf("ret = %d\n", (int)ret);
	pthread_exit(NULL);
}

运行结果:
在这里插入图片描述

6.1.5 杀死线程pthread_cancel()

帮助手册:man pthread_cancel
包含头文件:

  • #include <pthread.h>

函数原型:

  • int pthread_cancel(pthread_t thread);

函数说明:

线程的取消并不是实时的,而是有一定的延时。需要等待线程到达某个取消点(检查点)。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write…man 7 pthreads可以查看具备这些取消点的系统调用列表。
可以粗略的认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthrestcancel()函数自行设置一个取消点。
被取消的线程,退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1.可以在头文件pthread.h中找到他的定义#define PTHREAD_CANCELED ((void *)-1)

编译和链接时需加上-lpthread

参数说明
thread需要杀死的进程
return成功:0
失败:errorno
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* thr(void *arg)
{
	while(1)
	{
		// 如果把printf和sleep注释,由于没有取消点,线程无法被杀死
		printf("tid = %lu\n", pthread_self());
		sleep(1);
	}
	pthread_exit(NULL);
}

int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	sleep(3);
	pthread_cancel(tid);
	void* ret;
	pthread_join(tid, &ret);
	printf("thread exit with %d\n", (int)ret);
	return 0;
}

运行结果:
运行结果

6.1.6 线程分离pthread_detach()

帮助手册:man pthread_detach
包含头文件:

  • #include <pthread.h>

函数原型:

  • int pthread_detach(pthread_t thread);

函数说明:

  • 线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
  • 进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留的资源仍然存于系统中,导致内核认为该进程存在。
  • 也可使用pthread_create()函数第二个参数(线程属性)来设置线程分离。
  • 一般情况下,线程终止后,其终止状态一直保留到其他线程调用pthread_join()获取他的状态为止。但是线程也可以被设置为detach状态,这样的线程一旦终止就立刻回收他占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。
参数说明
thread需要被分离的线程
return成功:0
失败:errorno

示例:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

int val = 100;

void *thr(void* arg)
{
	printf("I am a thread. tid = %lu, val = %d\n", pthread_self(), val)
	sleep(2);
	val = 1001;
	printf("I am a thread. tid = %lu, val = %d\n", pthread_self(), val);
	pthread_exit(NULL);
}

int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	pthread_detach(tid);
	printf("I am main thread. tid = %lu, val = %d\n", pthread_self(), val);
	val = 1003;
	sleep(5);
	printf("I am main thread. tid = %lu, val = %d\n", pthread_self(), val);
	int ret = 0;
	if((ret = pthread_join(tid, NULL)) > 0)
	{
		printf("join err, ret = %d, %s\n", ret, strerror(ret));
	}
	pthread_exit(NULL);
}

运行结果(线程共享全局变量):
运行结果

6.1.7 比较两个线程ID是否相等pthread_equal()

帮助手册:man pthread_equal
包含头文件:

  • #include <pthread.h>

函数原型:

  • int pthread_equal(pthread_t t1, pthread_t t2);

编译和链接时需加上-lpthread

参数说明
t1第一个线程ID
t2第二个线程ID
return相同:非0
不同:0

示例创建多个线程:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void *thr(void* arg)
{
	int num = (int)arg;
	printf("I am %d thread, self = %lu\n", num, pthread_self());
	return (void*)(100 + num);
}

int main()
{
	pthread_t tid[5];
	int i;
	for(i = 0;i < 5; ++i)
	{
		pthread_create(&tid[i], NULL, thr, (void*)i);
	}
	for(i = 0;i < 5;++i)
	{
		void* ret;
		pthread_join(tid[i], &ret);
		printf("i = %d, ret = %d\n", i, (int)ret);
	}
	return 0;
}

运行结果:
在这里插入图片描述

6.2 线程属性

6.2.1 线程属性结构体:

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

6.2.2 线程的分离状态:

  • 线程的分离状态决定一个线程以什么样的方式来终止自己。
  • 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的资源。
  • 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放资源。应该根据自己的需要,选择适当的分离状态。
  • 线程分离状态的函数:
  • 线程设置属性,分离或分分离
    int pthread_attr_setdetachstate(pthread_attr_t * arrt, int detachstate);
  • 获取线程属性,分离或非分离
    int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数说明
attr已初始化的线程属性
detachstatePTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)

6.2.3 初始化线程的属性pthread_attr_init()

帮助手册:man pthread_attr_init
包含头文件:

  • #include <stdio.h>

函数原型:

  • int pthread_attr_init(pthread_attr_t *attr);

6.2.4 销毁线程的属性pthread_attr_destroy()

帮助手册:man pthread_attr_destroy
包含头文件:

  • #include <stdio.h>

函数原型:

  • int pthread_attr_destroy(pthread_attr_t *attr);

6.2.5 设置属性分离态pthread_attr_setdetachstate()

帮助手册:man pthread_attr_setdetachstate
包含头文件:

  • #include <stdio.h>

函数原型:

  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

6.2.6 获取属性分离态pthread_attr_getdetachstate()

帮助手册:man pthread_attr_getdetachstate
包含头文件:

  • #include <stdio.h>

函数原型:

  • int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

示例:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void *thr(void *arg)
{
	printf("I am a thread\n");
	return NULL;
}

int main()
{
	pthread_t tid;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_create(&tid, &attr, thr, NULL);
	int ret = 0;
	if((ret = pthread_join(tid, NULL)) > 0)
	{
		printf("join err:%d, %s\n", ret, strerror(ret));
	}
	pthread_attr_destroy(&attr);
	return 0;
}

运行结果:
在这里插入图片描述

6.3 线程使用注意事项

  • 主线程退出其他线程退出,主线程因调用pthread_exit()

  • 避免僵尸线程

    • pthread_join()
    • pthread_detach()
    • pthread_create()指定分离属性
      join线程可能在join函数返回前就释放完自己的所有资源,所以不应当返回被回收线程栈中的值
  • mallocmmap申请的内存可以被其他线程释放

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

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

  • 创建线程数:CPU核数*2+2

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT灰猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值