当前位置: 首页 > article >正文

《多线程基础之互斥锁》

【互斥锁导读】互斥锁是大家使用最多的线程同步手段,但仅仅知道怎么用还是不够的?比如:面试官问你"互斥锁是属于内核层还是应用层的同步保护机制?性能怎样?","频繁加解锁,会有什么问题?","死锁了,应该如何排查?"

     

      和锁相关的api,我就不介绍了,同学们只需要参考下锁使用的相关示例,就能很好掌握它的用法,好!现在直接上代码。 

#include <pthread.h>
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
using namespace std;

pthread_mutex_t myMutex;
int resourceNo = 0;

void* workthread(void* param)
{
   pthread_t threadNo = pthread_self();
   while(true)
   {
     pthread_mutex_lock(&myMutex);
     cout << "thread No: " << threadNo << " resource No: " << ++resourceNo << endl;
     pthread_mutex_unlock(&myMutex);
     sleep(1);
   }
    return NULL;   
}

int main()
{
   cout << getpid() << endl;
    pthread_mutexattr_t mutexAttr;
   pthread_mutexattr_init(&mutexAttr);
   //设置互斥锁的类型,PTHREAD_MUTEX_ERRORCHECK表明是检错锁,
   //如果加锁失败,接口会返回对应的错误码
   pthread_mutexattr_settype(&mutexAttr,         
     PTHREAD_MUTEX_NORMAL | PTHREAD_MUTEX_ERRORCHECK);
   pthread_mutex_init(&myMutex, &mutexAttr);

   //创建5个线程
   pthread_t threadID[5];
   for (int i = 0; i < 5; ++i)
      pthread_create(&threadID[i], NULL, workthread, NULL);

   for (int i = 0; i < 5; ++i)
      pthread_join(threadID[i], NULL);
   
   //销毁锁资源
   pthread_mutex_destroy(&myMutex);
   pthread_mutexattr_destroy(&mutexAttr);
   return 0; 
}
 

    运行结果如下,5个线程分别去争抢全局的锁对象myMutex,进而保证resourceNo的递增符合线程安全的要求。

图片

    互斥锁使用起来很轻便,但是其实是有性能问题的,因为一个线程去争取一把锁对象,势必要从用户态切换到内核态,释放锁资源后又从内核态切回用户态。其性能相对于信号量、自旋锁、临界区(windows)这些线程同步机制更低。

1、死锁问题

    好,我们稍微修改下上述的示例代码,让程序运行的流程出现死锁,我们可以关注下进程如果出现了死锁,会有怎样的表现

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <iostream>
using namespace std;

pthread_mutex_t myMutexA;
pthread_mutex_t myMutexB;
int resourceBNo = 0;
int resourceANo = 0;

void* workthreadA(void* param)
{
   pthread_t threadNo = pthread_self();
   while(true)
   {
	pthread_mutex_lock(&myMutexB);
	cout << "thread A: " << threadNo << " resource B No: " << ++resourceBNo << endl;
	//线程A占用锁B,不释放锁,随后去争夺锁A
	
	pthread_mutex_lock(&myMutexA);
        cout << "thread A" << " resource A No: " << ++resourceANo << endl;
        pthread_mutex_unlock(&myMutexA);
   }
   return NULL;   
}

void* workThreadB(void* param)
{
   pthread_t threadNo = pthread_self();
   while (true)
   {
	pthread_mutex_lock(&myMutexA);
       cout << "thread B: " << threadNo << " resource A No: " << ++resourceANo << endl;
  	
  	//线程B占用锁A,不释放,随后去争夺锁B
  	pthread_mutex_lock(&myMutexB);
        cout << "thread B " << " resource B No: " << ++resourceBNo << endl;
        pthread_mutex_unlock(&myMutexB);
   }
   return NULL;
}
  我们模拟死锁的场景,用gdb调试程序lock,果不其然,lock进程(进程号为9394,通过getpid()获取)卡住了,两个子线程分别争夺对方占用的锁。

图片

   使用top指令查看进程9394的状态如下:

图片

      但进程的状态是Sleep(任务字段S下面对应的值),让人有点意外,难道陷入死锁的进程,进程会陷入睡眠的状态?而且当前进程CPU消耗为0,物理内存占比也为0。看到线程最后卡死在__lll_lock_wait接口处,也就是说pthread_mutex_lock接口最终会走到__lll_lock_wait()接口处。
 

/* This function doesn't get included in libc.  */
#if IS_IN (libpthread)
void __lll_lock_wait (int *futex, int private)
{
  if (*futex == 2)
    lll_futex_wait(futex, 2, private); /* Wait if *futex == 2.*/

  while (atomic_exchange_acq (futex, 2) != 0)
    lll_futex_wait (futex, 2, private); /* Wait if *futex == 2.*/
}
#endif

/* Wait while *FUTEXP == VAL for an lll_futex_wake call on FUTEXP.  */
#define lll_futex_wait(futexp, val, private) \
      lll_futex_timed_wait (futexp, val, NULL, private)

#define lll_futex_timed_wait(futexp, val, timeout, private)  \
   lll_futex_syscall (4, futexp, \
      __lll_private_flag (FUTEX_WAIT, private), val, timeout)

       初步看了下源码,__lll_lock_wait会陷入无限循环的等待中去,因为最终传递给lll_futex_timed_wait接口的超时时间为NULL,直到和锁相关的futex状态变成预定值,也即其它线程释放了这把锁,更改了futex的状态。当前线程才会停止休眠等待,继续执行。

2、频繁加锁问题

    好,讨论下最后一个问题,频繁加锁会有什么问题,我们可以先用个小程序演示下。

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int main()
{
      pthread_mutex_t mymutex;
      pthread_mutexattr_t mutex_attr;
      pthread_mutexattr_init(&mutex_attr);
      pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_NORMAL);
      pthread_mutex_init(&mymutex, &mutex_attr);
      int ret = pthread_mutex_lock(&mymutex);
      printf("ret = %d\n", ret);
      ret = pthread_mutex_lock(&mymutex);
      printf("ret = %d\n", ret);
      pthread_mutex_destroy(&mymutex);
      pthread_mutexattr_destroy(&mutex_attr);
      return 0;
}

       针对普通的锁,反复对同一把锁进行加锁操作,linux下的表现就是阻塞当前进程或者线程,程序卡死__lll_lock_wait()接口处。

图片

       windows下呢?是会卡死吗?笔者可以先剧透下,windows下频繁加解锁,会引起crt库崩溃,中断当前进程,感兴趣的同学可以去实验下。

      所以锁底层的知识面还是很细的,需要深入剖析底层的源码,分析加解锁的流程,那么遇到锁一类的问题就能迎难而解了。

    


http://www.kler.cn/a/525128.html

相关文章:

  • HttpClient学习
  • 2025 春节联欢晚会魔术揭秘
  • 芯片AI深度实战:基础篇之langchain
  • PostgreSQL 数据备份与恢复:掌握 pg_dump 和 pg_restore 的最佳实践
  • 在无sudo权限Linux上安装 Ollama 并使用 DeepSeek-R1 模型
  • 可爱狗狗的404动画页面HTML源码
  • Java基础知识-第14章-Java注解
  • 上位机知识篇---Linux源码编译安装链接命令
  • web ssti注入
  • 《Operating System Concepts》阅读笔记:p1-p1
  • 基于Springboot的智能学习平台系统【附源码】
  • 让远程也能访问家里的电脑——frp反代
  • Elasticsearch 自定义分成器 拼音搜索 搜索自动补全 Java对接
  • 多线程执行大批量数据查询
  • 手写instanceof、手写new操作符
  • 多头潜在注意力(MLA):让大模型“轻装上阵”的技术革新——从DeepSeek看下一代语言模型的高效之路
  • python-leetcode-反转链表 II
  • vulfocus/thinkphp:6.0.12 命令执行
  • go-zero学习笔记(二)
  • Pyside的QWebEngineProfile类
  • OpenLayers知识总结1
  • 在Putty创建php文件
  • 安卓通过网络获取位置的方法
  • 透视B/S架构与C/S架构:构建未来网络应用的智慧选择
  • C27.【C++ Cont】时间、空间限制和STL库的简单了解
  • 跨境电商代购系统独立站深度分享