pthread线程

线程


概念

线程是轻量级进程,一般是一个进程中的多个任务。
进程是系统中最小的资源分配单位

进程 是操作系统中资源分配的最小单位。每个进程都有自己的地址空间,并且拥有独立的资源(如内存、文件句柄等)

进程之间通常是相互独立的,彼此不能直接访问对方的内存空间

线程是系统中最小的执行单位

线程 是操作系统中程序执行的最小单位,也称为“轻量级进程”。一个进程可以包含一个或多个线程,这些线程共享该进程的资源(如内存和文件句柄),但每个线程有自己的栈、寄存器和程序计数器

进程是资源分配的基本单元,而线程是调度和执行的基本单元。线程能够比进程更轻量级地执行任务,因为它们共享进程的资源。

特征

共享资源

线程共享进程的资源,如内存、文件句柄等。多个线程可以同时访问相同的资源,因此它们能够轻松地进行数据交换和通信,而不需要像进程之间那样复杂的IPC(进程间通信)机制。

效率高  

30%(并发度)

上下文切换开销小:线程之间的上下文切换(如切换栈、寄存器)比进程之间的上下文切换要轻量得多,因为线程共享进程的资源。

并发执行:在多核处理器上,多个线程可以真正并发地运行,利用系统资源更加充分。一般来说,通过多线程可以提高系统的并发度,并提升性能。

三方库

pthread  clone   posix

在C语言中,常用的线程库包括 pthreadclone 系统调用和 POSIX 标准库(其中 pthread 是最常用的)
3.1 编写代码头文件: pthread.h
3.2 编译代码加载库: -lpthread   library 

在编译使用 pthread 的程序时,需要链接 pthread

libpthread.so
gcc 1.c -lpthread 

设置gcc的别名
  • 临时别名设置

使用 alias 命令来设置 GCC 的别名

alias gcc='gcc -g -pthread '

这个设置会立即生效,但只在当前的终端会话中有效。如果关闭终端或重启系统,别名设置会消失。

  • 永久别名设置

为了使别名设置在每次终端启动时都有效,将别名添加到用户的 .bashrc 文件中,这样每次登录时,别名都会自动加载:

cd ~ # 切换到家目录

vim .bashrc # 编辑 .bashrc 文件

.bashrc 文件末尾添加以下内容:

alias gcc='gcc -g -pthread '

保存并退出 Vim:

:wq

然后,执行以下命令以重新加载 .bashrc,使别名立即生效:

source ~/.bashrc

这样设置后,每次新打开的终端都会自动应用设置的别名。

线程与进程对比

优点

比多进程节省资源,可以共享变量。


缺点

  • 稳定性

线程和进程相比,稳定性,稍微差些

线程共享进程的地址空间,如果一个线程发生错误(例如访问非法内存),可能会导致整个进程崩溃。这是因为线程之间没有内存隔离,因此相对于多进程模式,线程的稳定性稍差。

线程的调度依赖于操作系统的线程调度器,当系统负载过高时,线程之间的竞争可能导致线程饥饿或优先级反转等问题。

  • 调试难度

线程的调试gdb,相对麻烦些
info thread 查看当前程序中所有线程的状态
可以使用 thread <thread_id> 命令切换到指定的线程。例如,要调试线程 3,可以输入thread 3 

线程与进程区别

资源
  • 共享资源

线程:线程在同一个进程内运行,所有线程共享进程的全局资源,如地址空间、文件描述符、信号处理程序等。这种共享资源带来了一定的性能优势,但也引入了资源竞争的问题。例如,如果多个线程同时访问共享数据而没有正确的同步机制,就可能会导致数据不一致或竞态条件。

进程:进程有自己独立的资源,包括独立的内存空间、文件描述符等。进程之间没有直接的资源共享,如果需要通信,必须通过进程间通信(IPC)机制(如管道、消息队列、共享内存等)来进行。

  • 私有资源

线程:虽然线程共享大部分资源,但每个线程仍然有自己的私有资源,例如栈(用于函数调用、局部变量等)和寄存器(如程序计数器、栈指针等)。通常,每个线程的栈空间较小,默认大小在 8 MB 左右(这可以在系统配置或程序代码中调整)。

进程:进程的所有资源都是私有的,其他进程无法直接访问。每个进程都有自己的地址空间,典型的用户空间为 3 GB(在 32 位系统上),剩余 1 GB 保留给内核空间。

空间和通信
  • 地址空间

线程:线程共享同一个进程的地址空间,这意味着一个线程可以直接访问另一个线程的数据。这种共享空间允许线程之间进行快速通信和数据交换,但也要求开发者小心管理,以避免数据竞争和死锁等问题。

进程:进程有各自独立的地址空间,这意味着一个进程无法直接访问另一个进程的内存数据。这种隔离增强了系统的稳定性和安全性,但也使得进程间通信变得更为复杂,通常需要使用 IPC 机制来交换数据。

  • 通信

线程:由于线程共享同一个进程的地址空间,所以它们可以通过共享内存进行直接通信。没有额外的开销,可以非常高效地共享数据。

进程:进程之间不能直接共享数据,因此必须依赖操作系统提供的 IPC 机制进行通信。这些机制通常包括管道(pipe)、消息队列(message queue)、共享内存(shared memory)、信号(signal)等。虽然这些方法也可以实现数据交换,但相比线程间的直接通信,效率会稍低一些。

线程的设计框架  posix

创建多线程 >>线程空间操作 >>线程资源回收(栈区)
errno   strerror(errno)  perror();


创建多线程

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

该函数可以创建指定的一个线程。

  • 参数

thread 线程id,需要实现定义并由该函数返回。
attr   线程属性,一般是NULL,表示默认属性。
start_routine 指向指针函数的函数指针。
        本质上是一个函数的名称即可。称为th 回调函数,是线程的执行空间。
arg  回调函数的参数,即参数3的指针函数参数。

  • 返回值

成功 0        失败 错误码

注意:一次pthread_create执行只能创建一个线程。
  每个进程至少有一个线程称为主线程。(主线程不需要创建)
  主线程退出则所有创建的子线程都退出。 
  主线程必须有子线程同时运行才算多线程程序。
  线程id是线程的唯一标识,是CPU维护的一组数字。
  pstree 查看系统中多线程的对应关系。
  多个子线程可以执行同一回调函数。

获得当前线程的线程号

pthread_t pthread_self(void); 

返回值的类型unsigned long int; 可用%lu打印

  • 功能

获取当前线程的线程id

  • 参数

  • 返回值

成功 返回当前线程的线程id        失败  -1;

线程的退出

pthread_exit

自行退出>>自杀  >>子线程自己退出
exit(1);

void pthread_exit(void *retval);  exit  return p;
  • 功能

子线程自行退出

  • 参数

retval 线程退出时候的返回状态,临死遗言。

  • 返回值


pthread_cancel

强制退出 >>他杀  >>主线程结束子线程

int pthread_cancel(pthread_t thread);
  • 功能

请求结束一个线程

  • 参数

thread 请求结束一个线程tid

  • 返回值

成功 0        失败 -1

线程的回收


线程的回收机制 

不同与进程没有孤儿线程和僵尸线程
>>主线程结束任意生成的子线程都会结束
>>子线程的结束不会影响主线程的运行


pthread_join

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

 **改变指针的指向

  •   功能

通过该函数可以将指定的线程资源回收,该函数具有阻塞等待功能,如果指定的线程没有结束,回收线程会阻塞。

  •   参数

thread  要回收的子线程tid
retval  要回收的子线程返回值/状态。——ptread_exit(值);

  •   返回值

成功 0        失败 -1;


  子线程的回收策略

1、如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。
2、如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收。
3、如果子线程已知必须长时间运行则,不再回收其资源。


  线程的参数与返回值

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

void *fun(void *arg) {
    int x = *(int *)arg;  // 解引用指针,获取传递的整数值
    printf("Thread received: %d\n", x);
    return NULL;
}

int main() {
    pthread_t tid;
    int x = 10;
    
    pthread_create(&tid, NULL, fun, &x);  // 将 x 的地址传递给线程函数
    pthread_join(tid, NULL);  // 等待线程执行完成
    
    return 0;
}

pthread_create 传递参数

pthread_create(&tid, NULL, fun, &x); 中的 &x 是传递给线程函数 fun 的参数。由于线程函数的参数类型是 void *,所以必须传递指针。

线程函数 fun

void *fun(void *arg) 中的 arg 是一个通用指针,可以指向任何类型的数据

在函数内部,可将 arg 转换为具体的数据类型,这里是 int 类型的指针,然后通过解引用来获取实际的整数值:int x = *(int *)arg;

  • 由于线程函数的参数类型必须是 void *,所以任何参数都需要通过指针传递,甚至是简单的整数。
  • 在线程函数内部,通常需要将 void * 类型的参数转换回实际类型,如将 void *arg 转换为 int *

线程的分离

设置线程分离属性的主要目的是让线程在结束后,系统能够自动回收其资源,而不需要主线程通过 pthread_join 来等待它的结束。对于那些不需要与主线程同步或者不需要获取其返回值的线程,设置分离属性可以简化资源管理。

pthread_detach

int pthread_detach(pthread_t thread);
  • 功能

pthread_detach 函数用于将一个已经创建的线程设置为分离状态。一旦线程被设置为分离状态,当它终止时,其所有资源(如栈、线程控制块等)会自动被系统回收,而不需要调用 pthread_join 来等待它的结束。

  • 参数

thread:线程ID,表示要设置为分离状态的线程。通常传入的是当前线程的ID或其他需要分离的线程的ID。

  • 返回值

0:成功。

非0:失败,返回错误码。

pthread_detach 通常在以下情况下使用:

  • 不需要同步:如果主线程或其他线程不需要等待某个线程的结束(即不需要获取其返回值),可以将该线程设置为分离状态,以便其终止后自动释放资源。
  • 动态设置:可以在线程创建后,动态决定是否将该线程设置为分离状态。

线程的清理

pthread_cleanup_push

void pthread_cleanup_push(void (*routine)(void *), void *arg);
  • 功能

注册一个线程清理函数。当线程被取消或以其他方式退出时,会自动调用这个清理函数,确保资源被正确释放。

  • 参数

routine:清理函数的入口,即一个函数指针。该函数将在清理时被调用。

arg:传递给清理函数的参数。这个参数可以是任何需要在清理时使用的数据或资源句柄。

  • 返回值

无返回值。

pthread_cleanup_pop

void pthread_cleanup_pop(int execute);
  • 功能

调用先前由 pthread_cleanup_push 注册的清理函数。

这个函数通常与 pthread_cleanup_push 成对出现。

  • 参数

execute

如果 execute 非0,则会立即执行清理函数。

如果 execute 为0,则不会执行清理函数。

  • 返回值

无返回值。

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值