c++ 获取线程id_多线程探究

多线程的探究

1.基础概念

  • 它是cpu的最小调度实体
    • 进程
    • 线程组
    • 进程组
    • 作业
  • 由线程ID、一组寄存器、栈起始地址、函数入口等构成

2.线程控制 API

linux API

/*

de582eb6ec8fee9ca12857675945d09f.png
/*
  • 线程有三种终止方式:
    • 线程可以从启动例程中返回,返回值是线程的退出码;
    • 线程可以被同一进程的其它线程取消;
    • 线程调用pthread_exit;
/*
  • 还可以为线程设置退出回调函数,就像atexit给进程设置退出函数一样;
#include 
  • 清理函数执行的条件
    • 调用pthread_exit时;
    • 响应取消请求时;
    • 用非零execute参数调用pthread_cleanup_pop时;
  • 进程与线程有相似之处

进程原语线程原语描述forkpthread_create创建新的控制流exitpthread_exit从现有的控制流退出waitpidpthread_join从控制流中得到退出状态atexitpthread_cancle_push注册在退出控制流时调用的函数getpidpthread_self获取控制流的IDabortpthread_cancel请求控制流的非正常退出

  • 关于对pthread_detach的原理:https://blog.csdn.net/qq_33883085/article/details/89425933
/*

windows API

  • 《windows核心编程》与《unix高级环境编程 》的代码风格不一样
    • windows函数名都统一驼峰命名,且首个单词首字母大写;而unix是匈牙利命名风格
    • 变量名都把基础类型信息给带上了------个人认为没必要,尤其是成员变量在携带基础类型很冗余;
/*
  • 终止线程的运行
    • 线程函数返回(最好使用这种方式)
    • ExitThread函数自行撤销
    • 进程调用TerminateThread函数
    • 杀死进程
  • 线程终止时的操作
    • 撤销窗口,卸载挂钩
    • 线程的退出码从STILL_ACTIVE改为传递给它的退出码
    • 线程内核对象的状态已经通知
    • 最好一个线程退出进程会终止
    • 线程内核对象的引用计数-1
//功能介绍:自行撤销线程;最好使用_endthreadex

1fcc5bb923727ab36b150b3cc18168aa.png
  • 以上线程接口来自于kernel32.dll

e34ba70e5cc9e421e85a687e85b132e2.png
  • 书中告诫:绝对不要使用线程控制的系统API,应该使用C/C++运行库的函数,如下:
unsigned 
  • windows 标准库的命名更高与unix有点像了,说明微软内部有点混乱
void 
  • 不应该调用C/C++标准库的beginthread/endthread
    • windows API真的很混乱,珍爱生命原理windows开发
//获取自身线程或者进程ID
  • 共享资源的线程最好在同一个CPU核心上,因为NUMA内存模型决定CPU访问同一个节点的内存效率更高
  • 从下面的函数可以看出,在windows内核中进程与线程是不同的;linux内核中线程与进程无区别
/*

C11 API C++官网

  • 编写跨平台程序最好使用C++标准库的线程
  • 它是一个对象,而linux和windows的线程api都是c接口
  • 有点:使用起来简单快捷
  • 缺点:堆栈大小、默认属性等无法修改

class thread;

82ad10ff31a9c689e48889d0c3e549ff.png

example

// thread example

java 多线程 API

实现Runnable接口

pulic 

线程类Thread

public 

线程池工厂Excutor

  • CachedThreadPool将会为每个任务创建一条线程
  • FixedThreadPool使用有限的线程集合来执行所有任务
  • SingleThreadPool使用一个线程来服务所有的任务,内部维护了一个任务队列,一个任务执行完后才能处理其他任务
import 
  • 休眠:Thread.sleep()
  • 放弃cpu:Thread.yield()

3.线程同步

3.1 linux API

  • 使用场景
    • 多线程共同读写资源时;
  • 同步与不同步的效果如下图:

e17db2c3779af5573e0dc62edc35f85b.png

互斥量

  • 可以使用malloc分配pthread_mutex_t结构体内存,释放之前必须pthread_mutext_destroy清理其它资源;
  • 可以用PTHREAD_MUTEX_INITIALIZER静态分配互斥量;
  • 不管用何种方式分配互斥量,都需要使用下面的函数进行初始化之后才能使用
  • 把参数attr设置为NULL就能使用默认的参数初始化;
/*
  • 解决死锁问题的方法
    • 等待超时机制
    • 等待图
/*

读写锁

  • 与互斥量相比,读写锁有更高的并发性;
    • 互斥量只有两种状态:加锁或者不加锁;
    • 读写锁有三种状态:读、写、不加锁
  • 与互斥量类似,使用之前必须初始化,释放前必须先销毁相关资源
/*

条件变量

  • 它必须与互斥量一起使用
  • pthread_cond_wait先锁定互斥量,然后把传递给函数,函数把线程放在等待列表上,释放互斥量
    • 这样条件变量就不会错过任何变化
    • 当有信号来时,从等待列表上唤醒一个线程
  • 一般用在发布订阅模式中
#include 

自旋锁

  • 自旋锁不会是CPU进入休眠,是一种忙等;
  • 一般作为锁原语实现其他类型的锁;
  • 互斥量自旋次数超过一定限制后就会进入内核休眠
  • 所持有时间极短的场景
#include 

屏障

  • 适用于多线程算法:分治法
  • pthread_join也是一种屏障
#include 

3.2 windows API

3.2.1 用户层同步

  • 具有速度快的优点,同时具备浪费CPU的风险

互锁函数

  • 互锁函数的原理
    • 打开CPU中的一个特殊标志位,并注明被访问的内存
    • 将内存的值读入寄存器
    • 修改该寄存器的值
    • 如果CPU的特殊位是关闭的则转入第二步,否则特殊标志位任然打开,并将寄存器的值写回内存
      • 如果系统中的 另一个C P U试图修改同一个内存地址,那么它就能够关闭 C P U的特殊位标志,从而导致互锁函 数返回第二步
  • 互锁函数都是忙等,等待时间过长会浪费很多CPU资源
LONG 

高速缓存行工作原理

  • CPU1读取一个字节,使该字节和它的相邻字节被读入C P U 1的高速缓存行
  • CPU2读取同一个字节,使得第一步中的相同的各个字节读入C P U 2的高速缓存行。
  • CPU1修改内存中的该字节,使得该字节被写入C P U 1的高速缓存行。但是该信息尚未写 入R A M。
  • CPU2再次读取同一个字节。由于该字节已经放入C P U 2的高速缓存行,因此它不必访问 内存。但是C P U 2将看不到内存中该字节的新值。
    • 当一个C P U修改高速缓存行中的字节时,计算机中的其他 C P U会被 告知这个情况,它们的高速缓存行将变为无效。因此,在上面的情况下, C P U 2的高速缓存在 C P U 1修改字节的值时变为无效。在第 4步中,C P U 1必须将它的高速缓存内容迅速转入内存, C P U 2必须再次访问内存,重新将数据填入它的高速缓存行。
  • 从高速缓存行推出的编程技巧
    • 数据结构对齐方式应该与缓存行边界对齐,保证集中读写的数据都在同一个缓存行
    • 只读数据与读写数据分开

临界区

  • 使用场景:保护一段关键代码
  • 工作原理
    • 在应用层进行等待循环计数
    • 如果在计数满后还没得到锁才进入内核休眠状态
//初始化关键段

3.2.2 内核层同步-----内核同步对象

  • 内核对象
    • 进程、线程、作业、控制台输入、信标、互斥对象
    • 文件修改通知、事件、可等待定时器、文件

a3f26272b39f668dcda11049a904e2cc.png

等待事件

//功能:等待当个内核对象,第二参数为INFINITE时为一直等

事件内核对象

//创建内核对象

等待定时器内核对象

//创建定时器

信号量

//创建信号量

互斥量

HANDLE 

815c6152896832454a646f7d523bf11b.png

3.3 C11 API

  • 支持的锁类型
    • 原子操作
    • 条件变量
    • 互斥量
  • 模板函数
    • call_once
    • lock
    • try_lock

mutex

b14ea185713d7e08ccffc251c2f5a3ce.png

example

// mutex example
  • lock_guard
    • 类似mutex的只能指针
// lock_guard example

3.4 java API

  • 使用sychronized关键字
    • 优点:代码简单
    • 缺点:锁粒度大,性能低
  • 显示使用Lock对象
    • 优点:可以最小化锁粒度,提高性能;更加灵活
    • 缺点:需要手动锁定和解锁
  • 显示使用Condition
    • await()挂起线程
    • signal()激活挂起的线程
  • 原子变量
  • 临界区
  • 在其他对象上同步

使用关键字sychronized

sychronized 

显示使用Lock对象

import 

临界区

sychronized

4.线程控制

4.1 线程属性

af703a8a7eb7e573a6254895a613441e.png

0a1f22f682bd4aa85fc68ddd23d16874.png
#include 
  • 线程太多需要减小线程栈大小,默认是每个线程8M
  • 虚拟地址空间不够了,可以使用malloc或者mmap映射堆空间为栈;(默认在最顶部固定范围)
  • 某些处理器结构可能返回的地址不是开始位置,而是结尾位置(向上增长)
#include 
  • 为了避免栈溢出扩展内存的空间浪费,可以取消或者减小它,默认是1page
#include 

4.2同步属性

互斥量属性

  • 值得注意的三个属性
    • 进程共享
    • 健壮性
    • 类型属性
#include 
  • 如果把互斥量属性设置为进程共享,接口就会把同一个互斥量内存映射到不同进程进行进程共享
#include 
  • 多进程共享互斥量的时候可能另一个进程崩溃,而没有释放锁;这时候可以通过设置健壮性解决
    • PTHEAD_MUTEX_STALLED:持有锁进程终止时,等待进程会被拖住(死锁),行为未定;
    • PTHREAD_MUTEX_ROBUST:持有锁进程终止不释放时,等待锁的进程会获得锁,返回EOWNERDEAD而不是0
#include 
  • 持有锁进程终止,或者其它线程解锁互斥量时可能导致互斥量不可用,所以必须设置“一致性”
#include 
  • 互斥量还有以下属性

a5c089aae7820926870bd092ecdef4cf.png
  • 可以用下面的接口操作这些属性
#include 

读写锁属性

  • 支持进程共享属性
#include 

条件变量

  • 支持进程共享和时钟属性

屏障

  • 支持进程共享属性

5.线程池

  • linux和C11需要自己实现线程池
  • windows有内置线程池
  • java线城池

6.线程调度《深度linux内核架构》

参考《深入深入linux内核架构》(豆瓣8.8分) 第二章 进程管理和调度

  • 在linux内核中,进程与线程本质是一个数据结构task_struct实例
  • 创建进程的系统调用fork,创建线程的系统调用clone都会在内核调用do_work创建task_struct,然后把task_struct->entity注册到cpu调度器上

950696d10d5b7c484902cb4114784138.png
  • 每个cpu上有一个核心调度器,核心调度器会根据task_struct->entity的相关属性选择合适的调度器

5b9833e1fea78e1c5df22459f3b9715f.png
    • 完全公平调度器
      • 将线程就绪线程放在一颗红黑树上,红黑树按照等待时间的相反数排序
      • 等待时间最长的是红黑树的最左边节点
      • 每次从红黑树上取出一个线程执行
      • 执行时间片满,或者线程自动放弃cpu时间片,可以将线程放回这块红黑树

b3b52703d573f7e207f8e35d80d276a8.png
    • 周期性调度器
      • 内存回收线程
      • 电源休眠检测线程
    • 实时调度器
      • 任务根据不同的优先级放到一个散列表上;散列的键是优先级,键值是线程实例,线程实例按照注册顺序双链表排列
      • 调度顺序从高优先级依次往后调度,每个线程可以执行任意长时间

26e57d350d7f42555d7bb1edf40f6c4c.png

7.多线程算法(算法导论3)

  • 突破串行算法的极限时间复杂度
  • 充分利用多核CPU的每一个核
  • 静态线程与动态线程
    • 静态是指线程创建之需要保存线程环境上下文,就是我们通常理解的多线程;使用麻烦;
    • 直接使用并行语法即可,简化线程调度、通信、管理;
  • 示例算法
    • 计算第n个斐波那契数
    • 矩阵乘法
    • 归并排序
  • 将多线程运行指令集依赖关系看成是一个有向无环图,可以帮助理解
    • 串联的链必须串行计算
    • 并联的链可以并行计算,并以最长链为最终时间复杂度

4d5e747a89cb42c0f888827d7ca18874.png
  • 性能度量
    • 工作量:所有计算指令的总时间;即有向无环图的顶点数;单处理器运行时间;
    • 持续时间:有限无环图中的关键路径顶点数;无穷多个处理器运行时间;
    • 工作量定律
      • T p >= T1/p #Tp是每个处理器运行的平均总时间
    • 持续时间定律
      • Tp >= T
    • 加速比:T1/Tp;如果 T1/Tp = theta(p)为线性加速比;T1/Tp = P为完美线性加速比
      • 超过完美加速比增加CPU核心数已经没有意义;只能改进算法本身的并行度;
    • 并行度

b51136bb69bf136fe56ba89a1b88ab9f.png
  • 贪心调度器
    • 完全步:有大于核心数任务调度
    • 非完全步:少于CPU核心数任务可调度

8.函数可重入

  • 仅有局部变量的函数可重入
  • 有些系统调用和标准库函数值得注意
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值