当前位置: 首页 > news >正文

LINUX线程操作

文章目录

    • 线程的定义
    • LINUX中的线程模型
      • 一对一模型
      • 多对一模型
      • 多对多模型
    • 线程实现原理
    • 线程的状态
      • 新建状态(New)
      • 就绪状态(Runnable)
      • 运行状态(Running)
      • 阻塞状态(Blocked)
      • 死亡状态(Dead)
    • 线程的创建及使用
      • 创建线程
      • 线程的属性
        • 相关函数
      • 线程获取自身id
      • 判断两个线程是否相等
        • 判断两个线程是否相等有什么用??(待补充)
      • 线程终止
        • return 终止
        • pthread_exit
        • pthread_cancel
        • pthread_exit 和 return的区别
      • 线程的回收
      • 线程的分离
      • 线程的取消
      • 什么是取消点
      • 取消点的实现原理
      • 相关函数
        • int pthread_cancel(pthread_t thread)
        • int pthread_setcancelstate(int state, int *oldstate)
        • int pthread_setcanceltype(int type, int *oldtype)
        • void pthread_testcancel(void)
        • pthreads标准指定的取消点
      • 线程的资源清理
        • pthread_cleanup_push
        • pthread_cleanup_pop
        • 在pthread_exit时清理
        • pthread_cancel时清理
        • pthread_cleanup_pop的参数为非0值
        • 不显式清理时,线程在什么时候清理资源
    • 线程的同步
      • 互斥锁实现线程同步
        • 互斥锁的类型
          • 普通锁(PTHREAD_MUTEX_NORMAL)
          • 检错锁(PTHREAD_MUTEX_ERRORCHECK)
          • 嵌套锁(PTHREAD_MUTEX_RECURSIVE)
          • 默认锁(PTHREAD_MUTEX_ DEFAULT)
        • 线程互斥锁的使用
      • 自旋锁实现线程同步
        • 自旋锁
        • 自旋锁的使用
      • 条件变量 + 互斥锁 实现同步
      • 信号量实现同步
    • 读写锁
    • 线程的信号处理
    • (补充)进程组与会话组
    • (补充)用户线程如何映射到内核进程

线程的定义

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。

以上是百度给出的线程的定义,我们可以了解到:
1)线程是进程的一个分支,是一个单一的顺序流程
2)一个进程最少拥有一个线程==>主线程即程序本身
3)同进程的多个线程共享进程的的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。
4)每个线程拥有自己的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

LINUX中的线程模型

linux 最开始使用的线程是linuxThreads, 但是linuxThreads不符合POSIX标准, 后来出现了NGPT, 性能更高, 之后又出现了NPTL, 比NGPT更快, 随着时间推移, 就只剩下NPTL了。
线程的模型分为三种:
多对一(M:1)的用户级线程模型
一对一(1:1)的内核级线程模型: 例如linuxThreads和NPTL
多对多(M:N)的两极线程模型: 例如NGPT

查看当前系统使用的进程库

getconf GNU_LIBPTHREAD_VERSION
wangju@wangju-virtual-machine:/usr/include$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.31
wangju@wangju-virtual-machine:/usr/include$ ^C

Ubuntu使用NPTL线程库,NPTL 是 LinuxThreads 的替代者,而且其符合了 POSIX 的标准,在稳定性和性能方面都有了很大的提升。和 LinuxThreads 一样,NPTL 采用了一对一的线程模型。

一对一模型

一个用户线程对应一个内核线程。内核负责每个线程的调度,可以调度到其他处理器上面。
优点:
实现简单。
缺点:
对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换。
内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响。

多对一模型

顾名思义,多对一线程模型中,多个用户线程对应到同一个内核线程上,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。
优点:
用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换。使线程的创建、调度、同步等非常快。
缺点:
由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行。
内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等。

多对多模型

多对一线程模型是非常轻量的,问题在于多个用户线程对应到固定的一个内核线程。多对多线程模型解决了这一问题:m个用户线程对应到n个内核线程上,通常m>n。由IBM主导的NGPT采用了多对多的线程模型,不过现在已废弃。
优点:
兼具多对一模型的轻量
由于对应了多个内核线程,则一个用户线程阻塞时,其他用户线程仍然可以执行
由于对应了多个内核线程,则可以实现较完整的调度、优先级等
缺点:
实现复杂

线程实现原理

首先明确进程与进程的基本概念:
1)进程是资源分配的基本单位
2)线程是CPU调度的基本单位
3)一个进程下可能有多个线程
4)线程共享进程的资源

linux用户态的进程、线程基本满足上述概念,但内核态不区分进程和线程。可以认为,内核中统一执行的是进程,但有些是“普通进程”(对应进程process),有些是“轻量级进程”(对应线程pthread或npthread),都使用task_struct结构体保存保存。使用fork创建进程,使用pthread_create创建线程。两个系统调用最终都都调用了do_dork,而do_dork完成了task_struct结构体的复制,并将新的进程加入内核调度。

普通进程需要深拷贝虚拟内存、文件描述符、信号处理等;而轻量级进程之所以“轻量”,是因为其只需要浅拷贝虚拟内存等大部分信息,多个轻量级进程共享一个进程的资源。

linux加入了线程组的概念,让原有“进程”对应线程,“线程组”对应进程,实现“一个进程下可能有多个线程”。
task_struct中,使用pgid标的进程组,tgid标的线程组,pid标的进程或线程。
一个进程组包含多个进程,一个进程包含一个线程组,一个线程组包含多个线程,所以tgid相同的task_struct属于同一个线程组,即属于同一个进程(pid == tgid)。
在一个线程组内 线程id pid=tgid(线程组号)的是主线程,其余线程地位相等。
因此,调用getpgid返回pgid(主进程号),调用getpid应返回tgid(主线程号=进程号),调用gettid应返回pid(线程号)。

进程下除主线程外的其他线程是CPU调度的基本单位,这很好理解。而所谓主线程与所属进程实际上是同一个task_struct,也能被CPU调度,因此主线程也是CPU调度的基本单位。tgid相同的所有线程组成了概念上的“进程”,只有主线程在创建时会实际分配资源,其他线程通过浅拷贝共享主线程的资源。结合前面介绍的普通线程与轻量级进程,实现“进程是资源分配的基本单位”。

进程是一个逻辑上的概念,用于管理资源,对应task_struct中的资源,每个进程至少有一个线程,用于具体的执行,对应task_struct中的任务调度信息,以task_struct中的pid区分线程,tgid区分进程,pgid区分进程组

线程的状态

新建状态(New)

线程被创建后,但还没有调用start()方法。

就绪状态(Runnable)

调用start()方法后,线程处于就绪状态,等待CPU调度。

运行状态(Running)

线程获取CPU资源执行。

阻塞状态(Blocked)

线程因为某些原因放弃CPU使用权,暂时停止运行。

死亡状态(Dead)

线程执行完毕或被终止后的状态

线程的创建及使用

创建线程

#include <pthread.h>
typedef unsigned long int pthread_t;
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数:
pthread_t * :成功创建时接收线程id
pthread_attr_t :设置线程属性,一般为NULL。
void *(*start_routine) (void *):返回值为void参数为void的函数,线程的执行函数。
void *arg:给线程函数传参。
返回值:
成功:返回0;
失败:返回错误号 errno,并且第一个参数不会被设置

线程的属性

创建线程时通过参数pthread_attr_t 设置线程的属性

union pthread_attr_t
{char __size[__SIZEOF_PTHREAD_ATTR_T];long int __align;
};
#ifndef __have_pthread_attr_t
typedef union pthread_attr_t pthread_attr_t;
# define __have_pthread_attr_t 1typedef struct
{int                       detachstate;   // 线程的分离状态int                       schedpolicy;   // 线程调度策略structsched_param         schedparam;    // 线程的调度参数int                       inheritsched;  // 线程的继承性int                       scope;         // 线程的作用域size_t                    guardsize;     // 线程栈末尾的警戒缓冲区大小int                       stackaddr_set; // 线程的栈设置void*                     stackaddr;     // 线程栈的位置size_t                    stacksize;     // 线程栈的大小
} pthread_attr_t;

Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。在pthread_create中,把第二个参数设置为NULL的话,将采用默认的属性配置。
线程具有属性,用pthread_attr_t表示,在对该结构进行处理之前必须进行初始化,在使用后需要对其去除初始化。
调用pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。
如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。

相关函数
  • 1、pthread_attr_init
    功能: 对线程属性变量的初始化。
  • 2、pthread_attr_setscope
    功能: 设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。默认为PTHREAD_SCOPE_PROCESS。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
  • 3、pthread_attr_setdetachstate
    功能: 设置线程detachstate属性。该表示新线程是否与进程中其他线程脱离同步,如果设置为PTHREAD_CREATE_DETACHED则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
  • 4、pthread_attr_setschedparam
    功能: 设置线程schedparam属性,即调用的优先级。
  • 5、pthread_attr_getschedparam
    功能: 得到线程优先级。
    原文链接:https://blog.csdn.net/houzijushi/article/details/80978345

线程获取自身id

#include <pthread.h>
pthread_t pthread_self(void);

返回值:返回线程号,即对应task_struct 的 pid,并且该函数总是会成功

判断两个线程是否相等

int pthread_equal(pthread_t t1, pthread_t t2);

作用:比较两个线程ID是否相等。
返回值:相等返回非0值,不等返回0值。

判断两个线程是否相等有什么用??(待补充)

线程终止

线程终止有三种方式
1)return
2)pthread_exit
3) pthread_cancel
默认属性的线程执行结束后并不会立即释放占用的资源,直到整个进程执行结束,所有线程的资源以及整个进程占用的资源才会被操作系统回收。实现线程资源及时回收的常用方法有两种,一种是修改线程属性,另一种是在另一个线程中调用 pthread_join() 函数。如果主线程提前结束,会终止所有的同组线程。

return 终止

由上面的分析,我们了解到线程执行的是一个指定函数的内容,所以该函数return之后函数运行结束,调用它的线程也终止。return的值也可以被pthread_join函数接收。

pthread_exit
#include <pthread.h>void pthread_exit(void *retval);

参数:
retval 是 void* 类型的指针,可以指向任何类型的数据,它指向的数据将作为线程退出时的返回值。如果线程不需要返回任何数据,将 retval 参数置为 NULL 即可。

注意,retval 指针不能指向函数内部的局部数据(比如局部变量)。换句话说,pthread_exit() 函数不能返回一个指向局部数据的指针,否则很可能使程序运行结果出错甚至崩溃。

pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);

参数 :thread 指定需要取消的目标线程;成功返回 0,失败将返回错误码。
通过调用 pthread_cancel()库函数向一个指定的线程发送取消请求,要求指定线程终止。可以在一个线程中取消另一个线程。
*发出取消请求之后,函数 pthread_cancel()立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立刻退出,其行为表现为如同调用了参数为 PTHREAD_CANCELED(其实就是(void )-1)的pthread_exit()函数,但是,线程可以设置自己不被取消或者控制如何被取消,所以 pthread_cancel()并不会等待线程终止,仅仅只是提出请求。

pthread_exit 和 return的区别

无论是采用 return 语句还是调用 pthread_exit() 函数,主线程中的 pthread_join() 函数都可以接收到线程的返回值。return 语句和 pthread_exit() 函数的含义不同,return 的含义是返回,它不仅可以用于线程执行的函数,普通函数也可以使用;pthread_exit() 函数的含义是线程退出,它专门用于结束某个线程的执行。
此外,pthread_exit() 可以自动调用线程清理程序(本质是一个由 pthread_cleanup_push() 指定的自定义函数),return 则不具备这个能力。总之在实际场景中,如果想终止某个子线程执行,强烈建议大家使用 pthread_exit() 函数。终止主线程时,return 和 pthread_exit() 函数发挥的功能不同,可以根据需要自行选择。

线程的回收

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

参数:
thread:要等待回收的线程号
retval:接收return或pthread_exit返回的参数
return:成功返回0,失败返回errno号。
当指定的线程已经终止时立即返回,否则阻塞等待。
使用pthread_join时线程必须是未分离的,否则不可用
当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:

  • 被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。
  • 一个线程只能被一个线程所连接(阻塞等待)。
  • 被连接的线程必须是非分离的,否则连接会出错。
    所以可以看出pthread_join()有两种作用:
    1)用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。
    2)对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。
    原文链接:https://blog.csdn.net/yzy1103203312/article/details/80849831

线程的分离

int pthread_detach(pthread_t thread);

将指定线程与指控线程分离,线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止(或者进程终止被回收了)。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。

线程的取消

什么是取消点

1)pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置,即取消点是线程判断是否可被取消的位置,程序产生了取消点,则可以被取消。

2)线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

3)线程的取消点,也称为“中断点”或“cancel point”,是在程序设计中设定的一个特殊位置,其目的是允许外部请求暂停或者停止正在运行的线程。当线程到达这个点时,它会检查是否有被中断的情况发生,如果有,就会执行相应的中断处理逻辑。
作用主要有三个:
①响应中断:线程可能会在一个操作完成后检查是否需要中断,如网络请求、长时间计算等,以便及时响应中断信号,避免资源浪费。
②控制流程:通过设置取消点,程序员可以更好地管理复杂的异步任务,例如,在等待某个条件满足时,如果需要提前结束,可以在取消点上让线程退出。
③异常处理:作为一种优雅的方式,线程的取消点可以帮助捕获并处理非预期的终止请求,比如用户关闭应用程序时,主线程可以设置一个取消点来清理资源或记录日志。

取消点总结:取消点是线程stat=PTHREAD_CANCEL_ENABLE,type=PTHREAD_CANCEL_DEFFERED时,线程内部相应Cancel信号的位置。stat=PTHREAD_CANCEL_ENABLE,type=PTHREAD_CANCEL_ASYCHRONOUS时,立即响应Cancel信号,与取消点无关。

取消点的实现原理

下面我们看 Linux 是如何实现取消点的。(其实这个准确点儿应该说是 GNU 取消点实现,因为 pthread 库是实现在 glibc 中的。) 我们现在在 Linux 下使用的 pthread 库其实被替换成了 NPTL,被包含在 glibc 库中。以 pthread_cond_wait 为例,glibc-2.6/nptl/pthread_cond_wait.c 中:

/* Enable asynchronous cancellation. Required by the standard. */
cbuffer.oldtype = __pthread_enable_asynccancel ();/* Wait until woken by signal or broadcast. */
lll_futex_wait (&cond->__data.__futex, futex_val);/* Disable asynchronous cancellation. */
__pthread_disable_asynccancel (cbuffer.oldtype);

我们可以看到,在线程进入等待之前,pthread_cond_wait 先将线程取消类型设置为异步取消(__pthread_enable_asynccancel),当线程被唤醒时,线程取消类型被修改回延迟取消 __pthread_disable_asynccancel 。

这就意味着,所有在 __pthread_enable_asynccancel 之前接收到的取消请求都会等待 __pthread_enable_asynccancel 执行之后进行处理,所有在 __pthread_disable_asynccancel 之前接收到的请求都会在 __pthread_disable_asynccancel 之前被处理,所以真正的 Cancellation Point 是在这两点之间的一段时间。

原文链接 https://blog.csdn.net/m0_46535940/article/details/124908464

相关函数

int pthread_cancel(pthread_t thread)

发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

int pthread_setcancelstate(int state, int *oldstate)

设置本线程对Cancel信号的反应**(动作),state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态忽略CANCEL信号**继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。

int pthread_setcanceltype(int type, int *oldtype)

设置本线程取消动作的执行时机**(类型),type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出立即执行取消动作(退出)**;oldtype如果不为NULL则存入运来的取消动作类型值。

void pthread_testcancel(void)

是说pthread_testcancel在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.线程取消功能处于启用状态且取消状态设置为延迟状态时(表示 stat=PTHREAD_CANCEL_ENABLE type=PTHREAD_CANCEL_DEFFERED),pthread_testcancel()函数有效。如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号。

pthreads标准指定的取消点

(1)通过pthread_testcancel调用以编程方式建立线程取消点。
(2)线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。
(3)被sigwait(2)阻塞的函数
(4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。

线程的资源清理

主线程可以通道 pthread_cancel 主动终止子线程,但是子线程中可能还有未被释放的资源,比如malloc开辟的空间。如果不清理,很有可能会造成内存泄漏。因此,为了避免这种情况,于是就有了一对线程清理函数 pthread_cleanup_push 和 pthread_cleanup_pop 。两者必须是成对存在的,否则无法编译通过。
pthread_cleanup_push 和 pthread_cleanup_pop都是宏定义

#  define pthread_cleanup_push(routine, arg) \do {                                        \__pthread_cleanup_class __clframe (routine, arg)/* Remove a cleanup handler installed by the matching pthread_cleanup_push.If EXECUTE is non-zero, the handler function is called. */
#  define pthread_cleanup_pop(execute) \__clframe.__setdoit (execute);                        \} while (0)

可以看到只有成对出现时代码才是完整的push { , pop }。

pthread_cleanup_push
void pthread_cleanup_push(void (*routine)(void *), void *arg);

第一个参数 routine:回调清理函数。当上面三种情况的任意一种存在时,回调函数就会被调用
第二个参数 args:要传递个回调函数的参数
pthread_cleanup_push 的作用是创建栈帧,设置回调函数,该过程相当于入栈。回调函数的执行与否有以下三种情况:
线程被取消的时候(pthread_cancel)
线程主动退出的时候(pthread_exit)
pthread_cleanup_pop的参数为非0值(pthread_cleanup_pop)

pthread_cleanup_pop
void pthread_cleanup_pop(int execute);

当 execute = 0 时, 处在栈顶的栈帧会被销毁,pthread_cleanup_push的回调函数不会被执行
当 execute != 0 时,pthread_cleanup_push 的回调函数会被执行。
pthread_cleanup_pop 函数的作用是执行回调函数 或者 销毁栈帧,该过程相当于出栈。根据传入参数的不同执行的结果也会不同。

在pthread_exit时清理

这里 pthread_cleanup_pop 函数的放置位置和参数需要注意:
必须放在 pthread_exit 后面,否则pthread_cleanup_pop会先清除栈帧,pthread_exit就无法调用清理函数了。pthread_cleanup_pop的参数是 0,因为pthread_cleanup_pop的参数为非0值时也会调用回调清理函数

void* pthread_cleanup(void* args){printf("线程清理函数被调用了\n");
}void* pthread_run(void* args)
{pthread_cleanup_push(pthread_cleanup, NULL);pthread_exit((void*)1);     // 子线程主动退出pthread_cleanup_pop(0);     // 这里的参数要为0,否则回调函数会被重复调用
}int main(){pthread_t tid;pthread_create(&tid, NULL, pthread_run, NULL);sleep(1);pthread_join(tid, NULL);return 0;
}
pthread_cancel时清理

这里 pthread_cleanup_pop 函数的放置位置和参数需要注意,放置在取消点后,pop参数为0

void* pthread_cleanup(void* args){printf("线程清理函数被调用了\n");
}void* pthread_run(void* args)
{pthread_cleanup_push(pthread_cleanup, NULL);pthread_testcancel();       // 设置取消点pthread_cleanup_pop(0);     // 这里的参数要为0,否则回调函数会被重复调用
}int main(){pthread_t tid;pthread_create(&tid, NULL, pthread_run, NULL);pthread_cancel(tid);        // 取消线程sleep(1);pthread_join(tid, NULL);return 0;
}
pthread_cleanup_pop的参数为非0值
void* pthread_cleanup(void* args){printf("线程清理函数被调用了\n");
}void* pthread_run(void* args)
{pthread_cleanup_push(pthread_cleanup, NULL);pthread_cleanup_pop(1);     // 这里的参数为非0值
}int main(){pthread_t tid;pthread_create(&tid, NULL, pthread_run, NULL);sleep(1);pthread_join(tid, NULL);return 0;
}
不显式清理时,线程在什么时候清理资源

在C语言中,使用pthread库进行并发处理时,pthread_cleanup_push和pthread_cleanup_pop函数是用来管理清理上下文(cleanup context)的,它们主要用于指定在退出某个作用域时需要执行的清理操作。如果你没有调用pthread_cleanup_pop来弹出并执行之前设置的清理操作,那么这些清理任务通常会在以下几个情况下自动发生:

线程结束:当线程因为正常退出(如调用pthread_exit或其终止信号导致),系统会自动执行清理上下文中列出的任务。

异常退出:如果线程由于未捕获的错误或异常而被迫终止,清理操作将在内核层执行,尽管这可能会因平台而异。

栈溢出:如果线程的堆栈空间不足以完成当前的操作,可能导致栈溢出,这时清理可能不会被执行,取决于系统的行为。

但是,如果没有显式地通过pthread_cleanup_pop来控制清理顺序,不保证所有注册的清理操作一定会按预期执行,尤其是当线程提前中断时。因此,推荐在合适的位置调用pthread_cleanup_pop来管理和控制清理行为。如果不希望在特定条件下执行清理,可以手动清除清理队列,例如使用pthread_cleanup_destroy。

线程的同步

互斥锁实现线程同步

互斥锁的类型

互斥锁本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,当互斥锁由某个线程持有后,这个互斥锁会锁上变成lock状态,此后只有该线程有权力打开该锁,其他想要获得该互斥锁的线程都会阻塞,直到互斥锁被解锁。

普通锁(PTHREAD_MUTEX_NORMAL)

互斥锁默认类型。当一个线程对一个普通锁加锁以后,其余请求该锁的线程将形成一个 等待队列,并在该锁解锁后按照优先级获得它,这种锁类型保证了资源分配的公平性。一个 线程如果对一个已经加锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普 通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果。

检错锁(PTHREAD_MUTEX_ERRORCHECK)

一个线程如果对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK;对一个已 经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁,则解锁操作返回 EPERM。

嵌套锁(PTHREAD_MUTEX_RECURSIVE)

该锁允许一个线程在释放锁之前多次对它加锁而不发生死锁;其他线程要获得这个锁,则当前锁的拥有者必须执行多次解锁操作;对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM。

默认锁(PTHREAD_MUTEX_ DEFAULT)

一个线程如果对一个已经加锁的默认锁再次加锁,或者虽一个已经被其他线程加锁的默 认锁解锁,或者对一个解锁的默认锁解锁,将导致不可预期的后果;这种锁实现的时候可能 被映射成上述三种锁之一。

线程互斥锁的使用

// 静态方式创建互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 动态方式创建互斥锁,其中参数mutexattr用于指定互斥锁的类型,具体类型见上面四种,如果为NULL,就是普通锁。
int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁,阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁,非阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁

自旋锁实现线程同步

自旋锁

自旋锁顾名思义就是一个死循环,不停的轮询,当一个线程未获得自旋锁时,不会像互斥锁一样进入阻塞休眠状态,而是不停的轮询获取锁,如果自旋锁能够很快被释放,那么性能就会很高,如果自旋锁长时间不能够被释放,甚至里面还有大量的IO阻塞,就会导致其他获取锁的线程一直空轮询,导致CPU使用率达到100%,特别CPU时间。

自旋锁的使用
int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 创建自旋锁int pthread_spin_lock(pthread_spinlock_t *lock); // 加锁,阻塞
int pthread_spin_trylock(pthread_spinlock_t *lock); // 尝试加锁,非阻塞
int pthread_spin_unlock(pthread_spinlock_t *lock); // 解锁

条件变量 + 互斥锁 实现同步

Linux 条件变量:实现线程同步(什么是条件变量、为什么需要条件变量,怎么使用条件变量(接口)、例子,代码演示(生产者消费者模型))

信号量实现同步

使用系统的信号量实现同步。
linux线程同步方式3——信号量(semaphore)

读写锁

linux线程同步方式5——读写锁(rwlock)

线程的信号处理

Linux下多线程中的信号处理

(补充)进程组与会话组

(补充)用户线程如何映射到内核进程

相关文章:

LINUX线程操作

文章目录 线程的定义LINUX中的线程模型一对一模型多对一模型多对多模型 线程实现原理线程的状态新建状态&#xff08;New&#xff09;就绪状态&#xff08;Runnable&#xff09;运行状态&#xff08;Running&#xff09;阻塞状态&#xff08;Blocked&#xff09;死亡状态&#…...

在Lua中,Metatable元表如何操作?

Lua中的Metatable&#xff08;元表&#xff09;是一个强大的特性&#xff0c;它允许我们改变表&#xff08;table&#xff09;的行为。下面是对Lua中的Metatable元表的详细介绍&#xff0c;包括语法规则和示例。 1.Metatable介绍 Metatable是一个普通的Lua表&#xff0c;它用于…...

4D LUT: Learnable Context-Aware 4D LookupTable for Image Enhancement

摘要&#xff1a;图像增强旨在通过修饰色彩和色调来提高照片的审美视觉质量&#xff0c;是专业数码摄影的必备技术。 近年来&#xff0c;基于深度学习的图像增强算法取得了可喜的性能并越来越受欢迎。 然而&#xff0c;典型的努力尝试为所有像素的颜色转换构建统一的增强器。 它…...

瑞芯微rk3568平台 openwrt系统适配ffmpeg硬件解码(rkmpp)

瑞芯微rk3568平台 openwrt系统适配ffmpeg硬件解码(rkmpp) RK3568及rkmpp介绍编译安装mpp获取源码交叉编译安装 libdrmlibdrm-2.4.89 make 方式编译(cannot find -lcairo, 不推荐)下载源码编译编译错误: multiple definition of `nouveau debug‘错误cannot find -lcairo:…...

使用SuperMap制作地形图的详细教程

一、数据准备 本示例以山东为例&#xff0c;演示如何通过SuperMap iDesktopX制作一个好看的地形图。所有数据均来源于互联网公开数据&#xff0c;如有自己项目真实数据&#xff0c;可直接跳过数据下载进入下一步。 本示例所需数据包括&#xff1a; 数据类别 数据类型 DEM数据…...

PHP Array:精通数组操作

PHP Array&#xff1a;精通数组操作 PHP&#xff0c;作为一门流行的服务器端编程语言&#xff0c;提供了强大的数组处理能力。数组是PHP中非常灵活和强大的数据结构&#xff0c;它可以存储多个相同类型的值。在PHP中&#xff0c;数组不仅可以存储数字&#xff0c;还可以存储字…...

【使用命令配置java环境变量永久生效与脚本切换jdk版本】

java配置环境变量命令与脚本切换jdk版本 新建用户环境变量永久生效 setx JAVA8_HOME "D:\Java\jdk8" setx JAVA17_HOME "d:\Java\jdk-17" setx JAVA_HOME %JAVA8_HOME% setx CLASSPATH ".;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;"…...

STM32-笔记32-ESP8266作为服务端

esp8266作为服务器的时候&#xff0c;这时候网络助手以客户端的模式连接到esp8266&#xff0c;其中IP地址写的是esp8266作为服务器时的IP地址&#xff0c;可以使用ATCIFSR查询esp8266的ip地址&#xff0c;端口号默认写333。 当esp8266作为服务器的时候&#xff0c;需要完成哪些…...

RAG(Retrieval-Augmented Generation,检索增强生成)流程

目录 一、知识文档的准备二、OCR转换三、分词处理四、创建向量数据库五、初始化语言聊天模型1.prompt2.检索链3.对话 完整代码 知识文档的准备&#xff1a;首先需要准备知识文档&#xff0c;这些文档可以是多种格式&#xff0c;如Word、TXT、PDF等。使用文档加载器或多模态模型…...

【Python学习(六)——While、for、循环控制、指数爆炸】

Python学习&#xff08;六&#xff09;——While、for、循环控制、指数爆炸 本文介绍了While、for、循环控制、指数爆炸&#xff0c;仅作为本人学习时记录&#xff0c;感兴趣的初学者可以一起看看&#xff0c;欢迎评论区讨论&#xff0c;一起加油鸭~~~ 心中默念&#xff1a;Py…...

解释一下:运放的输入失调电流

输入失调电流 首先看基础部分:这就是同相比例放大器 按照理论计算,输入VIN=0时,输出VOUT应为0,对吧 仿真与理论差距较大,有200多毫伏的偏差,这就是输入偏置电流IBIAS引起的,接着看它的定义 同向和反向输入电流的平均值,也就是Ib1、Ib2求平均,即(Ib1+Ib2)/2 按照下面…...

力扣hot100——二分查找

35. 搜索插入位置 class Solution { public:int searchInsert(vector<int>& a, int x) {if (a[0] > x) return 0;int l 0, r a.size() - 1;while (l < r) {int mid (l r 1) / 2;if (a[mid] < x) l mid;else r mid - 1;}if (a[l] x) return l;else …...

PHP 使用集合 处理复杂数据 提升开发效率

在 PHP 中&#xff0c;集合&#xff08;Collections&#xff09;通常是通过数组或专门的集合类来实现的。 集合&#xff08;Collection&#xff09;是一种高级的数据结构&#xff0c;可以提供比普通数组更强大的操作和功能&#xff0c;特别是当你需要更复杂的数据处理时。 La…...

Unity 对Sprite或者UI使用模板测试扣洞

新建两个材质球&#xff1a; 选择如下材质 设置如下参数&#xff1a; 扣洞图片或者扣洞UI的材质球 Sprite或者UI的材质球 新建一个单独Hole的canvas&#xff0c;将SortOrder设置为0&#xff0c;并将原UI的canvans的SortOrder设置为1 对2DSprite则需要调整下方的参数 hole的O…...

unity学习3:如何从github下载开源的unity项目

目录 1 网上别人提供的一些github的unity项目 2 如何下载github上的开源项目呢&#xff1f; 2.1.0 下载工具 2.1.1 下载方法1 2.1.2 下载方法2&#xff08;适合内部项目&#xff09; 2.1.3 第1个项目 和第4项目 的比较 第1个项目 第2个项目 第3个项目 2.1.4 下载方法…...

PHP后执行php.exe -v命令报错并给出解决方案

文章目录 一、执行php.exe -v命令报错解决方案 一、执行php.exe -v命令报错 -PHP Warning: ‘C:\windows\SYSTEM32\VCRUNTIME140.dll’ 14.38 is not compatible with this PHP build linked with 14.41 in Unknown on line 0 解决方案 当使用PHP8.4.1时遇到VCRUNTIME140.dll…...

CDP集群安全指南-动态数据加密

[〇]关于本文 集群的动态数据加密主要指的是加密通过网络协议传输的数据&#xff0c;防止数据在传输的过程中被窃取。由于大数据涉及的主机及服务众多。你需要更具集群的实际环境来评估需要为哪些环节实施动态加密。 这里介绍一种通过Cloudera Manager 的Auto-TLS功能来为整个…...

【shell编程】报错信息:Undefined Variable(包含6种解决方法)

大家好&#xff0c;我是摇光~ 当Shell脚本报错“Undefined Variable”时&#xff0c;是未定义变量的意思。 以下是对每个可能原因及其对应详细解决方案的详细解释&#xff1a; 原因1&#xff1a;拼写错误 原因&#xff1a; 脚本中变量名的拼写在使用和定义时不一致。例如&…...

Dubbo扩展点加载机制

加载机制中已经存在的一些关键注解&#xff0c;如SPI、©Adaptive> ©Activateo然后介绍整个加载机制中最核心的ExtensionLoader的工作流程及实现原理。最后介绍扩展中使用的类动态编译的实 现原理。 Java SPI Java 5 中的服务提供商https://docs.oracle.com/jav…...

unity学习7:unity的3D项目的基本操作: 坐标系

目录 学习参考 1 unity的坐标系 1.1 左手坐标系 1.2 左手坐标系和右手坐标系的区别 1.3 坐标系的原点(0,0,0) 2 坐标系下的具体xyz坐标 2.1 position这里的具体xyz坐标值 2.2 父坐标 2.3 世界坐标和相对坐标 2.3.1 世界坐标 2.3.2 相对坐标 2.4 父物体&#xff0c;…...

PyTorch框架——基于深度学习EfficientDeRain神经网络AI去雨滴图像增强系统

第一步&#xff1a;EfficientDeRain介绍 EfficientDeRain 是一个针对单张图像去雨的开源项目&#xff0c;该项目由清华大学的研究团队提出&#xff0c;主要用于处理图像中的雨水干扰&#xff0c;恢复图像的真实场景 核心功能 图像去雨&#xff1a;EfficientDeRain 通过学习像素…...

写一个类模板三个模板参数K,V,M,参数是函数(函数参数、lambda传参、函数指针)

cal是类的成员函数。cal的3个入参是func1(K&#xff09;&#xff0c;func2&#xff08;K&#xff0c;V&#xff09;&#xff0c;func3(K&#xff0c;V&#xff0c;M)&#xff0c;请写出cal&#xff0c;并在main函数中调用cal 在您给出的要求中&#xff0c;cal成员函数并不直接…...

国内Ubuntu环境Docker部署Stable Diffusion入坑记录

国内Ubuntu环境Docker部署Stable Diffusion入坑记录 本文旨在记录使用dockerpython进行部署 stable-diffusion-webui 项目时遇到的一些问题&#xff0c;以及解决方案&#xff0c;原项目地址: https://github.com/AUTOMATIC1111/stable-diffusion-webui 问题一览&#xff1a; …...

现代光学基础6

总结自老师的ppt yt6 半导体激光器开卷考试学习资料 目录 半导体激光器边发射半导体激光器垂直腔面发射激光器&#xff08;VCSEL&#xff09;激光产生条件&#xff08;激光原理&#xff09;半导体激光器的水容器模型有源半导体区域类型和载流子注入发光二极管&#xff08;L…...

解决HBuilderX报错:未安装内置终端插件,是否下载?或使用外部命令行打开。

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 错误描述 在HBuilderX中执行npm run build总是提醒下载插件&#xff1b;图示如下&#xff1a; 但是&#xff0c;下载总是失败。运行项目时候依然弹出上述提醒。 解决方案 …...

基于Java的超级玛丽游戏的设计与实现【源码+文档+部署讲解】

目 录 1、绪论 1.1背景以及现状 1.2 Java语言的特点 1.3 系统运行环境及开发软件&#xff1a; 1.4 可行性的分析 1.4.1 技术可行性 1.4.2 经济可行性 1.4.3 操作可行性 2、 需求分析 2.1 用户需求分析 2.2功能需求分析 2.3界面设计需求分析…...

Spring Boot项目中使用单一动态SQL方法可能带来的问题

1. 查询计划缓存的影响 深入分析 数据库系统通常会对常量SQL语句进行编译并缓存其执行计划以提高性能。对于动态生成的SQL语句&#xff0c;由于每次构建的SQL字符串可能不同&#xff0c;这会导致查询计划无法被有效利用&#xff0c;从而需要重新解析、优化和编译&#xff0c;…...

conan从sourceforge.net下载软件失败

从sourceforge.net下载软件&#xff0c;经常会没有开始下载就返回了。 原因是&#xff1a; 自动选择的镜像站不能打开。 在浏览器中&#xff0c;我们可以手动选择站点尝试&#xff0c;但是conan就不行了。 手动选择一个站点&#xff0c;能够有文件保存窗口弹出&#xff0c;之后…...

通过爬虫方式实现视频号助手发布视频

1、将真实的cookie贴到解压后目录中cookie.txt文件里,修改python代码里的user_agent和video_path, cover_path等变量的值,最后运行python脚本即可; 2、运行之前根据import提示安装一些常见依赖,比如requests等; 3、2025年1月份最新版; 代码如下: import json import…...

springboot525基于MVC框架自习室管理和预约系统设计与实现(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装自习室管理和预约系统软件来发挥其高效地信息处理的作用&am…...