- 博客(269)
- 收藏
- 关注
原创 多线程局部存储技术
在两个线程中打印 g_global 的地址和值。但在后续的子线程在函数调用的时候,我们需要将指向 ThreadGlobal 类型的指针作为函数的参数传入来区分每个线程专属的全局变量,这边我们使用了宏定义,优化了这个问题。第 67 行,在创建子线程的时候,我们会 malloc 一个 ThreadGlobal 这个结构体,将这个指针传入每个子线程,这样每个子线程都有唯一的变量。第 10 行,我们通过 __thread 关键字来修饰变量 g_global,使得 g_global 变量在每个线程都有一份拷贝。
2024-05-05 16:58:32
270
原创 多线程与信号量简介
第 53 行,get_shared_memory() 函数调用了 shmget() 函数,shmget() 是一个Linux系统调用函数,用于创建一个新的共享内存段(segment)或获取一个已存在的共享内存段。子线程读完以后,信号量 w 的值会加一,主线程才能去写;由于信号量的初始值为 1,所以只能有一个子线程获取到信号量,其它的线程来获取信号量时,发现信号量的值为 0,就会阻塞等待信号量被释放。该程序中使用的两个信号量,信号量 w 和信号量 r,信号量 w 的初始值为 1,信号量 r 的初始值为 0。
2024-05-04 16:40:04
673
原创 线程同步与条件变量
第 54 行,如果生产者生产了产品后发现上一次的产品没了,则调用 pthread_cond_signal(&g_cond),来唤醒阻塞在 g_cond 这个条件变量上的单个消费者线程来取产品,这样原先阻塞在 g_cond 这个条件变量上的某个线程就会被唤醒,重新抢夺互斥锁,取走产品,然后释放互斥锁。第 24 行,如果消费者发现没有产品,则调用 pthread_cond_wait(&g_cond, &g_mutex) 阻塞在 g_cond 这个条件变量上,并释放已获取到的互斥锁 g_mutex。
2024-05-02 16:39:31
852
原创 多线程读写锁应用
无论是 "读锁" 还是 "写锁",解锁都是调用 pthread_rwlock_unlock()如果两个线程 "同时" 拿到了 "读锁" 和 "写锁",问:哪个线程先进入临界区执行?读写锁在实现上比互斥量复杂,单纯的 "上锁" 和 "解锁" 操作读写锁相对互斥量低效。因此,针对多线程 "读" & "写" 场景需要更具针对性的解决方案。问题:互斥量可用于多线程 "读" & "写" 共享变量的场景吗?保护临界区时,必须清楚当前需要使用 "读锁" 还是 "写锁"读优先:拿到 "读锁" 的线程优先进入临界区。
2024-02-23 17:22:40
442
1
原创 活锁方案与自旋锁
当超时未能获取目标锁,则让出处理器使用权,线程进入阻塞状态。自旋锁与互斥量类似,在任何时刻,最多只能有一个持有者。轻量级锁定,即:临界区相对短小,自旋锁持有时间非常短。即:线程 获取锁 到 释放锁 的时间内只有一个执行流。自适应锁相对普通互斥量效率更高,相对自旋锁安全性更好。线程一旦获取自旋锁,则不能让出处理器使用权。如果只有一个单核处理器,不建议使用自旋锁。自适应锁先以自旋的方式持续尝试获取目标锁。线程获取互斥量失败后究竟发生了什么?如何设置获取互斥量时的等待时间?一种特殊的互斥量,又名:自适应锁。
2024-02-05 17:28:56
445
原创 多线程互斥量进阶
假设有 三个线程 (A,B,C) 和三个互斥量 (R,S,T),三个线程对互斥量的获取及释放如下,问:是否可能发生死锁?一个线程多次尝试获取同一个互斥量,会发生什么?破坏死锁条件中的任意一个!
2024-02-02 10:29:17
368
原创 排队模型应用案例
定义任务结构体 (CurTask) 模拟顾客 (CurTask 变量表示具体客户)方案:将多个线程预先存储在一个 "池子" 内,当需要线程时直接从 "池子" 取出。使用队列数据结构表示等待队列,队列中存储 CurTask 变量 (顾客排队模拟)各个线程从队列中取任务时是互斥操作 (每次只能一个线程操作队列)如何模拟等待中的顾客 (即:如何模拟待执行的任务)?利用线程池模拟工作窗口,每个线程表示一个工作人员。如何模拟工作窗口 (即:如何模拟工作人员)?线程从队列中取出任务执行 (服务客户模拟)
2024-02-01 16:47:33
277
原创 初识多线程互斥量
这种操作一旦开始,就一直执行到结束,中途不会被打断原子操作可以是一个步骤,也可以是多个步骤的集合原子操作的顺序不可以被打乱,也不可以被切割而只执行其中的一部分原子操作在多 任务/线程 并发时能够保证操作结果的正确性。
2024-02-01 15:43:08
266
原创 线程的连接与分离
pthread_join() 等待的线程必然是 pthread_create() 创建的线程。父进程调用 wait() / waitpid(),为子进程 "收尸" 处理并释放暂留资源。pthread_join() 除了等待线程执行结束,还会释放线程所占用的资源。线程进入分离状态,其它线程无法连接 (不可等待 且 无法获取返回值)线程的分离状态指线程不可能执行连接操作 (并非脱离进程不可控)可连接的线程退出后需要执行连接操作,否则线程资源无法释放。分离状态的线程退出后主动释放系统资源 (常规需求)
2024-02-01 13:52:33
424
原创 Linux线程API详解(下)
pthread_cleanup_push() 和 pthread_cleanup_pop() 之间构成内部作用域。pthread_exit() 与 pthread_cancel() 总是会触发清理函数执行。pthread_cleanup_pop() 的参数非零时,触发一个清理函数执行。main() 中执行 return 语句,意味着主线程执行结束,因此进程结束。vfork() 进程只是一个新的执行流 (无任何附带资源的执行流)vfork() 不能和父进程同时执行 (父进程等待子进程结束)
2024-01-31 14:43:57
709
原创 Linux线程API讲解(上)
线程执行流调用 pthread_exit() 函数 (注意:void exit(int status);其它线程对指定线程调用 pthread_cancel() 函数 (不安全)线程入口函数执行了 return 语句,并返回指定值。该函数用于等待指定的线程 (tid) 执行结束。如果指定线程已经执行结束,则函数调用立即返回。如果指定线程必须不可连接,则函数调用失败。参数 retval 用于接收线程 返回值。
2024-01-29 10:37:48
410
原创 深入浅出线程原理
Linux 内核中的基本调度单位为 task_struct,即:内核中以 "任务" 作为调度的基本单位。对于线程来说,pthread_t 类型的标识符 与 pid_t 类型标识有什么不同?每一个线程在内核中都对应一个调度实体,拥有独立的结构体 (task_struct)pid_t pid => 线程标识符 (Thread ID)因此,kill 任意子线程的 pid_t 将导致整个进程结束。进程创建后默认拥有一个线程,即:主线程 (默认执行流)拥有多线程的进程,又被称为线程组 (谁是线程组长?
2024-01-12 14:44:37
912
原创 Linux线程编程初步
线程创建函数:int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);创建 / 销毁 线程花费的时间 < 创建 / 销毁 进程花费的时间。进程:应用程序的一次加载执行 (系统进行资源分配的基本单位)多进程程序共享一段内存 => "机制复杂"进程中的多个线程并行执行,共享进程资源!多线程代码复杂度 > 多进程代码复杂度。线程:进程中的程序执行流。
2024-01-11 19:43:08
416
原创 浅谈进程优先级(下)
换算关系:规范优先级 = MAX_RT_PRIO - 实时优先级 - 1。PRI = 规范优先级 - 40 = 59 - rt_priority。对于实时进程而言,内核模式的优先级与用户模式的优先级并不同。PRI = 规范优先级 - 40 + nice_value。将父进程设置为实时进程,且实时优先级为 99 (调度器)对于实时进程,设置 nice_value 会发生什么?父进程进入循环调度,根据时间片定义改变子进程的调度策略。在优先级不同的时候,先执行优先级高的进程。如何定制实时进程的执行时间?
2024-01-08 17:43:25
483
原创 浅析进程优先级(上)
进程优先级:将处理器资源分配给进程的先后顺序Linux 中每个进程都有相应的优先级 (优先级可能动态改变)进程优先级决定进程 何时执行 和 获得处理器的时间进程优先级通常表现为一个整数值 (数值大小决定优先级高低)
2024-01-08 14:15:14
911
原创 多核调度实验设计
吞吐量存在理论上限值,进程数量多余处理器数量时,吞吐量只能逼近理论上限值。执行进程数量 处理器数量:延迟增加,吞吐量不变。如何验证处理器,进程数量,吞吐量之间的关系?执行进程数量 = 处理器数量:吞吐量达到顶峰。
2024-01-04 16:27:47
363
原创 多核调度预备知识
ps -- 查看进程运行时数据 (ps au)top -- Linux 整体性能监测工具 (类似任务管理器)sar -- Linux 活动情况报告 (系统性能分析工具)
2024-01-03 17:53:43
867
原创 初探Linux进程调度
Linux 系统中可以使用 chrt 命令来查看、设置一个进程的优先级和调度策略命令用法主要参数-p, --pid 操作一个已存在的 PID,不启动一个新的任务-f, -fifo 设置调度策略为 SCHED_FIFO-m, --max 显示最小和最大有效优先级,然后退出-o, --other 设置调度策略为 SCHED_OTHER-r, --rr 设置调度策略为 SCHED_RR。
2024-01-02 10:45:48
338
原创 信号处理设计模式
线程创建函数:int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);先屏蔽所有信号 (无法递达进程),之后为屏蔽信号创建文件描述符;当时机成熟,通过 read() 系统调用读取未决信号 (主动接收信号)由于给每个信号唯一的标记位置,因此,所有信号转变为不可靠信号;其他线程:首先屏蔽所有可能的信号,之后执行任务代码。每个线程拥有独立的信号屏蔽掩码。
2023-12-26 16:51:38
1530
1
原创 信号优先级与安全性
方案:对目标进程发送 N 次 "无" 序信号,验证信号递达进程的先后次序。不要在信号处理函数中调用不可重入函数 (即:使用了全局变量的函数)对于同一个进程,如果存在两个不同的未决实时信号,那么先处理谁?当信号递达,转而执行信号处理函数时,不可重入的函数不能调用。信号的本质是一种软中断 (中断有优先级,信号也有优先级)不要调用函数中存在临界区的函数 (可能产生竞争导致死锁)不要调用标准 I/O 函数,如:printf() 函数。对于不同的未决实时信号,信号值越小优先级越高。目标:验证信号的优先级。
2023-12-25 10:36:32
832
原创 信号可靠性剖析
信号 32 与信号 33 (SIGCANCEL & SIGSETXID) 被NPTL 线程库征用。对于 Linux 内核,信号 32 是最小的可靠信号。不可靠信号的默认处理行为可能不同 (忽略,结束)设置信号队列上限:ulimit -i 1000。信号的可靠性由信号数值决定,与发送方式无关。基于信号发送的进程间通信方式可靠吗?查询信号队列上限:ulimit -i。可靠信号的默认处理行为都是结束进程。信号队列的上限可通过命令设置。不可靠信号 (传统信号)可靠信号 (实时信号)
2023-12-05 19:57:20
1018
原创 信号发送与处理-下
如果希望信号处理之后,被中断的系统调用能够重启,则:可以通过 errno == EINTR 判断重启系统调用。对于执行时间较长的系统调用 (write / read),被信号中断的可能性很大。对于 System V 风格的 signal 函数,会引起信号处理函数的重入。System V 风格的 signal 函数,注册的信号处理是一次性的。系统调用期间,可能收到信号,此时进程必须从系统调用中返回。进程收到信号后,调用由 signal 注册的处理函数。三种注册信号与处理函数的方法有什么区别?
2023-11-08 10:45:36
97
原创 信号发送与处理-上
信号是一种 "软件中断",用来处理异步事件内核发送信号到某个进程,通知进程事件的发送事件可能来自硬件,可能来自用户输入,可能来自除零错误信号是一种类型的进程间通信方式 (一个进程向另一个进程发送信号)A 进程发生事件 T,向 B 进程发送信号,B 进程执行动作响应事件进程可以对接收到的不同信号进行不同响应动作 (信号 => 处理)// 信号处理完毕才返回。
2023-11-07 17:10:43
136
原创 守护进程深度分析
守护进程是系统中执行任务的后台进程不与任何终端相关联 (不接收终端相关的信号)生命周期长,一旦启动,正常情况下不会终止 (直到系统退出)Linux 大多服务器使用守护进程实现 (守护进程名以后缀 d 结尾)
2023-10-22 12:03:14
181
原创 Linux 进程层次分析
/ 设置进程的组标识。// 获取指定进程的组标识。进程组可方便进程管理 (如:同时杀死多个进程,发送一个信号给多个进程)pid_t getpgrp(void);// 获取当前进程的组标识。每个进程都有一个进程组号 (PGID)
2023-09-25 17:27:07
333
原创 Linux 终端与进程
打开 PTY 从设备:slave = open(path_to_slave, O_RDWR);创建 PTY 主从设备:master = posix_openpt(O_RDWR);Linux 中的 终端,控制台,TTY,PTY 究竟是什么?TTY 演变为 Linux 中的抽象概念,对于进程而言 TTY 是一种输入输出设备。终端必然与进程关联才有意义!终端是一台独立于计算机的机器,是能够和计算机进行交互的设备。控制台是一个直接控制设备的面板 (属于设备的一部分)计算机上的输入设备和显示设备从主机独立出来。
2023-09-20 16:35:40
270
原创 类模板深度剖析
可以指定类模板的特定实现部分类型参数必须显示指定根据类型参数分开实现类模板重定义一个类模板和一个新类 (或者两个类模板)使用的时候需要考虑如何选择的问题特化以统一的方式使用类模板和特化类编译器自动优先选择特化类。
2023-09-19 17:16:38
96
原创 类模板的概念和意义
C++ 中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。 用于说明类中使用的泛指类型 T。如:数组类,链表类,Stack 类,Queue 类,等。类模板外部定义的成员函数需要加上模板 声明。声明的泛指类型 T 可以出现在类模板的任意地方。在 C++ 中是否能够将泛型的思想应用于类?类中数据组织的方式和数据元素的具体类型无关。类模板以相同的方式处理不同类型的数据。类模板不能分开实现在不同的文件中。以相同的方式处理不同的类型。
2023-09-19 16:45:09
118
原创 函数模板的概念和意义
Swap 泛型写法中的 T 不是一个具体的数据类型,而是泛指任意的数据类型。template 关键字用于声明开始进行泛型编程。函数模板是泛型编程在 C++ 中的应用方式之一。C++ 中有没有解决方案集合两种方法的优点?typename 关键字用于声明泛指类型。函数模板是 C++ 中重要的代码复用方式。函数模板能够根据实参对参数类型进行推导。C++ 中有几种交换变量的方法?函数模板支持显示的指定参数类型。不考虑具体数据类型的编程方式。
2023-09-19 14:18:32
133
原创 经典问题解析四
构造函数和析构函数不能发生多态行为,只调用当前类中定义的版本!new / delete 会触发构造函数或者析构函数的调用。dynamic_cast 是与继承相关的类型转换关键字。编译器会检查 dynamic_cast 的使用是否正确。dynamic_cast 是与继承相关的专用转换关键字。dynamic_cast 要求相关的类中必须有虚函数。delete 和 free 的区别是什么?new 和 malloc 的区别是什么?malloc 是由 C 库函数提供的函数。delete 能够触发析构函数的调用。
2023-08-30 15:31:54
101
原创 被遗弃的多重继承
需要进行强制类型转换时,C++ 中推荐使用新式类型转换关键字!与多重继承相关的强制类型转换用 dynamic_cast 完成。与多继承相关的强制类型转换用 dynamic_cast 完成。当架构设计中需要继承时,无法确定使用直接继承还是虚继承!当多重继承关系出现闭合时将产生数据冗余的问题!工程开发中采用 "单继承多接口" 的方式使用多继承。C++ 是否允许一个类继承自多个父类?先继承一个父类,然后实现多个接口。多继承中可以出现多个虚函数表指针。C++ 支持多重继承的编码方式。多重继承的本质与单继承相同!
2023-08-30 14:40:40
88
原创 C++中的抽象类和接口
在现实中需要知道具体的图像类型才能求面积,所以对概念上的 "图形" 求面积是没有意义的!Shape 只是一个概念上的类型,没有具体对象!
2023-08-23 16:02:18
125
原创 C++对象模型分析
class 是一种特殊的 struct在内存中 class 依旧可以看作变量的集合class 和 struct 遵循相同的内存对齐原则class 中的成员函数和成员变量是分开存放的。
2023-08-23 15:29:34
77
原创 多态的概念和意义
virtual 关键字是 C++ 中支持多态的唯一行为。通过作用域分辨符 (::) 可以访问到父类中的函数。被 virtual 声明的函数被重写后具有多态概念。同样的调用语句在实际运行时有多种不同的表现形态。根据实际的对象类型决定函数调用的具体目标。被 virtual 声明的函数叫做虚函数。根据实际的对象类型判断如何调用重写函数。根据实际的对象类型确定调用的具体函数。父类中被重写的函数依然会继承给子类。子类中重写的函数将覆盖父类中的函数。在程序运行过程中展现出动态的特性。被重写的虚函数可表现出多态的特性。
2023-08-23 10:14:36
85
原创 同名覆盖引发的问题
在编译这个函数的时候,编译器不可能知道指针 p 究竟指向了什么。于是,编译器认为最安全的做法是调用父类的 print 函数,因为父类和子类肯定都有相同的 print 函数。编译期间,编译器只能根据指针的类型判断所指向的对象。根据赋值兼容,编译器认为父类指针指向的是父类对象。因此,编译结果只可能是调用父类中定义的同名函数。子类对象可以当作父类对象使用 (赋值兼容)子类对象可以当作父类对象使用 (兼容性)父类指针可以正确的指向子类对象。父类对象可以正确的代表子类对象。子类中可以重写父类中的成员函数。
2023-08-23 09:41:10
69
原创 父子间的冲突
通过作用域分辨符 (::) 访问父类中的同名成员。子类中定义的函数是否能重载父类中的同名函数?子类中是否可以定义父类中的同名成员?子类可以定义父类中完全相同的成员函数。子类可以定义父类中完全相同的成员函数。使用作用域分辨符访问父类中的同名成员。子类中的成员将隐藏父类中的同名成员。子类中的成员将隐藏父类中的同名成员。父类中的同名成员依然存在于子类中。子类中的函数将隐藏父类的同名函数。子类无法重载父类中的成员函数。子类可以定义父类中的同名函数。子类可以定义父类的同名成员。类中的成员函数可以进行重载。
2023-08-23 09:18:53
61
原创 继承中的构造与析构
子类对象在创建时需要调用父类构造函数进行初始化。子类对象在销毁时需要调用父类析构函数进行清理。先执行父类的构造函数然后执行成员的构造函数。父类构造函数显示调用需要在初始化列表中进行。子类对象在创建时会首先调用父类的构造函数。父类构造函数和子类构造函数有什么关系?先执行父类构造函数再执行子类的构造函数。父类构造函数可以被隐式调用或者显示调用。2、调用成员变量的构造函数。2、执行成员变量的析构函数。析构顺序与构造顺序对称相反。3、调用类自身的构造函数。1、调用父类的构造函数。3、执行父类的析构函数。
2023-08-22 17:18:06
97
原创 进程创建大盘点
程序崩溃是因为 vfork() 后,子进程和父进程共享同一个进程空间,第 31 行,子进程 return 0 之后,会从创建点返回,破坏栈结构,使得父进程的栈空间被破坏掉,从而导致程序崩溃。我们在 fork() 之后,立即执行 execve() 的话,fork() 复制父进程进程空间的操作是多余的,因为 execve() 会覆盖复制出来的父进程进程空间。vfork() 后,子进程共享父进程的地址空间,父进程会等待子进程运行结束后,再向下运行。子进程可以使用父进程的数据 (堆,栈,全局)
2023-07-31 10:55:01
99
原创 不同的继承方式
是否可以将继承语句中的 public 改为 protected 或者 private?冒号 (:) 表示继承关系,Parent 表示被继承的类,public 的意义是什么?C++ 中的派生语言只支持一种继承方式 (public 继承)一般而言,C++ 工程项目中只使用 public 继承。C++ 的派生语言只支持 public 的继承方式。一般而言,工程中只使用 public 的继承方式。C++ 中默认的继承方式为 private!C++ 中支持 3 种不同的继承方式。private 继承。
2023-07-26 15:49:22
60
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人