多线程专题之线程死锁原因之谜

引子:线程死锁曾是多少程序员的噩梦,每每为此食不甘味,夜不成寐,一句话:苦不堪言。本文从几个场景入手,试图解开产生死锁的原因之谜。

教科书:说的很具体,理解很抽象

  关于死锁产生的原因《操作系统》中有比较好的说明:

  (1)因为系统资源不足。

  (2)进程运行推进的顺序不合适。

  (3)资源分配不当等。

  关于死锁出现的必要条件也有比较具体的说明:

  (1)互斥条件:一个资源每次只能被一个进程使用。

  (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  (3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

  (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

  这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,这也为我们实际应用中定位死锁问题,提供了路由。

情景一、不加锁,两线程访问,变量访问示例

  关于死锁,有锁才能死,如果我们不加锁,自然不会发生死锁,但是如果不加锁,对资源的访问,将会发生什么情况呢。不妨看下面的例子:

  当两个线程读写相同变量时,线程A读取变量然后给予变量赋予一个新的值,但是写操作需要两个存储器周期。当线程B在这两个存储器周期中间读取这个相同变量时,它就会得到不一致的值。这就是为什么要对多线程资源访问进行加锁,加锁以后的访问顺序就变成了顺序访问,从而可以避免资源的不一致访问。

情景二、不加锁,多线程访问,增量操作示例

  当两个或多个线程试图在同一时间修改同一个变量时,如果不加锁也会出现数据资源不一致的情况。如下图所示:

  我们可以看到,增量操作分为三个步骤进行:(1)从内存单元读入寄存器。(2)从寄存器中进行变量值的增加。(3)把新的值写回内存单元。如果两个线程试图同时对统一变量执行增量操作时,结果可能出现不一致。变量可能比原来增加了1,也可能增加了2,具体是1,还是2取决于第二个线程读取变量时获得的值是5还是6。这里面有一个前提就是变量增加的操作不是原子操作,这是因为现代计算机系统中,存储器访问需要多个总线周期,多处理器的总线周期通常在多个处理器上是交叉的,所以无法保证数据时顺序一致的。

情景三、互斥锁,多变量部分锁

  以上示例已经讲明了我们为何需要线程锁,不加锁将会导致数据资源访问的不一致。可是加锁后,如果存在满足死锁的必要条件,又会产生死锁,我们该怎么办呢?不妨先来看一个示例:

#include <stdlib.h>
#include <pthread.h>

#define NHASH 29
#define HASH(fp) (((unsigned long)fp)%NHASH)

struct foo *fh[NHASH];

pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;

struct foo {
    int             f_count;
    pthread_mutex_t f_lock;
    struct foo     *f_next; /* protected by hashlock */
    int             f_id;
    /* ... more stuff here ... */
};

struct foo *
foo_alloc(void) /* allocate the object */
{
    struct foo    *fp;
    int            idx;

    if ((fp = malloc(sizeof(struct foo))) != NULL) {
        fp->f_count = 1;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            return(NULL);
        }
        idx = HASH(fp);
        pthread_mutex_lock(&hashlock);
        fp->f_next = fh[idx];
        fh[idx] = fp->f_next;
        pthread_mutex_lock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        /* ... continue initialization ... */
        pthread_mutex_unlock(&fp->f_lock);
    }
    return(fp);
}
//增加
void
foo_hold(struct foo *fp) /* add a reference to the object */
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}
//查找已经对象
struct foo *
foo_find(int id) /* find an existing object */
{
    struct foo    *fp;
    int            idx;

    idx = HASH(fp);
    pthread_mutex_lock(&hashlock);
    for (fp = fh[idx]; fp != NULL; fp = fp->f_next) {
        if (fp->f_id == id) {
            foo_hold(fp);
            break;
        }
    }
    pthread_mutex_unlock(&hashlock);
    return(fp);
}
//减小
void
foo_rele(struct foo *fp) /* release a reference to the object */
{
    struct foo    *tfp;
    int            idx;

    pthread_mutex_lock(&fp->f_lock);
    if (fp->f_count == 1) { /* last reference */
        pthread_mutex_unlock(&fp->f_lock);  //如果不解锁会怎么样呢?
        pthread_mutex_lock(&hashlock);    //如果顺序发生变化呢?
        pthread_mutex_lock(&fp->f_lock);
        /* need to recheck the condition */
        if (fp->f_count != 1) {
            fp->f_count--;
            pthread_mutex_unlock(&fp->f_lock);
            pthread_mutex_unlock(&hashlock);
            return;
        }
        /* remove from list */
        idx = HASH(fp);
        tfp = fh[idx];
        if (tfp == fp) {
            fh[idx] = fp->f_next;
        } else {
            while (tfp->f_next != fp)
                tfp = tfp->f_next;
            tfp->f_next = fp->f_next;
        }
        pthread_mutex_unlock(&hashlock);
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    } else {
        fp->f_count--;
        pthread_mutex_unlock(&fp->f_lock);
    }
}

以上代码注意加锁的顺序,如果顺序错了,则会有可能出现死锁。

时间: 2024-08-03 01:37:08

多线程专题之线程死锁原因之谜的相关文章

java的线程死锁

由于线程可能进入堵塞状态,而且由于对象可能拥有"同步"方法--除非同步锁定被解除,否则线程不能访问那个对象--所以一个线程完全可能等候另一个对象,而另一个对象又在等候下一个对象,以此类推.这个"等候"链最可怕的情形就是进入封闭状态--最后那个对象等候的是第一个对象!此时,所有线程都会陷入无休止的相互等待状态,大家都动弹不得.我们将这种情况称为"死锁".尽管这种情况并非经常出现,但一旦碰到,程序的调试将变得异常艰难. 就语言本身来说,尚未直接提供防

waveInReset/waveOutReset死锁原因与解决方案

问题背景   录音播音实际需求 1.随时终止 2.录音并非文件,而是形成rtp发送 3.播音并非源于文件,而是源于rtp   因此Waveform audio使用的buffer较小,不断的装载/发送 buffer,终止的时候Reset并且close. 大致如下调用的循环   录音 waveInUnprepareHeader waveInPrepareHeader waveInAddBuffer   播音 waveOutUnprepareHeader waveOutPrepareHeader wa

C#多线程、跨线程与线程安全的示例详解

C#多线程.跨线程与线程安全的示例详解(三种不同方法)  代码如下 复制代码 using System.Threading; public static class Extensions     {         //控件扩展方法(用于跨线程操作),因为为了线程的安全,防止资源竞争出现死锁或不一致的状态,.NET是不允许进行跨线程访问窗体控件的.         public static void SafeCall(this Control ctrl, Action callback)   

java 进程死锁-java 模拟线程死锁实验

问题描述 java 模拟线程死锁实验 我想两个线程一直在售票知道票卖光,但是用while()控制循环后出现 售票2进入共享区 当前没有车票可以出售! 售票2进入共享区 当前没有车票可以出售! 售票2进入共享区 ..... 具体代码如下 package com; public class SaleThread extends Thread{ private String name; ShareResource shareresource; public SaleThread(){} public

线程死锁的质疑

问题描述 在网上搜到一篇线程死锁的博客,后来发现,两个线程并不会发生死锁.我的理由是Threadthread01=newThread(run01);Threadthread02=newThread(run02);这两个线程构造函数传进来的参数Runnable对象run01,run02,会分别创建obj1.obj2,也就说两个线程通过synchronized(obj1)和synchronized(obj2)并不能实现互斥.[/color]packagecn.thread;publicclassTh

线程 死锁-一个关于线程挂起、唤醒的问题

问题描述 一个关于线程挂起.唤醒的问题 在windows平台上, 线程的挂起可以使用API SuspendThread()函数,而减少线程的挂起计数则可以使用ResumeThread()函数,但是今天却发现了其中隐藏的问题:1. 现有一个主线程和子线程A,在主线程中调用SuspengThread()使得A被挂起:2. 主线程中调用ResumeThread()使得A恢复运行:3. 主线程立刻调用SuspendThread()意图挂起A时,主线程竟然被阻塞在了调用处!也就是说产生了deadlock!

多线程问题-新手求助关于Java多线程中启动线程问题

问题描述 新手求助关于Java多线程中启动线程问题 public class Example15_1 { /** * @param args */public static void main(String[] args) { // TODO Auto-generated method stub SpeakHello speakHello; SpeakNinhao speakNinhao; speakHello = new SpeakHello(); speakNinhao= new Speak

C# 如何让 多线程中每个线程间隔毫秒执行同一个方法?

问题描述 多线程的好处让效率提高很多倍,但是在某些情况下要求操作同一个方法的时候要求有间隔,这个间隔当然是毫秒级别的否则多线程的意义就体现不出来,本问题就是怎么让多线程中每个线程间隔毫秒执行同一个方法,测试代码如下:privatevoidbtnTest_Click(objectsender,EventArgse){TestManyThreadtest=newTestManyThread();test.Start();} 主要代码如下classTestManyThread{privatestati

synchronized-Java的一个简单线程死锁问题

问题描述 Java的一个简单线程死锁问题 class BadPerson { public synchronized void say(GoodPerson good) { System.out.println(""把钱给我,放了你的人.""); good.give(); } public synchronized void give() { System.out.println(""得到了钱,同时被警察抓了.""); }}c