Linux详解 --- 多线程1 (线程理解、线程创建、线程终止、线程等待、线程分离)

线程理解

 线程就是一个执行流。
 线程在进程内部运行 ----- 本质:线程在进程的地址空间内运行
//Linux中的每个执行流(线程)也被叫做“轻量级进程”。

线程与进程

 我们刚才说了,线程是在进程内部运行的,那么进程到底是什么?这里我们需要给进程一个明确的定义了!
 在内核角度:进程是承担分配系统资源的基本实体叫做进程。而线程则是调度的基本单位
//进程包括(task_struct、地址空间、页表)
 而对于线程而言,线程只是进程中的一个task_struct,一个进程中可以有多个线程。对于Linux而言,在Linux中并不存在真正意义的线程,因为Linux中没有为线程创建相应的数据结构,而是复用了进程的结构体。站在用户的角度,想要创建线程的话,可以使用原生线程库去创建线程。
在这里插入图片描述
根据这张图,对于CPU而言,CPU有办法识别task_struct是线程的还是进程的吗?
 不能识别,也不需要识别!CPU只关心一个一个的执行流(task_struct)。

线程的优缺点

· 优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 线程切换的代价要比进程切换小得多
  • 线程占用的资源要比进程少得多
  • 线程能充分利用多处理器的可并行数量
  • 在等待慢速IO操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • IO密集型应用,为了提高性能,将IO操作重叠,线程可以同时等待不同的IO操作

计算密集型:执行流的大部分任务,主要以计算为主:加密解密、排序查找
I/O密集型:执行流的大部分任务,主要以IO为主:刷新磁盘、访问网络、访问数据库

· 缺点

  • 性能损失:指当线程数量比可用的处理器数量多时,会花费额外的开销在线程的同步与调度上。
  • 鲁棒性低:在一个多线程程序当中,当一个线程出现异常崩溃时会导致整个进程都崩溃。
  • 缺乏访问控制
  • 编程难度提高

线程的私有数据与公有数据

线程共享进程的数据,但是线程也有一部分私有的数据:

  1. 线程ID
  2. 寄存器数据(保存了一个线程的上下文信息,以便该现成在被重新切换到时能够恢复当前的数据)
  3. errno
  4. 信号屏蔽字
  5. 调度优先级

线程的公有数据:

  1. 代码段
  2. 数据段
  3. 堆区
  4. 文件描述符表
  5. 每种信号的处理方式(SIG_DFL、SIG_IGN、自定义函数)
  6. 当前工作目录
  7. 用户ID和组ID

线程创建

注意:使用pthread线程库的时候,编译时要携带手动链接pthread库。即带上 -lpthread

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

参数:
 thread: 输出型参数,用来获取线程ID的。 #传入的时候要取地址哦!
 attr: 设置线程的属性信息,attr为NULL代表使用默认属性。
 start_routine: 函数地址。这里使用回调函数的方法,告诉新启动的线程要执行哪个函数。
 arg: 传给线程启动函数的参数,也就是配合start_routine函数的参数使用。
返回值:
 成功返回0;失败返回错误码errno

线程的查看

查看进程内的多个轻量级进程的指令:

	ps -aL

在这里插入图片描述
同一个进程中的多个轻量级进程的PID都是一样的,他们的区别在于LWP。
LWP (Light Weight Process): 轻量级进程ID
再纠正一个概念:
OS调度的时候,是靠LWP,而不是PID!//因为OS调度的基本单位是线程。

线程ID及获取

pthread_t pthread_self(void);

功能:返回当前线程的线程ID。
线程ID的类型是pthread_t,它实际上是unsigned long类型的数据。
问题:线程ID的含义是什么?
 我们将线程ID以 %p 的形式打印出来。
在这里插入图片描述
 线程ID的本质其实是地址!它是指向动态库中的某一块空间的起始地址!这里的"某一块空间"是用来描述线程相关信息的数据块,实际是使用pthread库中的函数创建线程,也就是pthread库帮我们对线程进行管理,而库的本质也是文件,库在运行的时候会被加载到堆栈之间的共享区,因此就有了下图所描绘的样子。
在这里插入图片描述

线程ID与LWP

 我们刚才在ps -aL当中获取到了一个叫做LWP的东西,这个是轻量级进程ID,而我们在创建线程时获取到了线程ID,这两个ID都能够唯一的标识一个线程,那它们之间是什么关系呢?
我们在ps -aL获取到的LWP的值和线程ID是什么关系呢?
 线程ID ≠ LWP
但是它们的关系是 1 :1的关系。
// 线程ID是用户级线程的标识,LWP是内核的轻量级进程ID。

线程终止

终止线程的方法:
 ①:在线程函数中直接使用return。 #对于主线程的return,会直接终止整个进程。
 ②:在线程函数中使用pthread_exit()来终止自己。
 ③:在其他线程中可以调用pthread_cancel来指定终止同一进程中的特定线程。
注意:
 在任意线程中使用exit函数,都会终止整个进程的!!!因此我们一般不在线程中使用exit函数。
· pthread_exit函数

	void pthread_exit(void* retval);

功能:让当前线程终止
参数:
 retval: 存放线程的退出码。


· pthread_cancel函数

	int pthread_cancel(pthread_t thread);

功能:让指定线程ID的线程终止
参数:
 thread: 指定取消的线程ID
返回值:
 成功返回0;失败返回错误码
注意:

  • 一般用于在主线程中去取消其他线程。
  • 不推荐新线程去取消主线程,在不同的版本下可能会有不一样的结果。
  • 也可以用于自己取消自己,但是这与pthread_cancel放置在代码中的位置也有关系。不推荐。

线程等待

问题:为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间当中。 #会造成内存泄漏!
  • 创建新的线程也不会复用刚才退出的线程的地址空间。

#这块跟僵尸进程很类似,但是不存在僵尸线程这一说法!,所以线程是需要被等待的!
· pthread_join函数

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

参数:
 thread: 线程ID
 retval: 它指向一个指针,指针所指向的是所等待线程的返回值。不关心的话可以设置为NULL。

对于retavl的说明

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

返回值:
 成功返回0;失败返回错误码

演示

#include <iostream>    
#include <pthread.h>    
using namespace std;    
    
void* Routine(void* arg)    
{    
  cout << (char*)arg << endl;     
  pthread_exit((void*)1);    
}    
    
int main()    
{    
  pthread_t tid;    
  const char* msg = "A message!";    
  pthread_create(&tid, NULL, Routine, (void*)msg);    
  pthread_join(tid, NULL);                                                                                                                                                                                     
  return 0;                   
}  

在这里插入图片描述

线程分离

线程分离的作用就是让线程自己退出时,自动释放线程资源。 //这样就不需要主线程等它了
· pthread_detach函数

	int pthread_detach(pthread_t thread);

 thread是线程ID。
 可以让线程组内的其他线程对目标线程进行分离,也可以是线程自己分离自己。 #传pthread_self就行
演示

#include <iostream>    
#include <unistd.h>    
#include <pthread.h>    
using namespace std;    
    
void* Routine(void* arg)    
{    
  pthread_detach(pthread_self()); //线程分离后就不用主线程再join子线程了    
  cout << (char*)arg << endl;    
  pthread_exit((void*)1);    
}    
    
int main()    
{    
  pthread_t tid;    
  const char* msg = "A message!";    
  pthread_create(&tid, NULL, Routine, (void*)msg);    
    
  sleep(1);    
  
  //如果线程已经分离了,那么pthread_join函数就会join失败,返回值就不是0而是错误码
  if(pthread_join(tid, NULL) == 0)    
  {    
    cout << "join thread Success!" << endl;    
  }    
  else    
  {    
    cout << "join thread Failed!" << endl;                                                                                                                                                                     
  }    
    
  return 0;    
} 

在这里插入图片描述

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值