Linux-互斥-互斥锁-死锁

Posted 天津 唐秙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-互斥-互斥锁-死锁相关的知识,希望对你有一定的参考价值。

1. 互斥

1.1 互斥锁的原理

  互斥锁的底层是一个互斥量,而互斥量的本质是一个计数器,计数器的取值只有两种,一种是1,一种是0。1表示当前临界资源可以被访问,0表示当前临界资源不可以被访问。

获取/释放互斥锁的逻辑:
  1.调用加锁接口,加锁接口内部判断计数器的值是否为1,如果为1,则能访问,并且如果加锁成功之后,就会将计数器的值从1变为0,如果为0,则不能访问。
  2.调用解锁逻辑,计数器的值从0变成1,表示资源可用。

加锁:
  寄存器和内存当中的计数器的值进行交换,判断寄存器的当中的值是否为1,如果为1,则能够加锁,反之不能。

1.2 互斥锁的接口

初始化互斥锁变量
  互斥锁的类型:pthread_mutex_t

1.2.1 静态初始化

pthread_mutex_t mutex = PTHREAD_MUTRX_INITIALIZER
  这是一个宏定义,本质上包含多个值
在这里插入图片描述
  root用户下执行下面语句可以打开查看

vim /user/include/pthread.h
vim /usr/include/bits/pthreadtypes.h

1.2.2 动态初始化

pthread_mutex_init函数
在这里插入图片描述
  int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
  mutex:该参数为出参,由调用者传递一个互斥锁变量的地址,由pthread_mutex_init这个函数进行初始化
  attr:互斥锁的属性信息,一般设置为NULL,采用默认属性
注意: 动态初始化互斥锁变量的情况需要动态销毁互斥锁(pthread_mutex_destroy),否则就会造成内存泄露。

1.2.3 加锁

  1.阻塞加锁接口:pthread_mutex_lock函数
在这里插入图片描述
  int pthread_mutex_lock(pthread_mutex_t *mutex);
注意:
  如果互斥锁变量当中的计数器的值为1,调用该接口,则加锁成功,该接口调用完毕,函数返回。如果互斥锁变量的那个汇总的计数器的值为0,调用该接口,则调用该接口的执行流阻塞。

  2.非阻塞加锁接口:pthread_mutex_trylock函数
在这里插入图片描述
  int pthread_mutex_trylock(pthread_mutex_t *mutex);
注意:
  不管有没有加锁成功,都会返回,所以,需要对加锁结果进行判断,判断是否加锁,如果加锁成功,则操作临界资源,反之,则需要循环获取互斥锁,直到拿到互斥锁。

  3.带有超时时间的加锁接口:pthread_mutex_timedlock
在这里插入图片描述
   int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

  超时时间内,如果没有获得互斥锁,则返回,超时时间内,如果获取了互斥锁也直接返回。
注意:
  带有超时时间的加锁接口还需要引入头文件time.h
在这里插入图片描述

1.2.4 解锁接口

  pthread_mutex_unlock函数
在这里插入图片描述
  int pthread_mutex_unlock(pthread_t* mutex)
注意:
  不管是阻塞加锁/非阻塞加锁/timedlock加锁成功都可以使用该接口进行解锁。

1.2.5 销毁接口

  pthread_mutex_destroy
在这里插入图片描述
  int pthread_mutex_destroy(pthread_t* mutex)
功能: 释放动态开辟的互斥锁的资源

1.3 互斥锁的使用

1.3.1 什么时候要初始化互斥锁?

  在创建工作线程之前,进行初始化互斥锁。

1.3.2 什么时候进行加锁?

  在执行流访问临界资源之前进行加锁操作。
注意:
  一个执行流加锁成功之后,再去获取互斥锁,该执行流也会阻塞。加锁之后一定要记得解锁,否则就会导致死锁。

1.3.3 什么时候解锁?

  在执行流所有有可能退出的地方进行解锁。

1.3.4 什么时候释放互斥锁资源?

  在所有使用该互斥锁的线程全部退出之后,就可以释放该互斥锁了。

1.4 代码验证

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREAD_NUM 2

int g_tickets = 100000;
pthread_mutex_t my_lock;

void* MyThreadStart(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&my_lock);
        if(g_tickets > 0)
        {
            printf("i have %d, i am %p\\n", g_tickets, pthread_self());
            g_tickets--;
        }
        else
        {
            pthread_mutex_unlock(&my_lock);
            pthread_exit(NULL);
        }
        pthread_mutex_unlock(&my_lock);
    }
    return NULL;
}

int main()
{
    pthread_mutex_init(&my_lock, NULL);

    pthread_t tid[THREAD_NUM];
    for(int i = 0; i < THREAD_NUM; i++)
    {
        int ret = pthread_create(&tid[i], NULL, MyThreadStart, NULL);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    for(int i = 0; i < THREAD_NUM; i++)
    {
        pthread_join(tid[i], NULL);
    }

    pthread_mutex_destroy(&my_lock);
    printf("phread_join end...\\n");

    return 0;
}

在这里插入图片描述
在这里插入图片描述
  因为我这使用的这个机器只有一个CPU所以,线程只能是并发的执行。

2. 死锁

2.1 死锁的定义

  简单的定义:当一个执行流获取到互斥锁之后,并没有进行解锁,就会导致其他执行流由于获取不到锁资源而进行阻塞,我们将这个现象称之为死锁。
  复杂的定义:当线程A获取到互斥锁1,线程B获取到互斥锁2的时候,线程A和线程B同时还想获取对方手中的锁(线程A还想获取互斥锁2,线程B还想获取互斥锁1),此时就会导致死锁。

2.2 死锁的现象

在这里插入图片描述

2.3 死锁的代码

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t lock_1;
pthread_mutex_t lock_2;

void* MyThread_A(void* arg)
{
    pthread_mutex_lock(&lock_1);
    sleep(2);
    pthread_mutex_lock(&lock_2);

    return NULL;
}

void* MyThread_B(void* arg)
{
    pthread_mutex_lock(&lock_2);
    sleep(2);
    pthread_mutex_lock(&lock_1);

    return NULL;
}

int main()
{
    pthread_mutex_init(&lock_1, NULL);
    pthread_mutex_init(&lock_2, NULL);

    pthread_t tid_A, tid_B;
    pthread_create(&tid_A, NULL, MyThread_A, NULL);
    pthread_create(&tid_B, NULL, MyThread_B, NULL);

    pthread_join(tid_A, NULL);
    pthread_join(tid_A, NULL);

    pthread_mutex_destroy(&lock_1);
    pthread_mutex_destroy(&lock_2);

    return 0;
}

1.执行死锁的代码
在这里插入图片描述
2.查看进程号
在这里插入图片描述
3.调试正在运行的进程
在这里插入图片描述
4.查看当前线程(主线程)的调用堆栈
在这里插入图片描述
5.将查看调用堆栈这个功能应用于所有线程
在这里插入图片描述
6.跳转到3号线程
在这里插入图片描述
7.查看锁的情况
在这里插入图片描述

2.4 死锁的必要条件

  1.不可剥夺:执行流获取了互斥锁之后,除了自己主动释放锁,其他执行流不能解释该互斥锁
  2.循环等待
  3.互斥条件:一个互斥锁只能被一个执行流在同一时刻拥有
  4.请求与保持:“吃着碗里瞧着锅里”

2.5 预防死锁

  1.破坏必要条件:循环等待或者请求与保持
  2.加锁顺序一致
  3.避免锁没有被释放
  4.资源一次性分配

2.6 避免死锁的算法

  1.死锁检测算法
  2.银行家算法

以上是关于Linux-互斥-互斥锁-死锁的主要内容,如果未能解决你的问题,请参考以下文章

Linux___线程互斥与同步

五互斥量概念用法死锁

golang-锁-死锁,互斥锁,读写锁

Linux多线程——互斥和同步

[linux] linux多线程详解

[linux] linux多线程详解