码迷,mamicode.com
首页 > 其他好文 > 详细

thread

时间:2020-07-12 12:03:55      阅读:59      评论:0      收藏:0      [点我收藏+]

标签:pipe   reload   对象   删除   pip   指令   调度   信号   ado   

多线程编程

并发编程的目的是:

  1. 提高资源的利用率
  2. 提高响应速度

常见资源

  • 带宽
  • 链接数
  • cpu
  • 内存
  • gpu

同步原语

  • 原子操作

    • 保证内存读取-修改-写回序列原子执行。
    • 原子意味着本地cpu不被中断或内存总线(或缓存)加锁
    • 是同步的基础
    • 原子操作
    • 状态

自旋锁(spinlock)
锁 + 自旋

信号量(semaphore)
锁 + 等待队列

互斥量(mutex)
binary 信号量

条件变量(condition variable)

管程(monitor)
高级同步元素,编程语言实现。有(变量 + 操作组成)

内存模型

多核CPU,每个CPU单独的Cache,主内存只有一个。

重排序

  • 编译器重排 合并读 合并写 变量推测(省掉分支)中间变量消除 很多优化在多线程下是不安全的。
  • cpu重排 指令预取,pipeline调度
  • 内存重排 由于缓存存在,写缓存意味着还没结束,并且难以预测何时结束。多线程下不安全。

内存屏障
屏障导致缓存flush和失效
简单分为四类:

  • loadload 读屏障 禁止前后读重排
  • storestore 写屏障 禁止前后写重排
  • storeload 一般屏障 禁止前后读和写重排
  • loadstore 少见。linux还有一种数据依赖的屏障

另外锁/原子量的acquire/release操作隐含了内存屏障的作用。
还有sleep thread.start thread.join

happens-before

acquire-release语义

volatile
java 可见性(约束),下一个读一定看见上一次写。
C++ 不要排序,驱动IO,内容无法推测。
相似点都是加入内存屏障(memory barrier),实现缓存flush和缓存失效。
volatile插入内存屏障表,根据机器架构以及前后关系会删除多余的内存屏障。

final
final值初始化以后在不同线程不应改看到不一样的值。
规则:

  • 构造函数final赋值 < 对象赋值给引用之间 插入storestore
  • 对象赋值 < 对象.final读 插入loadload

仅仅在某些乱序处理器需要特殊处理。

死锁
死锁产生的四个条件:

  • 资源互斥
  • 保持请求 保有资源再去请求资源
  • 不剥夺 资源占有即使超时也不能剥夺
  • 循环等待条件

避免死锁办法:

  • 获取不同资源锁的顺序一致(都先lock A,再lock B),破坏循环等待条件
  • 超时锁,失败释放资源再重试(复杂情况)
  • 合并锁,粒度变大,但是太粗,性能差,太细,overhead变大,代码复杂

避免死锁C++

  • lock可以锁多个锁,保证顺序,lock_guard(adopt_lock)保证锁的释放
  • 如果临界区有用户代码,一般使用锁的超时版本
  • hierarchical_mutex(不在标准库中),保证同层级(指调用层级)只能拥有锁一次,并且高级可以再锁低级

诊断死锁

  • 通过Jstack或者Jconsole找出JVM中线程的monitor是否存在环
  • java中monitor是带有owner信息的,因此很容易通过当前获取的monitor,找出是否存在环
  • C++ 也可以通过gdb打印出mutex结构(包含__owner线程字段),但是c++线程中并不保存当前pending的mutex信息,需要人工去看栈信息

thread

标签:pipe   reload   对象   删除   pip   指令   调度   信号   ado   

原文地址:https://www.cnblogs.com/finesimpletop/p/13287681.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!