并发的各种概念定义

数据竞争(Data Race)

数据共享读, 数据可写(会发生变化)

竞态条件(Race Condition)
  • 多线程、进程操作(读写)共享数据的最终结果取决于进程、线程执行的时序(顺序);如if(a>0) a++,可能执行一次a++也可能执行多次a++;
互斥

进程、线程竞争使用具有排它性的共享资源。

临界资源

临界资源、互斥资源或者共享变量(一次值允许一个进程、线程使用)。

临界区(互斥区)

对临界资源实施操作的程序片段

同步

系统中多个进程、线程中发生的事件存在某种时序关系,需要相互合作,共同完成一项任务。
关键字:多*程,通信,时序(协作)

同步机制

为何叫同步机制,而不是叫别的??因为常常把互斥看做一种特殊的同步,实际上同步机制技能解决互斥问题也能解决同步问题,

线程上下文

是指再某一时间CPU寄存器和程序计数器的内容

Tss任务状态段,位于内存中的一段数据

经典同步机制-信号量以及PV操作

信号量:
描述:进程、线程间传递信息的一个整数值
定义:

struc semaphore{
  int count
  queueType queue;//等待队列
}

操作:初始化 ,P和V(P、v分别是荷兰语的test(proberen)和 increment(verhogen))

P V
DOWN UP
- +
p(s){
  s.count--;
  if(s.count < 0){
    该进程状态设置为阻塞状态;
    将该进程插入s.queue这个等待队列末尾
    进程、线程让出cpu,操作系统调度选择新的线程、进程来运行;
  }
  //如果count 不小于0 ,那么继承继续持有cpu,继续运行。
}
v(s){
  s.count++;
  if(s.count <= 0){
    唤醒s.queue这个等待队中等待的一个进程;
    将其状态设置为就绪态,并将其插入就绪队列。
  }
  //如果count > 0,继续持有cpu,继续运行。
}

如何解决互斥和同步的问题?

同步机制-管程
  • PV操作由程序员控制容易出错,提供更方便的并发处理操作。
  • 由关于共享资源的数据结构以及在其上操作的一组过程组成
  • 进程只能通过调用管程中的过程来间接地访问管程中的数据结构。
  • 有标示以区分

如何解决互斥和同步问题

  • 互斥问题:
    1 管程是互斥进入(为了保证管程中数据结构的数据完整性)
    2 管程的互斥性是由编译器负责保证的。
  • 同步问题
    1 管程中设置条件变量,等待/唤醒操作,以解决同步问题。
    1.1 条件变量
    1.2 等待操作:可以让进程、线程在条件变量上等待(此时,应先释放管程的使用权,不然别其它线程、进程拿不到使用权);
    1.3 发信号操作:也可以通过发送信号将等待在条件变量上的进程、线程唤醒。
管程的大麻烦
并发的各种概念定义_第1张图片
image.png
HOARE管程

Q等待
Q被放置到条件变量等待队列中。
P唤醒Q P等待Q执行;
1.Q在条件变量队列中被移除唤醒,
2.P被放置到紧急等待队列中,
3.优先级 紧急等待队列 > 入口等待队列
缺点:两次进程、线程切换
P唤醒Q,P:活动,Q:等待
P等待Q, P: 休眠,Q:激活;Q执行完,P激活。

并发的各种概念定义_第2张图片
image.png
并发的各种概念定义_第3张图片
image.png

条件变量的实现

  • 在管程内部声明和使用
  • 对条件变量执行wait和signal操作

wait(c)操作:

  • 如果紧急等待队列非空,则唤醒第一个等待者(不施放管程的互斥权,入口等待队列的优先级低,不让执行);
  • 否则释放管程的互斥权
  • 执行此操作的进程进入C链末端

signal(c)操作:
c:链为空,则相当于空操作,执行此操作的进程继续执行;否则唤醒第一个等待着,执行此操作的进程进入紧急等待队列末尾。

管程的实现

实现途径:

  • 直接构造:语言中自带管程
  • 间接构造: 用某种已经实现的同步机制去构造(比如用信号量和PV操作来构造管程)
MESA管程
并发的各种概念定义_第4张图片
image.png
  • notify强调了不进行进程/线程切换,发信号的进程/线程继续执行。

  • notify的结果:位于条件变量等待队列头部(第一个wait)的进程在将来合适的时候,且当处理器可用时恢复执行,即不是notify后,立即执行。

  • 不能保证在它执行前,条件变量内容没变(1.可能其他的进程进入管程,修改了条件,2notify()方法 到 到线程退出前这区间的代码修改了条件),因此这个进程必须重新检查条件。用while循环取代if

  • 导致对条件变量至少多一次额外的检测,但不再有额外的进程切换;并且对等待进程在notify之后何时运行没有任何限制。

  • 对notify改进
    1.wait(long timeout);给每个条件原语关联一个监视计时器,无论是否被通知,一个等待时间超时的进程都可以被设置为就绪状态;当该进程被调度执行时,会再次检查相关的条件,如果满足则继续执行。
    3.超时可以防止这种情况,当某些进程/线程 在notify之前就挂掉,等待该条件的进程/线程会处于饥饿状态,永远的等下去;

  • 引入broadcast:使所有在该条件上等待的进程都被释放并进入就绪队列。

  1. 当不知道多少进程被激活时候,这种方式非常方便。
  2. 无法判准确判读按激活那个进程/线程,也可以使用广播。
CAS

CAS(Compare and swap ):比较并交换
白话说是 ,如果你是nn的情况下(比较),那么我才要把你更新成mm(交换);如果..那么.. 对应 and;所以是比较并交换。
JUC里的AtomicInteger,AtomicBoolean,AtomicLong等内部都用CAS操作来实现原子操作的功能。

ABA

ABA是一种现象,即从A变为B,又从B变为A,如果第一次看到第一个A,第二次看到第二个A, 两个A都是A,好像没有变化,但是其实漏掉中间状态B。如果我们的程序要求区分第一个A和第二个A,即认为两个A不同,且能区别,那只能通过附加的版本信息来标识区分。举个例子昨天的你和今天的你 都是你 ,但是又不是相同的你,如何区分两个你呢,比如今天的你是xx年xx月xx日的你,那么昨天的你就是xx年xx月mm日的你,附加上版本信息(这里用了日期)。
数据库的乐观锁,一般也都是采用加上一个Version字段,不但目标值要相同,版本也要相同。
JUC中的AtomicStampedReference,就是提供引用+stamp(版本)的方式来增强CAS的实现,我们可以借助它来避免ABA问题

自旋
while(xxx){
    //获取最新的值
   currentValue = get();
   // cas操作成功了,就退出;
   if(true == CAS(... , currentValue,destValue)){
      break;
    }
    //cas操作不成功,继续循环。这个循环的线程中不会主动的释放CPU的使用权。
}
公平锁 && 非公平锁

多个线程都申请持有锁,锁同一时刻只能被一个线程持有;
当持有锁的线程使用完毕,释放锁之后,其他线程开始抢占锁,其中一个线程拿到锁之后,其他线程等待锁释放后再争夺。

  • 公平锁 : 申请抢占锁的线程排队,当锁被释放后,先申请的将先得到锁;缺点,os激活线程是随机的,如果激活的不是队列头一个,就得放弃本次激活的线程的使用,再次执行线程调度。直到激活的线程是等待队列头的线程。
  • 非公平锁:当锁被释放后,哪个线程持有锁不遵守申请的顺序,而根据os对线程的调度的结果,哪个线程被优先唤醒,哪个线程就持有锁。
  • 公平锁的缺点:等待队列头的线程可能要经历n次线程调度才能被唤醒;这n次线程调度用时多久不确定。

你可能感兴趣的:(并发的各种概念定义)