做婚恋网站wordpress悬浮登录插件
web/
2025/10/3 11:07:43/
文章来源:
做婚恋网站,wordpress悬浮登录插件,民和县公司网站建设,网站开发公司网站目录
前文
回望页表
一#xff0c;什么是线程
二#xff0c;使用
pthread_create #xff08;线程创建#xff09;
三#xff0c;线程控制
1 #xff0c;线程共享进程数据#xff0c;但也拥有自己的一部分数据:
2#xff0c; 线程 VS 进程优点
3#xff0c;…目录
前文
回望页表
一什么是线程
二使用
pthread_create 线程创建
三线程控制
1 线程共享进程数据但也拥有自己的一部分数据:
2 线程 VS 进程优点
3pthread_join等待线程
4pthread_exit (线程终止)
5 pthread_cancel 线程取消
6. pthread_t 类型
7. pthread_detach (线程分离)
四线程互斥
1. 相关背景概念
2. 互斥量
1初始化互斥量
2互斥量加锁与解锁
3销毁互斥量
理解锁
补充重入 线程安全概念
3. 常见的线程不安全的情况
常见的线程安全的情况
常见不可重入的情况
常见可重入的情况
可重入与线程安全联系
可重入与线程安全区别 嘿收到一张超美的风景图希望你每天都能顺心 前文
结论在磁盘中储存着的程序文件他们其实已经被分成许多份大小为4KB的小空间块被称为页帧同时物理内存中数据以4KB为单位进行储存被称作页框。
当进行IO操作时例如向物理内存中导入数据以4KB形式传递。 回望页表
在曾经我们学习页表时只是简单提了一下今天我们再看页表了解地更详细。 一什么是线程 在一个程序里的一个执行路线就叫做线程 thread 。更准确的定义是线程是 “ 一个进程内部的控制序列 ”一切进程至少都有一个执行线程。 线程在进程内部运行本质是在进程地址空间内运行。 在 Linux 系统中在 CPU 眼中看到的 PCB 都要比传统的进程更加轻量化。 透过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流。 这样子我们终于能理解这句话了线程在进程内部执行同时也是OS调度的基本单位。线程在进程的地址空间中运行CPU不关心执行的是否是进程还是线程只要PCB来执行就行。 值得注意的是Linux只提供轻量级进程通过pthread库来实现多线程功能Windows对多线程会进行数据结构管理维护两者方案不同。
说到这里请让我们来重新理解进程用户视角进程由 内核数据结构 代码和数据同之前的理解差不多只是内核数据结构从之前的一个PCB成了多个PCB。内核视角进程 进程向OS申请空间承担系统资源的基本实体。
CPU视角Linux下PCB 其他系统的PCB。因为linux多线程实现是分配同一个进程的资源当进程只有一个线程才 ”等于“ 我们曾经所写的代码。话说到这里有人会问Linux拥有真正的线程吗 答案没有多线程只是实现的的一种功能。Linux没有对线程组织管理的数据结构是轻量级的进程Linux通过PCB模拟了多线程的功能同时我们也只有轻量级进程接口。
那怎么实现多线程功能呢 用 pthread 线程库——Linux自带的原生线程库 二使用
pthread_create 线程创建 thread: 返回线程 ID attr: 设置线程的属性 attr 为 NULL 表示使用默认属性 start_routine: 是个函数地址线程启动后要执行的函数 arg: 传给线程启动函数的参数 返回值成功返回 0 失败返回错误码 pthread库是用用户层的第三方库不属于C/C库所以我们在编译时需要额外链接pthread库-pthread。
#include iostream
#include unistd.h
#include pthread.husing namespace std;void *func(void* str)
{while (1){cout new pthread play..., pid: getpid() endl; sleep(1);}return nullptr;
}int main()
{pthread_t pt[5];for (int i 0; i 5; i){pthread_create(pt i, nullptr, func, (void* )victor);}while (1){cout main pthread play ..., pid: getpid() endl;sleep(1);}return 0;
}
代码是有了我们如何查看是否有这么多的线程呢走 PS -aL | grep Thread // 就如我们前面一样的来查看线程 这里同样也验证了我们之前的话当一个进程只有一个线程时其线程也可理解为进程。线程标号 PID 三线程控制
1 线程共享进程数据但也拥有自己的一部分数据: 1. 独立的线程ID 2. 一组寄存器需要有寄存器来储存线程上下文 3. 独立的栈 比如说存储产生的临时变量 4. errno 5. 信号屏蔽字 6. 调度优先级 2 线程 VS 进程优点
创建一个新线程的代价要比创建一个新进程小得多。代价小与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多。切换成本低线程占用的资源要比进程少很多。能充分利用多处理器的可并行数量。在等待慢速I/O操作结束的同时程序可执行其他的计算任务。计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现。I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作。
这里解释一下线程较进程切换代价小的原因CPU内部有 L1~L3cache(缓存)每当CPU读取数据时会向内存中读取并利用局部性原理用缓存记录下来那周围一部分数据如果只是切换线程由于线程之间共享代码和一些数据那么就大概率CPU能在内部找到所需数据不需要再次寻址载入缓存。而如果是切换进程需要保存旧进程数据需要重新加载CPU缓存效率自然就慢下来。 3pthread_join等待线程 功能 可以阻塞主线程等待目标线程返回。 thread: 线程 ID value_ptr: 它指向一个指针后者指向线程的返回值 返回值成功返回 0 失败返回错误码 retval : 线程结束返回值。那线程出现异常怎么办答案是不用关心线程是否出现异常因为线程一旦出现崩溃其他线程一同崩溃进程也崩溃。
成功返回0 失败返回错误码。 4pthread_exit (线程终止) 这里为什么不使用 exit()函数呢 原因是exit() 是进程退出而这个是线程退出execl进程替换也不能在线程中随意使用execl一旦第一进程进行替换进程中的代码数据也将被替换线程也无法继续执行。 调用该函数的线程将挂起等待 , 直到 id 为 thread 的线程终止。 thread 线程以不同的方法终止 , 通过 pthread_join 得到的终止状态是不同的总结如下: 1. 如果 thread 线程通过 return 返回 ,value_ ptr 所指向的单元里存放的是 thread 线程函数的返回值。 2. 如果 thread 线程被别的线程调用 pthread_ cancel 异常终掉 ,value_ ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED。 3. 如果 thread 线程是自己调用 pthread_exit 终止的 ,value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。 4. 如果对 thread 线程的终止状态不感兴趣 , 可以传 NULL 给 value_ ptr 参数 5 pthread_cancel 线程取消 thread: 也就是取消线程ID 当一个线程被取消那么线程退出码将被设置为PTHREAD_CANCELED(底层就是返回void*-1)
一般都是用于主线程取消副线程的场景
实践一下
void *func(void* str)
{int n 5;int *data new int[5];while (n--){cout new pthread play..., pid: getpid() endl;data[n] n; sleep(1);}pthread_exit((void*)111);
}int main()
{pthread_t pt;pthread_create(pt, nullptr, func, (void* )victor);sleep(3);pthread_cancel(pt); // 取消线程cout pthread_cancel get endl;sleep(5);int* ret nullptr;pthread_join(pt, (void**)ret); // 等待线程退出cout main pthread play ..., pid: getpid() ret : (long long )ret endl;return 0;
} 6. pthread_t 类型 pthread_t 到底是什么类型呢取决于实现。对于 Linux 目前实现的 NPTL 实现而言 pthread_t 类型的线程 ID 本质就是一个进程地址空间上的一个地址。 所以我们所打印新线程的地址是共享内存位置的地址。
另外在线程中我们也可以获取当前线程的IDpthread_self()。
上图中线程局部存储又是什么 答被__thread 修饰的全局变量
__thread int tmp 0; // __thread的结果让每个线程都有自己被修饰的全局变量这也是线程局部存储7. pthread_detach (线程分离) 默认情况下新创建的线程是 joinable 的线程退出后需要对其进行 pthread_join 操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值主线程一直阻塞join 是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。 可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离 : pthread_detach(pthread_self()) ; joinable和分离是冲突的一个线程不能既是joinable 又是分离的。当新线程进行分离主线程再用join进行等待那么join接口会返回错误码。 void *func(void* str)
{pthread_detach(pthread_self());pthread_exit((void*)111);
}int main()
{pthread_t pt;pthread_create(pt, nullptr, func, (void* )new pthread );sleep(1); // 需要等线程分离后才可进行join等待int* ret nullptr;cout main thread endl;int n pthread_join(pt, (void**)ret); // 等待线程退出cout n : n error : strerror(n) endl; return 0;
} 疑问既然一个新线程已经分离了那如果发生异常是否会影响整个进程呢 答案 会的因为线程依旧是共享着进程资源如果分离的线程出现异常依旧会导致整个进程发生退出崩溃 四线程互斥
1. 相关背景概念 临界资源多线程执行流共享的资源就叫做临界资源。 临界区每个线程内部访问临界资源的代码就叫做临界区。 互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用。 原子性后面讨论如何实现不会被任何调度机制打断的操作该操作只有两态要么完成要么未完成。 通过下面代码我们来理解为什么需要线程互斥
int ticket 1000;
void* getticket(void* str)
{// 打印并进行取票while(ticket 0){usleep(1000);cout getticket : ticket endl;ticket--;}pthread_exit(nullptr);
}int main()
{pthread_t t1, t2, t3,t4;pthread_create(t1, nullptr, getticket, nullptr);pthread_create(t2, nullptr, getticket, nullptr);pthread_create(t3, nullptr, getticket, nullptr);pthread_create(t4, nullptr, getticket, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
} 为什么可能无法获得争取结果 1. while 判断条件为真以后代码可以并发的切换到其他线程。 2. usleep 这个模拟漫长业务的过程在这个漫长的业务过程中可能有很多个线程会进入该代码段。 3. -- ticket 操作本身就不是一个原子操作。 -- 操作并不是原子操作而是对应三条汇编指令 load 将共享变量 ticket 从内存加载到寄存器中 update : 更新寄存器里面的值执行 -1 操作 store 将新值从寄存器写回共享变量 ticket 的内存地址 总之 票出现负数原因我们的getticket函数是可重入函数。由于CPU调度切换原因导致数据出现异步。 大部分情况线程使用的数据都是局部变量比如说栈上的变量变量的地址空间在线程栈空间内这种情况变量归属单个线程其他线程无法获得这种变量。 但有时候很多变量都需要在线程间共享这样的变量称为共享变量可以通过数据的共享完成线程之间的交互。 多个线程并发的操作共享变量会带来一些问题。
不太理解数据异步可以参考下面这个例子 2. 互斥量
为了解决下面的问题 代码必须要有互斥行为当代码进入临界区执行时不允许其他线程进入该临界区。 如果多个线程同时要求执行临界区的代码并且临界区没有线程在执行那么只能允许一个线程进入该临界区。 如果线程不在临界区中执行那么该线程不能阻止其他线程进入临界区。 要做到这三点本质上就是需要一把锁。 Linux 上提供的 这把锁叫互斥量 。 接口
1初始化互斥量
pthread_mutex_t 类型本质上是一个联合体。 两种方法
方法1静态分配全局锁 pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER 全局锁可以不用考虑销毁锁。 方法2动态分配局部锁 int pthread_mutex_init (pthread_mutex_t * restrict mutex const pthread_mutexattr_t * restrict attr) 参数 mutex 要初始化的互斥量 attr NULL 通过函数设置的锁在 生命周期快结束时需要销毁局部锁。 2互斥量加锁与解锁 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值 成功返回 0, 失败返回错误号。 调用 pthread_ lock 时可能会遇到以下情况 情况一互斥量处于未锁状态该函数会将互斥量锁定同时返回成功。 情况二发起函数调用时其他线程已经锁定互斥量或者存在其他线程同时申请互斥量但没有竞争到互斥量那么pthread_ lock 调用会陷入阻塞( 执行流被挂起 ) 等待互斥量解锁。 3销毁互斥量 销毁互斥量需要注意 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁 。不要销毁一个已经加锁的互斥量 已经销毁的互斥量要确保后面不会有线程再尝试加锁。尽量不销毁互斥量互斥量尽量用PTHREAD_MUTEX_INITIALIZER初始化 改善后的抢票系统
pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;
int ticket 1000;void* getticket(void* str)
{// 打印并进行取票while(1){pthread_mutex_lock(mutex); // 加锁if (ticket 0 ){ usleep(1000);ticket--;pthread_mutex_unlock(mutex); // 解锁// 为什么要将cout代码置出临界区// 我们需要注重临界区代码的颗粒度颗粒度越小越好。// 对于多线程访问临界资源我们需要考虑效率问题。将无关紧要的代码优化掉提高临界区的颗粒细密度。cout (char*)str getticket : ticket endl;}else{pthread_mutex_unlock(mutex);break;}pthread_exit(nullptr);
}int main()
{pthread_t t1, t2, t3,t4;pthread_create(t1, nullptr, getticket, (void*)线程一);pthread_create(t2, nullptr, getticket, (void*)线程二);pthread_create(t3, nullptr, getticket, (void*)线程三);pthread_create(t4, nullptr, getticket, (void*)线程四);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
}
错误代码反思分享 我们知道上面是对临界资源进行修改。那我们仅仅是访问临界资源呢那我们是否可以不用跟其他线程进行竞争锁直接访问 回答从语法上来说是允许的。但这是错误的编码思想。即使是访问临界资源也需要进行申请锁。 理解锁
1. 对临界区代码进行加锁使临界区的执行是穿行的。即使是CPU调度持有锁的线程被换下其他线程也执行不了临界区代码。
2. 多个线程线程竞争锁锁本身也是一种共享资源那如何保护锁的安全性 答申请锁与释放锁底层执行操作是原子操作。
3. 锁的底层原理 补充重入 线程安全概念 线程安全多个线程并发同一段代码时不会出现不同的结果。常见对全局变量或者静态变量进行操作并且没有锁保护的情况下会出现该问题。 重入同一个函数被不同的执行流调用当前一个流程还没有执行完就有其他的执行流再次进入我们称之为重入。一个函数在重入的情况下运行结果不会出现任何不同或者任何问题则该函数被称为可重入函数反之则不可重入函数。 3. 常见的线程不安全的情况 不保护共享变量的函数 函数状态随着被调用状态发生变化的函数 返回指向静态变量指针的函数 调用线程不安全函数的函数 常见的线程安全的情况 每个线程对全局变量或者静态变量只有读取的权限而没有写入的权限一般来说这些线程是安全的类或者接口对于线程来说都是原子操作多个线程之间的切换不会导致该接口的执行结果存在二义性。 常见不可重入的情况 调用了 malloc/free 函数因为 malloc 函数是用全局链表来管理堆的 调用了标准 I/O 库函数标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构 可重入函数体内使用了静态的数据结构 常见可重入的情况 不使用全局变量或静态变量 不使用用 malloc 或者 new 开辟出的空间 不调用不可重入函数 不返回静态或全局数据所有数据都有函数的调用者提供 使用本地数据或者通过制作全局数据的本地拷贝来保护全局数据 可重入与线程安全联系 函数是可重入的那就是线程安全的 。概率晦涩我们知道这个即可 函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题。 如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别 可重入函数是线程安全函数的一种。 线程安全不一定是可重入的而可重入函数则一定是线程安全的。 如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入函数若锁还未释放则会产生死锁因此是不可重入的。 下期多线程——下篇 结语 本小节就到这里了感谢小伙伴的浏览如果有什么建议欢迎在评论区评论如果给小伙伴带来一些收获请留下你的小赞你的点赞和关注将会成为博主创作的动力。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/86189.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!