『 Linux 』多线程互斥锁

5 篇文章 0 订阅
4 篇文章 0 订阅


资源竞争

请添加图片描述

当一个进程中多个线程同时对一个共享资源进行访问时将可能导致数据不一致问题;

#define NUM 5

int g_val = 700;

class threadData {
  // 封装一个线程的基本信息 用于描述线程
 public:
  threadData(const int number) { threadname_ = "Thread_" + to_string(number); }

 public:
  string threadname_;
  pthread_t tid_;
};

void *threadRoutine(void *args) {
  threadData *td =
      static_cast<threadData *>(args);  // 接收线程的管理结构体并进行类型强转
  td->tid_ = pthread_self();  // 存储自己的 tid 方便后续直接访问
  while (true) {
    if (g_val > 0) {
      usleep(100);
      printf("I am %s , the g_val = %d\n", td->threadname_.c_str(), g_val);
      g_val--;
    } else
      break;
  }
  delete td;
  return nullptr;
}

int main() {
  vector<pthread_t> tids;  // 用于管理线程的tid
  for (size_t i = 0; i < NUM; ++i) {
    pthread_t tid;
    threadData *td = new threadData(i);  // 用 new 在堆上实例化一个对象
                                         // 防止 for 循环后变量丢失
    pthread_create(&tid, nullptr, threadRoutine, td);  // 创建线程
    tids.push_back(tid);  // 将线程tid存放至STL容器中
                          // 方便主线程后期对这些线程进行管理
  }

  for (size_t i = 0; i < tids.size(); ++i) {
    // 循环调用 pthread_join 等待线程
    pthread_join(tids[i], nullptr);
  }

  return 0;
}

以该段代码为例;

该程序中创建了5个线程同时对全局数据g_val进行访问;

条件为当g_val > 0时进行--操作,否则break跳出循环;

最终运行结果为:

$ ./mythread 
I am Thread_1 , the g_val = 700
I am Thread_2 , the g_val = 700
I am Thread_0 , the g_val = 700
I am Thread_4 , the g_val = 700
I am Thread_3 , the g_val = 699
I am Thread_1 , the g_val = 695
...
...
I am Thread_0 , the g_val = 2
I am Thread_4 , the g_val = 1
I am Thread_3 , the g_val = 0
I am Thread_1 , the g_val = -1
I am Thread_2 , the g_val = -1
I am Thread_0 , the g_val = -3

即使设置了条件判断,最终的结果也导致了g_val的值变为了-3;

其中g_val--操作并不是原子性的;

该问题即为多线程进行资源竞争导致的数据不一致问题,可参考CSDN -『 Linux 』线程的资源共享,分离,以及多线程并发导致资源竞争;

可用 来保证多线程对一个共享资源的串型访问来放置该问题的发生;


pthread_mutex 互斥锁

请添加图片描述

pthread库提供了一系列的锁的接口以使得用户能通过使用锁来对多线程访问统一共享资源时提供保护;

pthread库中的锁为pthread_mutex锁,是一种互斥锁;

PROLOG
       This manual page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (consult the corresponding Linux
       manual page for details of Linux behavior), or the interface may not be implemented on Linux.

NAME
       pthread_mutex_destroy, pthread_mutex_init - destroy and initialize a mutex

SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
       
RETURN VALUE
       If  successful,  the pthread_mutex_destroy() and pthread_mutex_init() functions shall return zero; otherwise, an error number shall be returned to
       indicate the error.

       The [EBUSY] and [EINVAL] error checks, if implemented, act as if they were performed immediately at the beginning of processing for  the  function
       and shall cause an error return prior to modifying the state of the mutex specified by mutex.

该系列接口为 POSIX 线程库(pthread)中的核心部分;

用于实现互斥锁的创建,初始化,销毁,静态初始化等操作;

  • pthread_mutex_t

    这是一个互斥锁类型的定义,通常在pthread.h头文件中定义;

    互斥锁对象在初始化后可以用于控制对共享资源的访问;

    在使用锁的前提是必须存在一把锁,故在使用互斥锁之前必须使用该类型定义一把锁,如:

    int main(){
    	pthread_mutex_t lock; // 定义一把名为 lock 的锁
    	return 0;
    }
    
  • pthread_mutex_init()

    int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
    

    该接口的功能是初始化一个互斥锁对象,使其可以用于锁定和解锁操作,其参数如下:

    • pthread_mutex_t *restrict mutex

      该参数为指向要初始化的互斥锁对象指针;

    • const pthread_mutexattr_t *restrict attr

      该参数指向互斥锁属性对象的指针;

      当该参数传入nullptr时表示使用默认属性;

    函数调用成功时返回0,失败时返回错误码,一般可能出现的错误码为:

    • EBUSY

      表示该互斥锁正在被使用中(通常这个错误不会在初始化时出现);

    • EINVAL

      表示传递的参数无效,通常是因为传递的属性对象不是一个有效的属性对象;

  • pthread_mutex_destroy()

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

    该接口用于销毁一个互斥锁对象,使其不可再被使用,参数为如下:

    • pthread_mutex_t *mutex

      该参数表示传入一个需要被销毁的互斥锁对象指针;

    函数调用时返回0,失败时返回错误码,其可能出现的错误码与pthread_mutex_init()相同;

  • PTHREAD_MUTEX_INITIALIZER

    该宏提供了一种简单的静态初始化互斥锁的方法;

    当在全局区域中使用该宏定义了一个互斥锁时,该互斥锁将可不使用pthread_mutex_inti()进行初始化以及使用pthread_mutex_destroy()进行销毁;

    当锁被使用完后将被系统自动回收;

    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    

上述接口只是单纯的对互斥锁进行定义,初始化,以及销毁操作,而锁的使用还涉及到一系列接口:

PROLOG
       This manual page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (consult the corresponding Linux
       manual page for details of Linux behavior), or the interface may not be implemented on Linux.

NAME
       pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock - lock and unlock a mutex

SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_lock(pthread_mutex_t *mutex);
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       int pthread_mutex_unlock(pthread_mutex_t *mutex);

RETURN VALUE
       If successful, the pthread_mutex_lock() and pthread_mutex_unlock() functions shall return zero; otherwise, an error number shall  be  returned  to
       indicate the error.

       The  pthread_mutex_trylock()  function shall return zero if a lock on the mutex object referenced by mutex is acquired. Otherwise, an error number
       is returned to indicate the error.

这批接口用于对互斥锁进行锁定和解锁操作;

  • pthread_mutex_lock()

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    

    该接口功能为锁定一个互斥锁对象,如果互斥锁已经被其他线程锁定则该调用线程将阻塞,直到互斥锁可用,参数pthread_mutex_t *mutex表示传入一个需要锁定的互斥锁对象指针;

    当函数调用成功时返回0,失败时返回一个错误码;

  • pthread_mutex_trylock()

    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    

    该接口功能为尝试锁定一个互斥锁对象,如果互斥锁已经被其他线程锁定则返回,不会被阻塞;

    参数pthread_mutex_t *mutex表示传入一个试图要锁定的互斥锁对象指针;

    函数调用成功时返回0,否则返回一个错误码;

  • pthread_mutex_unlock()

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    

    该接口功能为解锁一个互斥锁对象,使用该函数进行解锁时必须由锁定该互斥锁的线程调用此函数;

    参数pthread_mutex_t *mutex表示传入一个需要解锁的互斥锁对象指针;

    函数调用成功时返回0,否则返回一个错误码;

  • 返回值与错误码

    该系列的所有函数在调用失败时都可能返回以下错误码:

    • EINVAL

      表示传递的参数无效,通常是因为传递的互斥锁对象不是一个有效的互斥锁;

    • EPERM

      试图解锁一个没有被当前线程锁定的互斥锁;

    • EDEADLK

      检测到死锁条件(只在 pthread_mutex_lock 中出现);

    • EBUSY

      互斥锁已被其他线程锁定(只在 pthread_mutex_trylock 中出现);


临界区与互斥锁的使用

请添加图片描述

本质上互斥锁是一种以时间换安全的一种操作形式;

在使用互斥锁进行锁定与解锁时必须尽可能在临界区中使用;

因为当一个线程试图锁定一个被其他线程锁定的互斥锁时,它必须进行阻塞等待,直到互斥锁被解锁,这个等待的时间是换取共享资源安全的必要条件;

通过确保共享资源在任意时刻只能被一个线程访问,即多个线程串型访问,防止竞态条件发生;

为了最大化系统性能和减少等待时间,互斥锁的使用必须尽可能限制在临界区内,并且临界区应尽可能小;

所谓的临界区指的是代码段中对共享资源进行访问的一段代码,以本文最初出现的代码为例:

void *threadRoutine(void *args) {
  threadData *td =
      static_cast<threadData *>(args);  // 接收线程的管理结构体并进行类型强转
  td->tid_ = pthread_self();  // 存储自己的 tid 方便后续直接访问
  while (true) {
    if (g_val > 0) {
      usleep(100);
      printf("I am %s , the g_val = %d\n", td->threadname_.c_str(), g_val);
      g_val--;
    } else
      break;
  }
  delete td;
  return nullptr;
}

其中该段代码中的临界区及为:

if (g_val > 0) {
      usleep(100);
      printf("I am %s , the g_val = %d\n", td->threadname_.c_str(), g_val);
      g_val--;
    } else
      break;
  }

对应的使用互斥锁后的代码即为:

#define NUM 5

int g_val = 700;

class threadData {
 public:
  threadData(const int number, pthread_mutex_t *mutex) : lock_(mutex) {
    threadname_ = "Thread_" + to_string(number);
  }

 public:
  string threadname_;
  pthread_t tid_;
  pthread_mutex_t *lock_;
  // 在类中定义一个互斥锁对象类型指针用于接收在主线程中实例化的锁
};

void *threadRoutine(void *args) {
  threadData *td = static_cast<threadData *>(args);

  td->tid_ = pthread_self();
  while (true) {
    pthread_mutex_lock(td->lock_);  // 锁定互斥锁对象
    if (g_val > 0) {
      usleep(100);
      printf("I am %s , the g_val = %d\n", td->threadname_.c_str(), g_val);
      g_val--;

      pthread_mutex_unlock(td->lock_);  // 解锁互斥锁对象

    } else {
      pthread_mutex_unlock(td->lock_);  // 解锁互斥锁对象
      /*
      当一个线程锁定了一个锁时必须经过 if 或者 else 两个选项之一
      为了避免带锁的线程未在 else 处解锁而退出所导致死锁问题
      应在 if else 两处都进行解锁
      */

      break;
    }
  }
  delete td;  // 线程退出时释放描述自身基本属性的结构体对象
  return nullptr;
}

int main() {
  vector<pthread_t> tids;

  pthread_mutex_t lock;                // 定义一个互斥锁对象
  pthread_mutex_init(&lock, nullptr);  // 初始化该互斥锁对象

  for (size_t i = 0; i < NUM; ++i) {
    pthread_t tid;
    threadData *td = new threadData(i, &lock);
    pthread_create(&tid, nullptr, threadRoutine, td);
    // (传入互斥锁对象的指针)利用 new 实例化一个用来维护线程的结构体对象
    // 并将该实例化的对象传给线程作为参数

    tids.push_back(tid);
  }

  for (size_t i = 0; i < tids.size(); ++i) {
    pthread_join(tids[i], nullptr);
  }
  pthread_mutex_destroy(&lock);  // 销毁互斥锁对象
  return 0;
}

其运行结果为:

$ ./mythread 
I am Thread_1 , the g_val = 700
I am Thread_1 , the g_val = 699
I am Thread_1 , the g_val = 698
I am Thread_1 , the g_val = 697
I am Thread_1 , the g_val = 696
I am Thread_1 , the g_val = 695
...
...
I am Thread_2 , the g_val = 4
I am Thread_2 , the g_val = 3
I am Thread_2 , the g_val = 2
I am Thread_2 , the g_val = 1

共享资源因互斥锁而受了保护,避免了资源竞争,从而不会出现数据不一致问题;

修改后的代码使用的为局部初始化的锁,而若是使用宏来全局初始化锁只需要全局定义一个:

pthread_mutex_t lock =  PTHREAD_MUTEX_INITIALIZER;

并且去掉对应的pthread_mutex_init(),pthread_mutex_destroy()同时直接调用pthread_mutex_lock(lock)等函数即可;


饥饿问题

请添加图片描述

饥饿问题指在多线程并发程序中一个或多个线程因无法获取所需的资源从而长时间无法执行从而陷入 “饥饿” 状态;

该问题通常发生在资源分配不公平的情况下;

纯互斥环境下,如果锁分配不够合理则可能导致其他线程的饥饿问题;

以上文中使用互斥锁的代码为例,虽然只截取部分结果,但仍可看出实际上只有部分线程在跑,即当一个线程解锁之后从而再次获取锁导致其他线程唤醒之后锁定互斥锁变量时该锁已经被上一个线程锁定从而再次陷入阻塞等待;

$ ./mythread 
I am Thread_1 , the g_val = 700
I am Thread_1 , the g_val = 699
I am Thread_1 , the g_val = 698
I am Thread_1 , the g_val = 697
I am Thread_1 , the g_val = 696
I am Thread_1 , the g_val = 695
...
...
I am Thread_2 , the g_val = 4
I am Thread_2 , the g_val = 3
I am Thread_2 , the g_val = 2
I am Thread_2 , the g_val = 1

本质原因是在该段代码中每个线程对于锁的竞争能力不同;

一般情况下调度优先级较高的线程将优先获得锁资源;

在该代码中可以理解为唤醒阻塞线程的速度不及线程解锁互斥锁的速度,从而导致一个线程解锁了互斥锁后立马再次锁定互斥锁,当互斥锁被解锁时将唤醒该线程去锁定互斥锁,在锁定互斥锁时锁定失败(已经被解锁后再次锁定互斥锁的线程先锁定),从而导致其他线程无效唤醒同时长期无法获得锁资源而产生饥饿问题;

可使用usleep()等接口时线程在解锁之后进入休眠状态一段时间,从而避免其在解锁之后又马上再次锁定互斥锁从而导致的饥饿问题;


/* 其余代码不变 */

void *threadRoutine(void *args) {
  threadData *td = static_cast<threadData *>(args);

  td->tid_ = pthread_self();
  while (true) {
    pthread_mutex_lock(td->lock_);
    if (g_val > 0) {
      usleep(100);
      printf("I am %s , the g_val = %3d\n", td->threadname_.c_str(), g_val);
      g_val--;

      pthread_mutex_unlock(td->lock_);

    } else {
      pthread_mutex_unlock(td->lock_); 
      break;
    }
    usleep(50); // 使解锁后的线程不马上再次锁定互斥锁
  }
  delete td;  
  return nullptr;
}

运行结果为:

$ ./mythread 
I am Thread_0 , the g_val = 700
I am Thread_1 , the g_val = 699
I am Thread_2 , the g_val = 698
I am Thread_3 , the g_val = 697
I am Thread_4 , the g_val = 696
...
...
I am Thread_4 , the g_val =   5
I am Thread_1 , the g_val =   4
I am Thread_3 , the g_val =   3
I am Thread_0 , the g_val =   2
I am Thread_2 , the g_val =   1

互斥锁的原理及其原子性

请添加图片描述

互斥锁的视线大多数体系结构都提供了swapexchange指令;

该指令的作用是把寄存器和内存单元的数据进行交换,本质上在使用锁时,锁定互斥锁与解锁互斥锁的操作以汇编代码表示可以为如下:

/* 锁定与解锁的伪代码 */

lock: /* 锁定 */
	movb $0, %al
	xchgb %al, mutex
	if (al 寄存器内容 > 0 ){
		return 0;
	}
	else 挂起等待;
	goto lock;
	
unlock: /* 解锁 */
	movb $1, mutex
	唤醒等待 mutex 的线程;
	return 0;
	

在使用互斥锁时,多个线程将访问同一把锁从而使其访问共享资源时构成互斥,互斥锁本质上也是一种临界资源;

为了避免多个线程访问同一把锁而产生资源竞争,互斥锁的设计本身就具有原子性;

线程在执行任何指令时都可能因时间片结束而被切走,从而调度下一个线程;

上图中关键的指令即为xchgb %al, mutex;

该指令是一个具有原子性的交换指令,若是存在两个线程(线程1,线程2),其中 线程1 在执行完xchgb指令后时间片被使用完而被切换为 线程2 ,此时内存中的锁变量值为0,无论 线程2 如何执行xchgb指令其寄存器内容始终为0,从而会阻塞等待;

即为:

  • 线程1执行完xchgb后时间片结束

    线程1 执行完交换指令后时间片结束,保存寄存器内容;

  • 线程2 试图锁定互斥锁

    线程2 判断失败进行挂起等待,直至mutex被解锁时才被唤醒;

  • 线程2 被挂起,切回 线程1

    线程1 恢复上下文内容并向下执行进行判断;

    直至将mutex互斥锁解锁时唤醒 线程2 ;

互斥锁的解锁只需要将内存中的mutex置为1即可,因为每次在进行锁定时都会先将线程中关于mutex的上下文初始化为0;


锁的封装

请添加图片描述

可将互斥锁进行封装使其使用更加便捷;

/* lock.hpp */

#ifndef LOCK_HPP
#define LOCK_HPP

#include <pthread.h>

class Mutex {
 public:
  Mutex(pthread_mutex_t *lock) : lock_(lock) {}
  void Lock() { pthread_mutex_lock(lock_); }
  void Unlock() { pthread_mutex_unlock(lock_); }

  ~Mutex() {}

 private:
  pthread_mutex_t *lock_;
};

class LockGuard {
 public:
    // 构造函数自动调用锁
  LockGuard(pthread_mutex_t *lock) : mutex_(lock) {
    mutex_.Lock();
  }
    // 析构函数自动调用解锁
  ~LockGuard() { mutex_.Unlock(); }

 private:
  Mutex mutex_;
};

#endif

pthread线程库中所提供的互斥锁进行封装;

当全局定义了一把互斥锁后,即可使用LockGuard类构造一个临时对象,并使用{}对锁的声明周期进行管理,当生命周期结束时该临时对象将自动调用析构函数从而进行解锁;

该锁为一个RAII风格的锁;

利用封装后的锁修改最初的代码:

#include "lock.hpp"
#define NUM 5

int g_val = 700;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;  // 定义一把全局的锁
class threadData {
 public:
  threadData(const int number, pthread_mutex_t *mutex) {
    threadname_ = "Thread_" + to_string(number);
  }

 public:
  string threadname_;
  pthread_t tid_;
};

void *threadRoutine(void *args) {
  threadData *td = static_cast<threadData *>(args);

  td->tid_ = pthread_self();
  while (true) {
    {
      LockGuard lockguard(&lock);  // 实例化一个临时锁对象
      if (g_val > 0) {
        usleep(100);
        printf("I am %s , the g_val = %3d\n", td->threadname_.c_str(), g_val);
        g_val--;
      } else
        break;
    }  // { } 用于控制锁对象的生命周期
    usleep(50); // 防止其他线程饥饿状态
  }
  delete td;
  return nullptr;
}

int main() {
  vector<pthread_t> tids;

  for (size_t i = 0; i < NUM; ++i) {
    pthread_t tid;
    threadData *td = new threadData(i, &lock);
    pthread_create(&tid, nullptr, threadRoutine, td);

    tids.push_back(tid);
  }

  for (size_t i = 0; i < tids.size(); ++i) {
    pthread_join(tids[i], nullptr);
  }
  return 0;
}

运行结果为:

$ ./mythread 
I am Thread_1 , the g_val = 700
I am Thread_2 , the g_val = 699
I am Thread_3 , the g_val = 698
I am Thread_4 , the g_val = 697
I am Thread_0 , the g_val = 696
...
...
I am Thread_2 , the g_val =   5
I am Thread_3 , the g_val =   4
I am Thread_1 , the g_val =   3
I am Thread_4 , the g_val =   2
I am Thread_0 , the g_val =   1

重入与线程安全问题

请添加图片描述

  • 重入

    重入是指多线程中多个线程执行同一个函数,当一个执行流未执行完毕时其他执行流再次进入时,该现象被称为 “重入现象” ;

    一个函数在重入的情况下出现问题时,该函数被称为 “不可重入函数” , 反之被称为 “可重入函数” ;

  • 线程安全

    线程安全指一个代码块或数据结构在多线程环境中被多个线程同时访问时不会引发竞态条件或是数据不一致问题;

    常见的当多线程对全局变量或静态变量进行操作且没有锁保护的情况下会出现线程安全问题;

常见的线程不安全有以下情况:

  • 未保护共享变量的函数
  • 函数状态随着被调用状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

常见的不可重入情况如下:

  • 调用malloc/free函数;
  • 调用了标准I/O库函数;
  • 可重入函数体内使用了静态的数据结构;

可重入不等于线程安全;

线程调用了可重入函数不代表线程安全,但是线程调用了不可重入函数必定存在线程不安全;

以该图为例;

准确来说是否可重入属于线程安全中的一个更为严格的子集;


死锁概念

请添加图片描述

死锁指的是两个或者多个线程在互相等待对方释放资源从而进入无限等待的状态导致整个系统无法继续运行;

当产生了死锁的必要条件即会产生死锁问题:

  • 互斥条件

    至少有一个资源处于非共享模式,即一次只能被一个线程占用;

  • 占用并等待条件

    一个线程已经持有至少一个资源,同时等待获取其他资源,而这些资源被其他线程占用;

  • 不可剥夺条件

    资源不能被强制剥夺,必须由该持有资源的线程自行释放;

  • 循环等待条件

    在一个线程集合中每个线程都在等待下一个线程锁持有的资源从而形成一个等待环;

多线程环境中,当一个线程在持有锁的情况下退出或是在持有锁的情况下再次申请锁都会造成死锁问题;

  • 持有锁线程先行退出

    int g_val = 0;
    
    void *ThreadRoutine(void *args) {
      sleep(3); // 确保所有线程都被创建完毕
      pthread_mutex_t *lock = static_cast<pthread_mutex_t *>(args); // 接收锁对象指针
      while(true){
        usleep(1000);
        pthread_mutex_lock(lock);
        if(g_val>0){ // 判断为假退出线程
          g_val--;
        }
        else{
          break;
            // break 跳出循环随后退出线程
        }
      }
      printf("I am Thread-%d , quit ... \n",(int)pthread_self()%1000); // 打印确保线程已经退出
      // 退出前未解锁互斥锁
      return nullptr;
    }
    
    int main() {
      vector<pthread_t> tids;
      pthread_mutex_t lock;
      pthread_mutex_init(&lock, nullptr);
      for (int i = 0; i < NUM; ++i) {
        pthread_t tid;
        pthread_create(&tid, nullptr, ThreadRoutine, &lock);
        tids.push_back(tid);
      }
    
      for (size_t i = 0; i < tids.size(); ++i) {
        if(pthread_join(tids[i], nullptr)){
          cout<<"join fail"<<endl;
        }
      }
      pthread_mutex_destroy(&lock);
      return 0;
    }
    

    该段代码中创建两个线程并且同时对值为1g_val全局变量进行访问;

    当一个线程拿到锁后进行判断,判断为假break跳出循环并带锁退出;

    运行结果为:

    $ ./mythread 
    I am Thread-368 , quit ... 
    
    
    
    ^C
    

    发生死锁,其余线程仍在等待,可使用shell脚本观察并再次执行程序:

    $ while : ; do ps -aL | head -1 && ps -aL |  grep mythread ; echo "------------------------------------" ; sleep 1 ;done
    
      PID   LWP TTY          TIME CMD
    25909 25909 pts/0    00:00:00 mythread
    25909 25910 pts/0    00:00:00 mythread
    25909 25911 pts/0    00:00:00 mythread
    ------------------------------------
      PID   LWP TTY          TIME CMD
    25909 25909 pts/0    00:00:00 mythread
    25909 25910 pts/0    00:00:00 mythread
    25909 25911 pts/0    00:00:00 mythread
    ------------------------------------
      PID   LWP TTY          TIME CMD
    25909 25909 pts/0    00:00:00 mythread
    25909 25911 pts/0    00:00:00 mythread
    ------------------------------------
      PID   LWP TTY          TIME CMD
    25909 25909 pts/0    00:00:00 mythread
    25909 25911 pts/0    00:00:00 mythread
    ------------------------------------
    

    结果可以看出包括主线程共有3个线程,一个带锁线程先行退出,在退出时未解锁导致其他线程持续等待互斥锁资源

  • 持有锁的线程再次申请锁

    以上述代码为基础进行修改:

    #define NUM 2
    
    int g_val = 0;
    
    void *ThreadRoutine(void *args) {
      pthread_mutex_t *lock = static_cast<pthread_mutex_t *>(args);
      while (true) {
        usleep(1000);
        pthread_mutex_lock(lock); // 第一次锁定互斥锁
        pthread_mutex_lock(lock); // 第二次锁定互斥锁
        if (g_val > 0) {
          g_val--;
        } else {
          break;
        }
      }
      printf("I am Thread-%d , quit ... \n", (int)pthread_self() % 1000);
      return nullptr;
    }
    
    int main() {
      vector<pthread_t> tids;
      pthread_mutex_t lock;
      pthread_mutex_init(&lock, nullptr);
      for (int i = 0; i < NUM; ++i) {
        pthread_t tid;
        pthread_create(&tid, nullptr, ThreadRoutine, &lock);
        tids.push_back(tid);
      }
    
      for (size_t i = 0; i < tids.size(); ++i) {
        sleep(1);
        if (pthread_join(tids[i], nullptr)) {
          cout << "join fail" << endl;
        }
      }
      pthread_mutex_destroy(&lock);
      return 0;
    }
    

    配合shell脚本观察运行程序,结果如下:

    # 程序所在会话
    $ ./mythread 
    
    
    ^C
    
    # shell 脚本所在会话
      PID   LWP TTY          TIME CMD
    25940 25940 pts/0    00:00:00 mythread
    25940 25941 pts/0    00:00:00 mythread
    25940 25942 pts/0    00:00:00 mythread
    ------------------------------------
      PID   LWP TTY          TIME CMD
    25940 25940 pts/0    00:00:00 mythread
    25940 25941 pts/0    00:00:00 mythread
    25940 25942 pts/0    00:00:00 mythread
    ------------------------------------
    ...
    

    结果为线程持续等待,无法退出引发死锁;

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dio夹心小面包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值