Linux线程:线程控制

目录

一、线程的退出与等待

1.1pthread_join线程等待

1.2线程异常

1.3线程如何退出和结束

​编辑 二、线程切换

三、线程的优缺点

3.1优点

3.2缺点

3.3线程vs进程

四、多线程的使用及实操

4.1堆空间共享


一、线程的退出与等待

在Linux中线程具有如下的特点:

1、主线程退出=进程退出=所有线程都要退出。

2、线程也要被"wait",不然会产生和进程类似的问题,造成内存泄露。

3、新线程和主线程的运行先后顺序是不确定的,由调度器来决定。

1.1pthread_join线程等待

int pthread_join(pthread_t thread,void** retval)

 等待一个指定的线程,如果该线程正常退出则返回0,否则返回错误码。如果目标线程没有退出,调用join的线程则会阻塞等待。

而retval实际上是一个输出型参数,用来让调用join的线程拿到目标线程的具体属性和数据。 因为c语言的缘故void类型可以用来接收任意类型,所以我们要想让调用join的线程拿到目标线程的具体信息,目标函数的返回值类型就应当是一个void*类型而retval之所以是void**则是因为我们要手动的创建一个void*的类型来接收和保存目标线程所返回的void*,而要更改void*类型,就需要用到void**。

1.2线程异常

多线程中任何一个线程出现异常都会导致退出则会导致整个进程退出所以多线程的代码往往容易出现健壮性不好的问题。

所以:join不考虑线程异常的情况!因为线程一旦出现异常,整个进程就会跟着退出,join就等待不到目标线程了。

1.3线程如何退出和结束

1、线程函数正常结束,即代码跑完了。

2、不能用exit直接退出,因为exit是用来终止进程的。它会让整个进程直接挂掉。

3、pthread_exit()终止一个线程。

pthread_exit((void*)123);
退出当前线程,退出码123

4、pthread_ cancel()取消一个线程

给指定的线程发送退出信号。

pthread_cancel(tid);//tid为pthread_t类型

而接收到退出信号的线程所返回的退出码就为-1,而-1实际上就是PTHREAD_CANCELED。

 二、线程切换

ls cpu查看CPU中的详细信息。

说到线程切换,其令人首先想要对比的数据就是进程切换,在CPU中存在着大量的寄存器,在进程切换时寄存器中所保存的大量的进程信息以及上下文数据会被加载到正在执行的进程PCB中,然后再将新的进程的上下文数据以及代码等信息加载到CPU中,然后再去执行新的进程,而在Linux操作系统中,线程是由进程进行模拟的在CPU看来线程也是和进程一样的PCB,不过是更加轻量级的进程,而多数人往往存在一个误区,那就是进程间切换和线程间切换所消耗的时间和成本没有区别,因为其二者都是PCB结构,切换时都是进行上下文数据的保存。但着其中存在着一个很大的误区,CPU中除了存在大量的寄存器还存在着一个比寄存器要大的缓存cache,而CPU在执行代码时也并不是傻瓜式的去一行一行拿着虚拟地址通过页表转换到物理内存然后再去执行代码,而是在执行代码时将代码往下的区域一同加载到cache中,这也叫做局部性原理,即使有时会出现跳转到其他代码块的情况,但是从概率上来讲,大概率执行完此行代码会继续往下执行下一行,而同一进程内不同线程之间的代码、数据、地址空间等资源都是共享的,切换线程只需要切换PCB而不需要再像切换进程一样将新进程的代码再次加载进cache中,将cache中的冷数据变成热数据。所以线程切换时相比与进程切换时要付出的成本要小很多。

三、线程的优缺点

3.1优点

1、创建一个新线程的代价要比创建一个新进程小得多。
2、与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
3、线程占用的资源要比进程少很多。
4、能充分利用多处理器的可并行数量。
5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
6、计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
7、I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

3.2缺点

1、性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。
2、健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3、缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4、编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

3.3线程vs进程

进程是资源分配的基本单位。
线程是调度的基本单位。
线程共享进程数据,但也拥有自己的一部分数据:
1、线程ID
2、一组寄存器(即每个线程自己的上下文数据)
3、栈(每个线程都有自己独立的栈结构,因为在运行代码时,临时变量等数据都会来回进行压栈出栈,如果多个线程共用一个栈,就会导致数据混乱)。
4、errno
5、信号屏蔽字
6、调度优先级
所以以下两个部分一定是线程所私有的:
1、线程的硬件上下文(CPU寄存器的值)---线程的调度角度
2、线程的独立栈结构---线程的常规运行
进程的多个线程共享 同一地址空间,因此 Text Segment(代码区)、Data Segment(数据区)都是共享的 ,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
1、 文件描述符表(文件描述符表描述的是文件和进程之间的关系)
2、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
3、当前工作目录
4、用户id和组id
进程和线程的关系如下图 :

 所以:

1、一个线程出问题,导致其他线程出问题,最终导致整个进程退出---线程安全问题。

2、多线程中,公共函数被多个线程同时进入---该函数被重入了。

四、多线程的使用及实操

4.1堆空间共享

如下图代码所示我们边创建线程边让之前创建好的线程去运行:

我们原本的目的是想让1 2 3 4 5这五个线程去不断的打印自己对应的名字,可是运行代码后却出现了如下的问题:

这里就暴露了线程共享数据所导致的问题:

从结果来看貌似只有5号线程在不断的运行其实不然,细看代码可以发现,我们在创建代码进行传参时,传入的是threadname的地址,所以所创建进程拿到的也是threadname的地址,但在main函数中threadname本身是在不断变化的,而创建的新线程和main函数所在的主线程是在同时运行的而每次新线程去调用threadname时都会拿到当前main函数中threadname的值。而跑到最后threadname中就是五号线程的名字,所以导致最后所有线程都在打印五号线程的名字。

所以要想解决这个问题就需要借用堆来解决:

每次给线程创建threadname时都在堆上new一块全新的空间,这样每个线程拿到的都是刚刚才开辟的不同的堆空间。 

  • 48
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

C+五条

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

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

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

打赏作者

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

抵扣说明:

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

余额充值