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 1

typedef 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下多线程中的信号处理

(补充)进程组与会话组

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948637.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AcWing练习题:油耗

给定一个汽车行驶的总路程&#xff08;km&#xff09;和消耗的油量&#xff08;l&#xff09;&#xff0c;请你求出汽车每消耗 1 升汽油可行驶多少公里路程。 输入格式 输入共两行&#xff0c;第一行包含整数 X&#xff0c;表示行驶总路程。 第二行包含保留一位小数的浮点数…

前后端规约

文章目录 引言I 【强制】前后端交互的 API请求内容响应体响应码II 【推荐】MVC响应体III【参考】IV 其他引言 服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护…

Redis(二)value 的五种常见数据类型简述

目录 一、string&#xff08;字符串&#xff09; 1、raw 2、int 3、embstr 二、hash&#xff08;哈希表&#xff09; 1、hashtable 2、ziplist 三、list&#xff08;列表&#xff09; ​编辑 1、linkedlist 2、ziplist 3、quicklist&#xff08;redis 3.2后的列表内…

RabbitMQ 客户端 连接、发送、接收处理消息

RabbitMQ 客户端 连接、发送、接收处理消息 一. RabbitMQ 的机制跟 Tcp、Udp、Http 这种还不太一样 RabbitMQ 服务&#xff0c;不是像其他服务器一样&#xff0c;负责逻辑处理&#xff0c;然后转发给客户端 而是所有客户端想要向 RabbitMQ服务发送消息&#xff0c; 第一步&a…

仿生的群体智能算法总结之二(十种)

群体智能算法是一类通过模拟自然界中的群体行为来解决复杂优化问题的方法。以下是10种常见的群体智能算法,接上文https://blog.csdn.net/lzm12278828/article/details/144933367仿生的群体智能算法总结之一(十种)-CSDN博客https://blog.csdn.net/lzm12278828/article/detail…

Jenkins(持续集成与自动化部署)

Jenkins 是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具。 官网&#xff1a;https://www.jenkins.io/ GitLab安装使用 安装前提&#xff1a;内存至少需要4G 官方网站&#xff1a;https://about.gitlab.com/ 安装文档&#xff1a;https://docs.gitlab.c…

Luma AI 简单几步生成视频

简单几步生成视频 登录我们的 AceDataPlatform 网站&#xff0c;按照下图所示即可生成高质量的视频&#xff0c;同时&#xff0c;我们也提供了简单易用的 API 方便集成调用&#xff0c;可以查看 Luma API了解详情 技术介绍 我们使用了 Luma 的技术&#xff0c;实现了上面的图…

Day17补代码随想录 654.最大二叉树|617.合并二叉树|700.二叉搜索树中的搜索|98.验证二叉搜索树

654.最大二叉树 题目 【体会为什么构造二叉树都是前序遍历】 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右…

vue代理问题

vue代理问题 场景:前后端分离项目问题,在前端中请求接口,返回数据这个过程,但是在这个过程中,前端会有两个环境,一个是开发环境,一个是生产环境. 在开发环境中请求接口可能会遇到跨域问题,比如请求的端口是3000,当前端口是8080,这时候就会遇到跨域问题,或者ip不同,也会存在跨…

学英语学压测:02jmeter组件-测试计划和线程组ramp-up参数的作用

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#xff1a;先看关键单词&#xff0c;再看英文&#xff0c;最后看中文总结&#xff0c;再回头看一遍英文原文&#xff0c;效果更佳&#xff01;&#xff01; 关键词 Functional Testing功能测试[ˈfʌŋkʃənəl ˈtɛstɪŋ]Sample样…

phpIPAM容器化部署场景下从1.5.x更新到1.7.0提示禁用安装脚本配置的处理

phpIPAM容器化部署场景下从1.5.x更新到1.7.0&#xff0c;在系统登录页面出现“Please disable installaion scripts....”提示&#xff0c;本文件记录处理过程。 一、问题描述 phpIPAM从1.5.x更新到1.7.0&#xff0c;在系统登录页面出现提示&#xff1a; “Please disable in…

第三届图像处理、计算机视觉与机器学习国际学术会议(ICICML 2024)

目录 重要信息 大会简介 组织单位 大会成员 征稿主题 会议日程 参会方式 重要信息 大会官网&#xff1a;www.icicml.org 大会时间&#xff1a;2024年11月22日-24日 大会地点&#xff1a;中国 深圳 大会简介 第三届图像处理、计算机视觉与机器学…

技术人做Youtuber第一次实战

2025年第一篇&#xff0c;新年好~ 大概2012年还是大三时&#xff0c;不记得从哪里搞到了youtube注册方法&#xff0c;注册了youtube, facebook等被"walled"的网站&#xff0c;当时沉迷海贼王&#xff0c;上传了类似"六分钟看海贼王多热血"的视频&#xff0…

仓颉笔记——windows11安装启用cangjie语言,并使用vscode编写“你好,世界”

2025年1月1日第一篇日记&#xff0c;大家新年好。 去年就大致看了一下&#xff0c;感觉还不错&#xff0c;但一直没上手&#xff0c;这次借着元旦的晚上安装了一下&#xff0c;今年正式开动&#xff0c;公司众多的应用国产化正等着~~ 第一步&#xff1a;准备 官网&#xff1a;…

大模型数据采集和预处理:把所有数据格式,word、excel、ppt、jpg、pdf、表格等转为数据

大模型数据采集和预处理&#xff1a;把所有数据格式&#xff0c;word、excel、ppt、jpg、pdf、表格等转为数据 文本/图片/表格&#xff0c;分别提取处理工具选择不同格式文件&#xff0c;使用不同工具处理1. 确认目标2. 分析过程(目标-手段分析法)3. 实现步骤4. 代码封装效果展…

使用函数求e的近似值(PTA)C语言

自然常数e可以用级数11/1!1/2!⋯1/n!来近似计算。本题要求实现一个计算阶乘的简单函数&#xff0c;使得可以利用该函数&#xff0c;对给定的非负整数n&#xff0c;求该级数的前n1项和。 函数接口定义&#xff1a; double fact( int n ); 其中n是用户传入的参数&#xff0c;函…

9.系统学习-卷积神经网络

9.系统学习-卷积神经网络 简介输入层卷积层感受野池化层全连接层代码实现 简介 卷积神经网络是一种用来处理局部和整体相关性的计算网络结构&#xff0c;被应用在图像识别、自然语言处理甚至是语音识别领域&#xff0c;因为图像数据具有显著的局部与整体关系&#xff0c;其在图…

循环冗余校验CRC的介绍

一、简介 循环冗余校验CRC&#xff08;Cyclic Redundancy Check&#xff09;是数据通信领域中最常用的一种差错校验码。该校验方法中&#xff0c;使用多项式出发&#xff08;模2除法&#xff09;运算后的余数为校验字段。CRC只能实现检错&#xff0c;不能实现纠错&#xff0c;使…

C语言 数组名

1.数组名 数组名是数组首元素的地址。 数组名确实能表示首元素的地址 但是有两个例外&#xff1a; 1.sizeof(数组名&#xff09;,这里的数组名表示整个数组&#xff0c;计算的是整个数组的大小&#xff0c;单位是字节 2.&数组名&#xff0c;这里的数组名表示整个数组&…

办公 三之 Excel 数据限定录入与格式变换

开始-----条件格式------管理规则 IF($A4"永久",1,0) //如果A4包含永久&#xff0c;条件格式如下&#xff1a; OR($D5<60,$E5<60,$F5<60) 求取任意科目不及格数据 AND($D5<60,$E5<60,$F5<60) 若所有科目都不及格 显示为红色 IF($H4<EDATE…