2024年C C++最全Linux多线程(线程互斥与线程锁)_linux 线程锁(1),2024年最新真牛皮

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

注意,线程有自己独立的栈结构,所以临时变量不需要控制线程安全。

二、互斥与同步

要保证线程安全,就需要线程之间是互斥和同步的。下面介绍几个概念来引出互斥和同步的概念。

1.临界资源:凡是被线程共享访问的资源都是临界资源(多线程,多进程都有临界资源。比如多个进程向显示器打印数据,显示器就是临界资源)
2.临界区:代码中访问临界资源部分的代码。因此,对临界区的保护本质上就是对临界资源的保护。(通过互斥和同步实现)
3.互斥:在任意时刻,只允许一个执行流访问某段代码(访问某部分资源),称之为互斥!
4.原子性:一段代码要么不执行,要么执行完毕。称这段代码具有元祖性
5.同步:一般而言,让访问临界资源的过程在安全的前提下(互斥并且原子的),让访问的资源具有一定的顺序性。

三、线程安全问题的底层原因

(1)抢票逻辑

使用一段模拟抢票的代码来验证实现线程同步和互斥的必要性。

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
using namespace std;
int tickets=1000;
void\* ThreadRoutine(void\* args)
{
   int id=\*(int\*)args;
   delete (int\*)args;//接收线程的id
   while(true)
   {
      if(tickets>0)//对临界资源tickets进行操作
      {
         usleep(1000);
         cout<<"我是"<<id<<"我抢到的票是:"<<tickets<<endl;
         tickets--;
      }
      else
      {
         break;
      }
   }
}
int main()
{
   pthread\_t tid[5];
   for(int i=0;i<5;i++)
   {
      int\* id=new int(i);
      pthread\_create(tid+i,nullptr,ThreadRoutine,(void\*)id);//创建5个线程,这里传递id而不是i,因为毕竟传递的是地址,担心线程中有代码将i的值进行更改。
   }
   for(int i=0;i<5;i++)
   {
      pthread\_join(tid[i],nullptr);//线程等待
   }
   return 0;
}

我们希望这五个线程将这1000张票抢购一空(tickets–至0)。但是我们发现运行结果并不是我们想的那样tickets–到0,甚至有的线程抢到了负数票。
在这里插入图片描述
此时我们发现tickets虽然是临界资源,但是并没有线程的安全性来保证。
那么为什么会出现这种情况呢?

(2)底层原理

tickets作为临界资源,所有的线程都要对它进行判断ticket是否大于0,以及ticket–的操作。用ticket–操作举例,虽然他看起来是一行C语言的代码,但是实际上它的底层汇编经历了三个阶段,分别是load命令,减法命令,以及store命令。
由于线程是不断在切换的,因此一个线程在执行完load命令之后,很可能还没来得及做减法或者写回操作,就被切走了。CPU开始执行下一个线程。
在这里插入图片描述
当线程A被切走的时候,它会抱着它的临时数据,也就是还没来得及进行–操作的1000。此时线程B进来,假设它执行了–操作,并成功将tickets–到了10,并写回到了内存中。
过了一会,A线程带着它的临时数据1000回来了,它认为tickets的值还是原来的1000,执行–操作,将值变成了999,此时写回内存中。写回的过程中,就将原来B写回的数据进行了覆盖。B的–白白进行执行了。
因此我们发现,如果多个线程同时执行的话,这是一个相当混乱的状态。
我们还可以分析一下,出现负数的情况,当一个线程进行判断操作后发现tickets是大于0的(此时还没将tickets放入CPU中),突然线程被切换走了。另一个线程来了,并将tickets–到了0,此时再将原来的线程切换回来,它认为自己已经判断完tickets的大小了。拿到ticktes的值后直接就放入CPU中进行了–操作,因此出现了负数。
为了解决这一问题,我们引入了线程锁的概念。

四、线程锁

1.锁的使用

对于上述的问题,我们只需要保证在一个线程对tickets的操作的时候,其他线程不会对tickets进行操作。(注意,不是保证线程不会被切走。)
使用线程锁,我们需要了解一个类型,以及四个线程锁有关的函数。

(1)初始化和销毁

在这里插入图片描述
其中参数中的pthread_mutex_t就是一个锁的类型,我们使用它来定义一把锁。
函数pthread_mutex_init是用来初始化的函数,第一个参数是一个指针指向要初始化的锁,第二个参数是锁的属性,我们置为NULL即可。
函数pthread_mutex_destroy是销毁锁的函数,它的参数指向要销毁的锁。

(2)加锁和解锁

在这里插入图片描述
函数pthread_mutex_lock是加锁函数,pthread_mutex_unlock是解锁函数。
我们只需要在访问临界资源的区域(临界区),前进行加锁,在访问后进行解锁即可以保证在某个线程访问临界资源的时候,其他线程无法访问该资源。

2.抢票逻辑

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
#include<stdio.h>
using namespace std;
class Ticket
{
private:
   pthread_mutex_t mtx;//定义一把锁
   int tickets=1000;//定义票数
public:
   Ticket():tickets(1000)
   {
       pthread\_mutex\_init(&mtx,nullptr);//构造函数中,对锁进行初始化
   }
   bool GetTicket()
   {
      bool res=true;
      pthread\_mutex\_lock(&mtx);//访问临界资源tickets要进行加锁
      if(tickets>0)
      {
         usleep(1000);
         cout<<"我是"<<pthread\_self()<<"我抢到的票是:"<<tickets<<endl;
         tickets--;
      }
      else
      {
         res=false;
         cout<<"票被抢光了"<<endl;
         printf("");
      }
      pthread\_mutex\_unlock(&mtx);//访问结束,进行解锁
      return res;
   }
   ~Ticket()
   {
      pthread\_mutex\_destroy(&mtx);//析构函数中对锁进行销毁
   }   
};
void\* ThreadRoutine(void\* args)
{
   Ticket\* t=(Ticket\*)args;
   cout<<"我是线程"<<pthread\_self()<<endl;
   while(true)
   {
      if(t->GetTicket())
      {
         continue;
      }
      else
      {
         break;
      }
   }
}
int main()
{
   Ticket\* t=new Ticket();
   pthread_t tid[5];
   for(int i=0;i<5;i++)
   {
      pthread\_create(tid+i,nullptr,ThreadRoutine,(void\*)t);
   }
   for(int i=0;i<5;i++)
   {
      pthread\_join(tid[i],nullptr);
   }
   return 0;
}

为了实现这一过程,可以定义一个Ticket类,在其中定义ticket和一把锁。使用访问函数来帮助线程对锁进行访问,在临界区处进行加锁,解锁的操作。
此时再运行代码,我们发现是我们期望的抢票结果。
在这里插入图片描述
我们也可以使用C++提供的函数来进行锁的操作,需要包含头文件

mutex mymtx//定义锁
mymtx.lock();//加锁
mymtx.unlock;//解锁

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

u9-1715528658931)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值