死锁和活锁的区别是什么?

谁能举例(代码)解释一下死锁活锁之间的区别?

223419 次浏览

摘自http://en.wikipedia.org/wiki/Deadlock:

在并发计算中,死锁是一种状态,其中一组操作的每个成员都在等待其他成员释放锁

A 活锁类似于死锁, 除了状态 活锁中涉及的进程 关于一个不断地改变 另一个,毫无进展。活锁是 资源饥饿的特例; 一般的定义只是说 这不是一个特定的过程 进展。< / p >

一个真实世界的例子 两个人见面时就会出现Livelock 在狭窄的走廊里,每个人都在尝试 礼貌地让开让 另一关,但他们最终 在外面左右摇摆 有什么进展吗,因为他们都 重复以相同的方式移动 同时。< / p >

Livelock是一个风险 一些检测和的算法 从死锁中恢复。如果超过 一个进程采取行动,即死锁 检测算法可以重复 触发。这可以通过 确保只选择一个进程 随机或按优先级)采取行动

< >强死锁 死锁是一种任务等待的情况 永远不可能发生的情况 满意 - task要求独占共享的控制权 资源 - task在等待其他任务时持有资源 待释放资源 —不能强制任务放弃资源 -循环等待条件

< >强活锁 当两个或 更多的任务依赖和使用some 资源导致循环依赖 这些任务继续执行的条件 永远的奔跑,因此阻挡了一切的下降 优先级任务从运行(这些 低优先级任务出现问题 称为饥饿)< / p >

Livelock .

一个线程通常对另一个线程的动作做出响应。如果 另一个线程的动作也是对另一个线程动作的响应

与死锁一样,活锁线程是无法取得进一步进展。然而,线程不被阻塞 -它们只是忙着回复对方,没时间继续工作。这就好比两个人试图在走廊里擦肩而过:阿方斯向左移动让加斯东过去,而加斯东向右移动让阿方斯过去。看到他们仍然互相阻挡,阿方斯向右移动,加斯顿向左移动。他们仍然在互相阻挡,等等……

< em >活锁< / em >< em > < / em >死锁之间的主要区别是线程不会被阻塞,相反,它们会尝试连续地相互响应。

在此图像中,两个圆圈(线程或进程)都将尝试通过左右移动为另一个圆圈提供空间。但是他们不能再前进了。

enter image description here

这里所有的内容和例子都来自

< p > 操作系统:内部原理和设计原则 < br > 威廉切除< br > 8º版< / p >

死锁:两个或多个进程无法继续进行的情况,因为每个进程都在等待另一个进程执行某项操作。

例如,考虑两个进程P1和P2,以及两个资源R1和R2。假设每个进程都需要访问这两个资源来执行其部分功能。那么有可能出现以下情况:OS将R1分配给P2,将R2分配给P1。每个进程都在等待两个资源中的一个。在获得资源之前,双方都不会释放已经拥有的资源 并执行需要这两种资源的功能。这两个

.进程死锁

活锁:两个或多个进程连续地改变它们的状态以响应其他进程的变化而不做任何有用的工作的情况:

饥饿:可运行进程被调度程序无限忽略的情况;虽然它可以继续,但它永远不会被选择。

假设三个进程(P1、P2、P3)都需要定期访问资源r。假设P1拥有资源,P2和P3都被延迟,等待资源。当P1退出临界区时,应该允许P2或P3访问r。假设操作系统授予P3访问权,P1在P3完成临界区之前再次要求访问。如果操作系统在P3完成之后授予P1访问权,然后交替授予P1和P3访问权,那么P2可能会无限期地拒绝访问资源,即使没有死锁情况。

附录a -并发性中的主题

死锁的例子

如果两个进程都在执行while语句之前将它们的标志设置为true,那么两个进程都会认为对方已经进入临界区,从而导致死锁。

/* PROCESS 0 */
flag[0] = true;            // <- get lock 0
while (flag[1])            // <- is lock 1 free?
/* do nothing */;      // <- no? so I wait 1 second, for example
// and test again.
// on more sophisticated setups we can ask
// to be woken when lock 1 is freed
/* critical section*/;     // <- do what we need (this will never happen)
flag[0] = false;           // <- releasing our lock


/* PROCESS 1 */
flag[1] = true;
while (flag[0])
/* do nothing */;
/* critical section*/;
flag[1] = false;

活锁的例子

/* PROCESS 0 */
flag[0] = true;          // <- get lock 0
while (flag[1]){
flag[0] = false;     // <- instead of sleeping, we do useless work
//    needed by the lock mechanism
/*delay */;          // <- wait for a second
flag[0] = true;      // <- and restart useless work again.
}
/*critical section*/;    // <- do what we need (this will never happen)
flag[0] = false;


/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
flag[1] = false;
/*delay */;
flag[1] = true;
}
/* critical section*/;
flag[1] = false;

[…考虑以下事件的顺序:

  • P0设置标志[0]为true。
  • P1设置标志[1]为true。
  • P0检查标志[1]。
  • P1检查标志[0]。
  • P0设置标志[0]为false。
  • P1设置标志[1]为false。
  • P0设置标志[0]为true。
  • P1设置标志[1]为true。

这个序列可以无限地延长,两个进程都不能进入临界区。严格地说,这是没有死锁,因为两个进程的相对速度的任何改变都会打破这个循环,并允许其中一个进程进入临界区。这个条件被称为活锁。回想一下,当一组进程希望进入临界区,但没有一个进程能够成功时,就会发生死锁。使用活锁,有可能成功的执行序列,但也可以描述一个或多个没有进程进入临界区的执行序列。

不再满足于书本。

那么自旋锁呢?

自旋锁是一种避免操作系统锁机制成本的技术。通常你会这样做:

try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}

beginLock()的成本远远高于doSomething()时,问题就开始出现了。用非常夸张的话说,想象一下当beginLock花费1秒,而doSomething只花费1毫秒时会发生什么。

在这种情况下,如果你等待1毫秒,你将避免1秒的阻碍。

为什么beginLock会花费这么多?如果锁是空闲的,不会花费很多(参见https://stackoverflow.com/a/49712993/5397116),但如果锁不是空闲的,操作系统会“冻结”你的线程,设置一个机制在锁被释放时唤醒你,然后在将来再次唤醒你。

所有这些都比检查锁的一些循环要昂贵得多。这就是为什么有时做“自旋锁”更好。

例如:

void beginSpinLock(lock)
{
if(lock) loopFor(1 milliseconds);
else
{
lock = true;
return;
}


if(lock) loopFor(2 milliseconds);
else
{
lock = true;
return;
}


// important is that the part above never
// cause the thread to sleep.
// It is "burning" the time slice of this thread.
// Hopefully for good.


// some implementations fallback to OS lock mechanism
// after a few tries
if(lock) return beginLock(lock);
else
{
lock = true;
return;
}
}

如果您的实现不小心,您可能会使用livelock,将所有CPU都花费在锁机制上。

还看到:

< p > https://preshing.com/20120226/roll-your-own-lightweight-mutex/ < br > 我的旋转锁实现是正确的和最优的吗? < / p >

总结:

死锁:没有人进步的情况,什么都不做(睡觉,等待等)。CPU使用率将较低;

活锁:没有进展的情况,但是CPU花在锁定机制上而不是你的计算上;

饥饿:一个进程永远没有机会运行的情况;因为纯粹的坏运气或它的某些属性(例如低优先级);

自旋锁:避免等待释放锁的代价的技术。

也许这两个例子说明了死锁和活锁的区别:


死锁的java示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class DeadlockSample {


private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);


public static void main(String[] args) {
Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
threadA.start();
threadB.start();
}


public static void doA() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");


try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");


try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
}


public static void doB() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");


try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");


try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
}
}


样例输出:

Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2

实例:


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class LivelockSample {


private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);


public static void main(String[] args) {
Thread threadA = new Thread(LivelockSample::doA, "Thread A");
Thread threadB = new Thread(LivelockSample::doB, "Thread B");
threadA.start();
threadB.start();
}


public static void doA() {
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");


try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");


try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}


public static void doB() {
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");


try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");


try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
}


样例输出:

Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...
这两个例子都强制线程以不同的顺序获取锁。 当死锁等待另一个锁时, livelock并不真正等待——它拼命地试图获得锁,却没有机会得到它。

.每次尝试都会消耗CPU周期

假设你有线程A和线程b,它们都是同一个对象上的synchronised,在这个块中有一个全局变量,它们都在更新;

static boolean commonVar = false;
Object lock = new Object;


...


void threadAMethod(){
...
while(commonVar == false){
synchornized(lock){
...
commonVar = true
}
}
}


void threadBMethod(){
...
while(commonVar == true){
synchornized(lock){
...
commonVar = false
}
}
}

因此,当线程A进入while循环并持有锁时,它会做它必须做的事情,将commonVar设置为true。然后线程B进来,进入while循环,由于commonVar现在是true,所以它能够持有锁。它这样做,执行synchronised块,并将commonVar设置回false。现在,线程A再次获得它的新CPU窗口,它commonVar1即将退出while循环,但线程B刚刚将其设置回false,因此循环再次重复。线程做了一些事情(所以它们不是传统意义上的阻塞),但几乎什么也没有做。

提到livelock不一定要出现在这里可能也不错。我假设一旦synchronised块完成执行,调度器就倾向于另一个线程。大多数时候,我认为这是一个难以实现的期望,取决于许多事情发生在引擎盖下。

我只是想分享一些知识。

< em >死锁 如果一组线程/进程中的每个线程/进程都在等待集合中只有另一个进程可以导致.

. conf事件,则该线程/进程被死锁

这里重要的是另一个进程也在同一个集合中。这意味着另一个进程也被阻止了,没有人可以继续。

当进程被授予对资源的独占访问权时,就会发生死锁。

要产生死锁,必须满足这四个条件。

  1. 互斥条件(每个资源分配给1个进程)
  2. 保持和等待状态(处理持有资源,同时可以请求其他资源)。
  3. 没有抢占条件(以前授予的资源不能被强制拿走)#这个条件取决于应用程序
  4. 循环等待条件(必须是由2个或更多进程组成的循环链,每个进程都在等待链的下一个成员所持有的资源)#它将动态发生

如果我们发现了这些条件,那么我们就可以说可能发生了像死锁这样的情况。

活锁

每个线程/进程一次又一次地重复相同的状态,但没有进一步的进展。类似于死锁,因为进程不能进入临界区。然而,在死锁中,进程在等待而不做任何事情,但在livelock中,进程试图继续,但进程一次又一次地重复到相同的状态。

(在死锁计算中,没有可能成功的执行序列。在活锁计算中,有成功的计算,但有一个或多个执行序列中没有进程进入临界区。

不同于死锁和活锁

当死锁发生时,不会发生任何执行。但在livelock中,会发生一些执行,但这些执行不足以进入临界区。