LINUX系统编程:多线程互斥

news2024/11/19 9:26:12

目录

1.铺垫

2.线程锁接口的认识

静态锁分配

动态锁的分配

互斥量的销毁

互斥量加锁和解锁

3.加锁版抢票

4.互斥的底层实现


1.铺垫

先提一个小场景,有1000张票,现在有4个进程,这四个进程疯狂的去抢这1000张票,看看会发生什么呢?

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"


class ticket
{
public:
static int tickets;//总共的票数
ticket(std::string &name)
:_name(name)
{}

std::string &name()
{
    return _name;
}
   int _count = 0;//抢到多少票
   std::string _name;//线程的名字
};

int ticket::tickets = 1000;

void handler(ticket *t)
{
    while(true)
    {
        if(t->tickets > 0)
        {
        usleep(10000);
        std::cout<< t->name()<<"tickets-garbbing ticket:"<<t->tickets<<std::endl;
        t->tickets--;
        t->_count++;
        }
        else
        {
            break;
        }
    }
}

using namespace mythread;//自己封装的线程库
int count = 4;
int main()
{
    std::vector<thread<ticket*>> threads;
    // 创建一批线程
    std::vector<ticket*> data;
    for (int i = 0; i < count; i++)
    {
       
        std::string name = "thread" + std::to_string(i);
        ticket *t = new ticket(name);
        data.push_back(t);
        threads.emplace_back(handler, t, name);
    }

    //启动一批线程
    for(auto &t : threads)
    {
        t.start();
    }

    //等待一批线程
    for(auto &t : threads)
    {
        std::cout <<t.name() <<" wait sucess "<<std::endl;
        t.join();
    }

    //查看结果
    for(auto p : data)
    {
        std::cout << p->_name<<" get tickets "<<p->_count<<std::endl;
         sleep(1);
    }

    return 0;
}


我们发现这四个线程竟然把票数抢到负数了,代码中已经判断if(t->tickets > 0)为什么票数还会减为0呢?

假设当前tickets只剩下1时。

thread0进行判断,thread0发现票数是大于0的,他就会进入循环,但是这个时候thread0的时间片到了,thread0进入等待队列。

thread1开始执行,thread1进行判断,thread1发现票数也是大于0的,进入循环,这个时候hread1的时间片到了,thread1进入等待队列。

thread2和thread3同样。

当cpu再次调度到thread0的时候,thread0对thickets--, thickets  = 0.

调度到thread1的时候,thread1对thickets--,tickets = -1.

thread2和thread3同样。

这也就解释了,为什票会抢到负数,究其原因就是我们抢票+判断的操作不是原子的,所以我们要通过互斥锁把这两个操作编程"原子"的,这个原子是在线程看来是原子的,不是真正意义上的原子。

也可以理解为把线程并行抢票,变成串行抢票,因为锁只有一把,一次只能有一个线程抢票

2.线程锁接口的认识

线程锁有两种分配方法,静态全局锁和局部锁

静态锁分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

动态锁的分配

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

mutex:是要初始化的互斥量。

attr: nullptr。

互斥量的销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:1.使用PTHREAT_MUTEX_INITIALIZER初始化的静态锁不用销毁。

           2.互斥量加锁了,就不要销毁了。

           3.销毁的互斥量,就不要在加锁了。

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值 : 成功返回 0, 失败返回错误码。

注意:lock的时候会有两种情况,一种是lock成功返回0。

          另一种是互斥量已经被lock,这时候该线程会阻塞等待。

3.加锁版抢票

静态锁板

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义一个全局锁

class ticket
{
public:
    static int tickets; // 总共的票数
    ticket(std::string &name)
        : _name(name)
    {
    }

    std::string &name()
    {
        return _name;
    }
    int _count = 0;    // 抢到多少票
    std::string _name; // 线程的名字
};

int ticket::tickets = 1000;

void handler(ticket *t)
{
    // pthread_mutex_lock(&mutex);不能在这里上锁,在这里上锁,一个线程就把票抢完了
    while (true)
    {
        pthread_mutex_lock(&mutex);
        if (t->tickets > 0)
        {
            usleep(10000);
            std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;
            t->tickets--;
            t->_count++;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
}

using namespace mythread; // 自己封装的线程库
int count = 4;
int main()
{
    std::vector<thread<ticket *>> threads;
    // 创建一批线程
    std::vector<ticket *> data;
    for (int i = 0; i < count; i++)
    {

        std::string name = "thread" + std::to_string(i);
        ticket *t = new ticket(name);
        data.push_back(t);
        threads.emplace_back(handler, t, name);
    }

    // 启动一批线程
    for (auto &t : threads)
    {
        t.start();
    }

    // 等待一批线程
    for (auto &t : threads)
    {
        sleep(1);
        std::cout << t.name() << " wait sucess " << std::endl;
        t.join();
    }

    // 查看结果
    for (auto p : data)
    {
        std::cout << p->_name << " get tickets " << p->_count << std::endl;
        sleep(1);
    }

    return 0;
}

动态锁板

在主函数定义一个局部锁

然后在ticket类中,增加一个互斥量,这个互斥量是要加引用的,为了所有的线程都能看见同一个锁。

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"


class ticket
{
public:
    static int tickets; // 总共的票数
    ticket(std::string &name, pthread_mutex_t &mutex)
        : _name(name), _mutex(mutex)
    {
        pthread_mutex_init(&mutex, nullptr);
    }

    std::string &name()
    {
        return _name;
    }
    int _count = 0;    // 抢到多少票
    std::string _name; // 线程的名字
    pthread_mutex_t &_mutex;//让所有的线程看到同一个锁
};

int ticket::tickets = 1000;

void handler(ticket *t)
{
    // pthread_mutex_lock(&mutex);不能在这里上锁,在这里上锁,一个线程就把票抢完了
    while (true)
    {
        pthread_mutex_lock(&t->_mutex);
        if (t->tickets > 0)
        {
            usleep(10000);
            std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;
            t->tickets--;
            t->_count++;
            pthread_mutex_unlock(&t->_mutex);
        }
        else
        {
            pthread_mutex_unlock(&t->_mutex);
            break;
        }
    }
}

using namespace mythread; // 自己封装的线程库
int count = 4;
int main()
{
    std::vector<thread<ticket *>> threads;
    // 创建一批线程
    pthread_mutex_t mutex;
    std::vector<ticket *> data;
    for (int i = 0; i < count; i++)
    {
        std::string name = "thread" + std::to_string(i);
        ticket *t = new ticket(name, mutex);
        data.push_back(t);
        threads.emplace_back(handler, t, name);
    }

    // 启动一批线程
    for (auto &t : threads)
    {
        t.start();
    }

    // 等待一批线程
    for (auto &t : threads)
    {
        sleep(1);
        t.join();
        std::cout << t.name() << " wait sucess " << std::endl;

    }

    // 查看结果
    for (auto p : data)
    {
        std::cout << p->_name << " get tickets " << p->_count << std::endl;
        sleep(1);
    }

    return 0;
}

运行结果

票是不会抢到负数了,但是出现了个问题。

为什么有的线程一个票也没抢到?

这个是因为不同的线程竞争能力不同,竞争能力强的就可以一直抢到锁,而竞争能力不强的就只能等待。

这个需要是用条件变量解决,下次介绍。

4.互斥的底层实现

互斥的底层是依赖swap 和exchange这两条指令的,这两条指令是原子的。

正常交换两个变量都需要,定义一个临时变量。

但是swap 和exchange这两条指令不用,可以直接交换,cpu寄存和内存的内容进行交换。

将lock和unlock的过程转化为伪代码(粗略只为了解原理)。

假设内存中存在一个mutex锁,mutex = 1时是解锁状态,mutex = 0是上锁状态 

我们发现1只有一个,哪个线程拿到1,哪个线程能继续执行代码,否则就要挂起等待。

重要的话

放在内存中的数据是所有线程共享的,但是一旦被加载到cpu中,就变成cpu的上下文数据,变成了线程私有的数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1882177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

熊猫烧香是什么?

熊猫烧香&#xff08;Worm.WhBoy.cw&#xff09;是一种由李俊制作的电脑病毒&#xff0c;于2006年底至2007年初在互联网上大规模爆发。这个病毒因其感染后的系统可执行文件图标会变成熊猫举着三根香的模样而得名。熊猫烧香病毒具有自动传播、自动感染硬盘的能力&#xff0c;以及…

简单爬虫案例——爬取快手视频

网址&#xff1a;aHR0cHM6Ly93d3cua3VhaXNob3UuY29tL3NlYXJjaC92aWRlbz9zZWFyY2hLZXk9JUU2JThCJTg5JUU5JTlEJUEy 找到视频接口&#xff1a; 视频链接在photourl中 完整代码&#xff1a; import requestsimport re url https://www.kuaishou.com/graphql cookies {did: web_…

C语言 | Leetcode C语言题解之第207题课程表

题目&#xff1a; 题解&#xff1a; bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize) {int** edges (int**)malloc(sizeof(int*) * numCourses);for (int i 0; i < numCourses; i) {edges[i] (int*)malloc(0);…

一点连接千家银行,YonSuite让“银企对账”一键确认

在当今数智化浪潮下&#xff0c;成长型企业面临着前所未有的机遇与挑战。特别是在与银行的对接以及银企对账等方面&#xff0c;传统的手动操作模式已难以满足企业高效、安全的金融管理需求。用友YonSuite作为一款全场景SaaS应用服务&#xff0c;凭借其强大的银企直联功能&#…

【C++ 初阶路】--- C++内存管理

目录 一、C/C内存分布二、C内存管理方式2.1 new/delete操作内置类型2.2 new和delete操作自定义类型 三、operator new与operator delete函数四、new和delete的实现原理4.1 内置类型4.2 自定义类型 一、C/C内存分布 int globalVar 1; static int staticGlobalVar 1; void Tes…

树立行业标杆,林清轩获“以油养肤开创者”市场地位认证

从0到1的创造&#xff0c;才能快速实现从1到100的裂变&#xff0c;这是亘古不变的商业逻辑。 6月25日&#xff0c;知名美妆国货品牌林清轩&#xff0c;获得了CIC灼识的市场地位确认书&#xff0c;确定“以油养肤开创者” 的地位。 近两年&#xff0c;以油养肤的概念逐渐兴起&am…

【Python】入门Python,你必须了解这些事

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言一、认识Python什么是Python&#xff1f;!Python的起源Python的特点简洁易读跨平台&#xff0c;可移植拥有强大的库和框架面向对象 Python的优缺点优点缺点 Python的应用环境扩…

【第六节】C/C++静态查找算法

目录 前言 一、搜索查找 二、查找算法 1. 线性查找&#xff08;Linear Search&#xff09; 2. 二分查找&#xff08;Binary Search&#xff09; 3. 插值查找&#xff08;Interpolation Search&#xff09; 4. 哈希查找&#xff08;Hash Search&#xff09; 5. Fibonacc…

气膜足球馆需要投资多少—轻空间

随着足球运动的普及和人们对健康生活方式的追求&#xff0c;建设高质量的足球场地成为许多城市和社区的需求。在众多建设方案中&#xff0c;气膜足球馆因其独特的优势&#xff0c;逐渐成为一种受欢迎的选择。轻空间将探讨建设气膜足球馆所需的投资情况&#xff0c;并分析其成本…

InnoDB 表空间2---系统表空间

系统表空间 了解完了独立表空间的基本结构&#xff0c;系统表空间的结构也就好理解多了&#xff0c;系统表空间的结构和独立表空间基本类似&#xff0c;只不过由于整个MySQL进程只有一个系统表空间&#xff0c;在系统表空间中会额外记录一些有关整个系统信息的页&#xff0c;所…

MySQL之主从同步、分库分表

1、主从同步的原理 MySQL主从复制的核心是二进制日志 二进制日志&#xff08;binlog&#xff09;记录了所有DDL语句和DML语句&#xff0c;但不包括数据查询&#xff08;select、show&#xff09;语句。 1.1、复制分三步 master主库在事务提交时&#xff0c;会把数据变更记录…

干货分享:Spring中经常使用的工具类(提示开发效率)

环境&#xff1a;Spring5.3…30 1、资源工具类 ResourceUtils将资源位置解析为文件系统中的文件的实用方法。 读取classpath下文件 File file ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX "logback.xml") ; // ...读取文件系统文件 file Resou…

淘系-万相台无界实操运营课:淘系 付费工具课(40节课)

课程目录 01_万相台无界系统性忖费推广思维.mp4 02_万相台无界七大推广场景详解.mp4 03关键词推广计划之标准计划搭建技巧.mp4 04_关键词推广之智能计划推广技巧.mp4 05_关键词推广之趋势选品计划推广技巧.mp4 06关键词推广之智能选品计划推广技巧.mp4 07_非标品的关键词…

做好准备了吗?智能手机又一轮涨价潮来了……

还记得十一二年前&#xff0c;智能手机刚大力普及的时候&#xff0c;以小米、魅族为首的新势力品牌&#xff0c;快速打破三星、苹果及HTC们的高价门槛&#xff0c;将旗舰产品的价格快速压到了两千价位。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 一时间&#xff0c;手机市场快速完成了…

鸿蒙:路由Router原理

页面路由&#xff1a;在应用程序中实现不同页面之间的跳转和数据传递 典型应用&#xff1a;商品信息返回、订单等多页面跳转 页面栈最大容量为32个页面&#xff0c;当页面需要销毁可以使用router.clear()方法清空页面栈 router有两种页面跳转模式&#xff1a; router.pushUrl…

压缩算法LZ4

LZ4简介 LZ4 是无损压缩算法&#xff0c;提供每个核 大于 500 MB/s 的压缩速度&#xff0c;可通过多核 CPU 进行扩展。LZ4算法解压速度极快&#xff0c;单核解压速度达到GB/s&#xff0c;通常达到多核系统的 RAM 速度限制。 压缩速度可以动态调整&#xff0c;选择一个“加速”…

VLOOKUP函数在表格的简单运用-两个表匹配

1.什么是VLOOKUP&#xff1f; VLOOKUP是Excel中的一个内置函数&#xff0c;主要用于在区域或表格的首列查找指定的值&#xff0c;并返回该行中其他列的值。它特别适用于跨表格数据匹配 2.函数运用 2.1.这边两个表取名a表和b表&#xff0c;做为我们的实例表。 表格a包含&…

windows环境下创建python虚拟环境

windows环境下创建python虚拟环境 使用virtualenv库创建虚拟环境&#xff0c;可使不同的项目处于不同的环境中 安装方法&#xff1a; pip install virtualenv -i https://pypi.tuna.tsinghua.edu.cn/simple pip install virtualenvwrapper-win -i https://pypi.tuna.tsinghua…

git 还原被删除的分支

在多人项目开发中&#xff0c;有一次碰到忘记合并到master分支了&#xff0c;直接就把开发分支给删除了&#xff0c;现在记录下怎么还原被删除的分支 必须保证删除的分支之前已经被推送到了远程仓库 # 找出被删除分支的最后一个提交的哈希值 git reflog show# 找到提交哈希值…

【数据结构】(C语言):队列

队列&#xff1a; 线性的集合。先进先出&#xff08;FIFO&#xff0c;first in first out&#xff09;。两个指针&#xff1a;头指针&#xff08;指向第一个进入且第一个出去的元素&#xff09;&#xff0c;尾指针&#xff08;指向最后一个进入且最后一个出去的元素&#xff0…