Linux——多线程(二)

在上一篇博客中我们已经介绍到了线程控制以及对应的函数调用接口,接下来要讲的是真正的多线程,线程安全、线程互斥、同步以及锁。

一、多线程

简单写个多线程的创建、等待的代码 

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

const int threadnum = 5;
void *handlerTask(void *args)
{
    string name = static_cast<char*>(args);
    while(true)
    {
        sleep(1);
        cout<<"I am "<<name<<endl;
    }
    return nullptr;
}

int main()
{
    vector<pthread_t> threads;
    for(int i=0;i<threadnum;i++)
    {
        char threadname[64];
        snprintf(threadname,64,"thread-%d",i+1);
        pthread_t tid;
        sleep(1);
        pthread_create(&tid,nullptr,handlerTask,threadname);//多线程创建
        threads.push_back(tid);
    }
    for(auto &tid:threads)
    {
        pthread_join(tid,nullptr);//等待
    }
    return 0;
}

 

用一下之前的接口实现多线程的抢票功能

thread.hpp 

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<functional>
#include<pthread.h>

namespace Thread_Module
{
    template <typename T>
    using func_t = std::function<void(T&)>;
    // typedef std::function<void(const T&)> func_t;

    template <typename T>
    class Thread
    {
    public:
        void Excute()
        {
            _func(_data);
        }
    public:
        Thread(func_t<T> func,T &data,const std::string &threadname = "none")
        :_threadname(threadname)
        ,_func(func)
        ,_data(data)
        {}
        static void* threadrun(void *args)//线程函数
        {
            Thread<T> *self = static_cast <Thread<T>*>(args);
            self->Excute();
            return nullptr;
        }
        bool Start()//线程启动!
        {
            int n = pthread_create(&_tid,nullptr,threadrun,this);
            if(!n)//返回0说明创建成功
            {
                _stop = false;//说明线程正常运行
                return true;
            }
            else
            {
                return false;
            }
        }
        void Stop()
        {
            _stop = true;
        }
        void Detach()//线程分离
        {
            if(!_stop)
            {
                pthread_detach(_tid);
            }
        }
        void Join()//线程等待
        {
            if(!_stop)
            {
                pthread_join(_tid,nullptr);
            }
        }
        std::string threadname()//返回线程名字
        {
            return _threadname;
        }
        ~Thread()
        {}
    private:
        pthread_t _tid;//线程tid
        std::string  _threadname;//线程名
        T &_data;//数据
        func_t<T> _func;//线程函数
        bool _stop; //判断线程是否停止 为true(1)停止,为false(0)正常运行

    };
}

 main.cc

#include <iostream>
#include <mutex>
#include "Thread.hpp"
#include<string>
#include<vector>


using namespace Thread_Module;
int g_tickets = 10000; // 共享资源,没有保护的

void route(int &tickets)
{
    while (true)
    {
        if(tickets>0)
        {
            usleep(1000);
            printf("get tickets: %d\n", tickets);
            tickets--;
        }
        else
        {
            break;
        }
    }
}

const int num = 4;
int main()
{
    // std::cout << "main: &tickets: " << &g_tickets << std::endl;

    std::vector<Thread<int>> threads;
    // 1. 创建一批线程
    for (int i = 0; i < num; i++)
    {
        std::string name = "thread-" + std::to_string(i + 1);
        threads.emplace_back(route, g_tickets, name);
    }

    // 2. 启动 一批线程
    for (auto &thread : threads)
    {
        thread.Start();
    }

    // 3. 等待一批线程
    for (auto &thread : threads)
    {
        thread.Join();
        std::cout << "wait thread done, thread is: " << thread.threadname() << std::endl;
    }

    return 0;
}

运行之后,可以看出抢多了票,票数怎么能抢到负数了

原因是发生了线程不安全问题

二、线程安全

抢票抢到负数造成的票数不一致本质是g_tickets是全局变量(无保护),对全局的g_tickets的判断不是原子的

为什么抢到负数?

可能是tickets已经等于1了,但是判断的时候,让多个线程进入抢票逻辑了(涉及到CPU调度),进入抢票逻辑后还执行了ticket--(等价于tickets = tickets - 1)

tickets--的本质:

  • 从内存读到CPU
  • CPU内部进行--操作
  • 数据写回内存

 tickets--操作不是原子的-----又引出一个问题------>什么是原子的?

当一条语句转成汇编代码只有一条汇编是,那么就可以说这条语句是原子的

那么如何解决这种数据不一致问题呢?

导致数据不一致的原因:共享资源没有被保护,多线程对该资源进行了交叉访问

而解决办法就是:对共享资源进行加锁

三、线程互斥(锁)

再引入几个概念

临界资源:多个执行流进行安全访问的共享资源 

临界区:访问临界资源的代码就叫做临界区

线程互斥:让多个线程串行访问共享资源,任何时候只能有一个执行流在访问共享资源

加锁的本质就是把并行执行转成串行执行

原子性:对一个资源进行访问的时候要么就不做,要么就做完

当一条语句转成汇编代码只有一条汇编是,那么就可以说这条语句是原子的

3.1加锁保护 

锁的相关系统调用:

锁的全局(静态)初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

man pthread_mutex_init(初始化锁)

 参数:

pthread_mutex_t *mutex:                                创建的互斥锁指针

const pthread_mutexattr_t *mutexattr:            锁的属性,一般设为nullptr

返回值:                                                          初始化成功返回0,失败返回错误码

man pthread_mutex_destroy(锁的销毁)

参数:

pthread_mutex_t *mutex:                                创建的互斥锁指针

返回值:                                                          销毁成功返回0,失败返回错误码

 man pthread_mutex_lock(加锁):

参数:

 pthread_mutex_t *mutex:                                互斥锁指针

返回值:                                                           加锁成功返回0,失败返回错误码

1

申请锁成功:函数就会返回,允许继续向后运行

申请锁失败:函数就会阻塞

函数调用失败:出错返回

  man pthread_mutex_unlock(解锁):

 

参数:

 pthread_mutex_t *mutex:                                还是互斥锁指针

返回值:                                                           解锁成功返回0,失败返回错误码

一般都是这么用的

//初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,nullptr);

//加锁
pthread_mutex_lock(&mutex);
//临界区——访问临界资源的代码写这里
//。。。
//访问完了解锁

pthread_mutex_unlock(&mutex);

 3.2解决数据不一致问题

有了以上的系统调用我们对抢票代码进行修改,对其进行加锁

.hpp 

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<functional>
#include<pthread.h>

namespace Thread_Module
{
    template <typename T>
    using func_t = std::function<void(T)>;
    // typedef std::function<void(const T&)> func_t;

    template <typename T>
    class Thread
    {
    public:
        void Excute()
        {
            _func(_data);
        }
    public:
        Thread(func_t<T> func,T data,const std::string &threadname = "none")
        :_threadname(threadname)
        ,_func(func)
        ,_data(data)
        {}
        static void* threadrun(void *args)//线程函数
        {
            Thread<T> *self = static_cast <Thread<T>*>(args);
            self->Excute();
            return nullptr;
        }
        bool Start()//线程启动!
        {
            int n = pthread_create(&_tid,nullptr,threadrun,this);
            if(!n)//返回0说明创建成功
            {
                _stop = false;//说明线程正常运行
                return true;
            }
            else
            {
                return false;
            }
        }
        void Stop()
        {
            _stop = true;
        }
        void Detach()//线程分离
        {
            if(!_stop)
            {
                pthread_detach(_tid);
            }
        }
        void Join()//线程等待
        {
            if(!_stop)
            {
                pthread_join(_tid,nullptr);
            }
        }
        std::string threadname()//返回线程名字
        {
            return _threadname;
        }
        ~Thread()
        {}
    private:
        pthread_t _tid;//线程tid
        std::string  _threadname;//线程名
        T _data;//数据
        func_t<T> _func;//线程函数
        bool _stop; //判断线程是否停止 为true(1)停止,为false(0)正常运行

    };
}

.cc


class ThreadData
{
public:
    
    ThreadData(int &tickets,const std::string &name,pthread_mutex_t &mutex)
    :_tickets(tickets)
    ,_name(name)
    ,_total(0)
    ,_mutex(mutex)
    {}
    ~ThreadData()
    {}
public:
    int &_tickets;//总票数的引用
    std::string _name;
    int _total;//线程单独抢的票数
    //std::mutex &_mutex;
    pthread_mutex_t &_mutex;
};
int g_tickets = 10000;//总票数
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;

void scramble(ThreadData *td)//抢票
{
    while(true)
    {
        //pthread_mutex_lock(&gmutex);
        pthread_mutex_lock(&td->_mutex);
        if(td->_tickets > 0)
        {
            usleep(1000);
            std::cout<<"线程:"<<td->_name.c_str()<<"正在抢票中..."<<" 抢到的票数是:"<<td->_total<<std::endl;
            td->_tickets--;
            // pthread_mutex_unlock(&gmutex);
            pthread_mutex_unlock(&(td->_mutex));

            td->_total++;
        }
        else
        {
            pthread_mutex_unlock(&(td->_mutex));
            //pthread_mutex_unlock(&gmutex);
            break;
        }
    }
}
const int num = 3; 

int main()
{
    
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,nullptr);
    std::vector<Thread<ThreadData*>> threads;
    std::vector<ThreadData *> datas;

    //创建一些线程
    for(int i=0;i<num;i++)
    {
        std::string name = "thread - "+std::to_string(i+1);
        ThreadData *td = new ThreadData(g_tickets,name,mutex);
        threads.emplace_back(scramble,td,name);
        datas.emplace_back(td);
    }
    //启动全部线程
    for(auto &thread:threads)
    {
        thread.Start();
    }

    //等待线程
    for(auto &thread:threads)
    {
        thread.Join();
    }

    for(auto data:datas)
    {
        std::cout<<"线程名字:"<<data->_name<<"以及此线程总共抢的票数:"<<data->_total<<std::endl;
        delete data;    
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

此时票数正好10000,结果正常

需要注意的是:

当一个线程从临界区出来并且释放锁后,执行后续任务这段时间,其它线程才有更大的概率去竞争锁

加锁时,要保证临界区的粒度非常小,那些不是必须放临界区内的代码就别放里面

3.3互斥

锁的封装

#include<iostream>
#include<pthread.h>

class LockGuard
{
public:
    LockGuard( pthread_mutex_t *mutex)
        :_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }

private:
    pthread_mutex_t *_mutex;
}

线程互斥:让多个线程以串行方式访问一段临界区代码 

之前使用锁的代码,这个锁必须被所有线程所看见,所以对于锁本身而言也是一个共享资源,那么谁来保证锁的安全性呢?通过加锁解锁都是原子的来保证锁自身的安全性 

互斥到底是啥?

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下

交换(xchgb)的本质:不是拷贝到寄存器,而是所有线程在竞争锁的时候只有一个1(这个1就是锁)

加锁过程中,xchgb是原子的,可以保证锁的安全

锁只能被一个线程所持有,而且由于xchgb汇编只有一条指令,即使申请锁的过程时CPU进行调度线程被切换也不影响。一旦一个线程通过交换那到了锁,它走的时候也就把锁带走了,其它线程只能等他用完把锁还回来。

 

CPU寄存器硬件只有一套,但是CPU寄存器内部的数据,就是线程的硬件上下文

数据在内存中,所有线程都能访问,属于共享的;但是如果转移到CPU内部寄存器中,就属于一个线程独有了

mutex简单理解就是一个0/1的计数器,用于标记资源访问状态:

  • 0表示已经有执行流加锁成功,资源处于不可访问,
  • 1表示未加锁,资源可访问。

但是还有另外的问题:加锁,线程竞争锁是自由竞争的,竞争能力强的线程会导致其他线程抢不到锁,访问不了临界资源导致其他线程一直阻塞,造成其它线程的饥饿问题

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【案例实操】银河麒麟桌面操作系统实例分享,V10SP1重启后网卡错乱解决方法

1.问题现象 8 个网口&#xff0c; 命名从 eth1 开始到 eth8。 目前在系统 grub 里面加了 net.ifnames0 biosdevname0 参数&#xff0c; 然后在 udev 规则中加了一条固定网卡和硬件 pci 设备号的规则文件。 最后在 rc.local 中加了两条重新安装网卡驱动的命令&#xff08; rmmod…

yolov10模块

yolov10模块 1 C2f2 C2fCIB2.1 CIB2.2 RepVGGDW 3 PSA4 SCDown5 v10Detect 论文代码&#xff1a;https://github.com/THU-MIG/yolov10 论文链接&#xff1a;https://arxiv.org/abs/2405.14458 Conv是Conv2dBNSiLU PW是Pointwise Convolution(逐点卷积) DW是Depthwise Convolut…

45页超干PPT:AGV技术详解

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 完整版文件和更多学习资料&#xff0c;请球友到知识星球【智能仓储物流技术研习社】自行下载 AGV&#xff08;Automated Guided Vehicle&#xf…

JVM的垃圾回收机制

目录 GC的工作范围 谁是垃圾 怎么判断&#xff0c;某个对象是否有引用指向捏&#xff1f; &#xff08;1&#xff09;引用计数 缺陷 释放垃圾的策略 &#xff08;1&#xff09;标记清除&#xff08;不实用&#xff09; &#xff08;2&#xff09;复制算法 &#xff08…

公网IP地址如何查询?

公网IP地址是指在互联网中可以被全球范围内的设备访问的IP地址。在网络通信中&#xff0c;公网IP地址扮演着重要的角色&#xff0c;它可以标识设备在互联网中的位置。查询公网IP地址是一种常见的网络管理需求&#xff0c;因为它能够提供网络设备的准确位置信息&#xff0c;方便…

首套真题解析!安徽211难度适中!两门课!

这个系列会分享名校真题。并做详细解析&#xff01;此为24年第一套&#xff01; 今天分享的是22年合肥工业856的信号与系统试题及解析。 小马哥Tips&#xff1a; 本套试卷难度分析&#xff1a;本套试题内容难度中等&#xff0c;里面较多的考察了信号与系统的知识&#xff0c…

[Python]用Qt6和Pillow实现截图小工具

本文章主要讲述的内容是&#xff0c;使用python语言借助PyQt6和Pillow库进行简单截图工具的开发&#xff0c;含义一个简单的范围裁剪和软件界面。 主要解决的问题是&#xff0c;在高DPI显示屏下&#xff0c;坐标点的偏差导致QWidget显示图片不全、剪裁范围偏差问题。 适合有一点…

基于 Redis 实现分布式锁的全过程

前言 这一篇文章拖了有点久&#xff0c;虽然在项目中使用分布式锁的频率比较高&#xff0c;但整理成文章发布出来还是花了一点时间。在一些移动端、用户量大的互联网项目中&#xff0c;经常会使用到 Redis 分布式锁作为控制访问高并发的工具。 一、关于分布式锁 总结&#x…

QT 信号和槽教程,窗体和控件对象之间的沟通一般都使用信号和槽

Qt的信号和槽&#xff08;Signals and Slots&#xff09;机制是一种强大的对象间通信方式&#xff0c;它允许对象在完全解耦的情况下相互通信。以下是关于Qt信号和槽的简明教程&#xff1a; 基本概念 信号&#xff08;Signal&#xff09;&#xff1a;信号是由Qt对象发出的通知…

OpenAI已全面开放自定义GPT以及文件上传等功能

今天&#xff0c;OpenAI兑现了前段时间做出的承诺&#xff1a;免费向所有用户开放GPT-4o。这意味着所有的免费用户都能使用自定义GPT模型、分析图表等其他GPT-4o新功能了。现在ChatGPT界面长这样&#xff1a; 可以看出&#xff0c;免费用户也能使用GPT store中定义好的模型&…

构建智慧监控系统的功能架构,保障安全与便利

智慧监控系统作为现代城市安全管理的重要工具&#xff0c;不仅能够提供有效的安防监控&#xff0c;还能为人们的生活带来更多的便利。本文将探讨智慧监控系统的功能架构&#xff0c;以实现安全和便利的双重目标。 ### 1. 智慧监控系统背景 随着城市化进程的加速&#xff0c;人…

构建高效便捷的家政平台系统——打造优质家政服务的关键

随着人们生活节奏的加快和工作压力的增大&#xff0c;家政服务的需求日益增长。为了满足这一需求&#xff0c;家政平台系统应运而生。本文将探讨家政平台系统的整体架构&#xff0c;以实现高效便捷的家政服务&#xff0c;打造优质家政体验。 ### 1. 家政平台系统背景 随着现代…

语音降噪算法库介绍

一.语音降噪技术方向介绍 软件上进行语音降噪目前主要是两个方向&#xff1a;传统降噪算法和AI降噪算法&#xff0c;他们各有千秋&#xff0c;目前看他们各有千秋&#xff0c;有各自适用场景。 推荐一个不错的人工智能学习网站&#xff0c;通俗易懂&#xff0c;内容全面&#…

vue3组件传值---vue组件通过属性,事件和provide,inject进行传值

通过属性传值&#xff08;父传子&#xff09; vue的组件具有props自建属性&#xff08;自定义名称&#xff0c;类似于class&#xff0c;id的属性&#xff09;&#xff0c;通过这个属性&#xff0c;父组件可以向子组件传递参数&#xff0c;从而实现组件之间的信息传递&#xff0…

SpringSecurity6从入门到实战之Filter过滤器回顾

SpringSecurity6从入门到实战之Filter过滤器回顾 如果没有SpringSecurity这个框架,我们应该通过什么去实现客户端向服务端发送请求时,先检查用户是否登录,登录了才能访问.否则重定向到登录页面 流程图如下 官方文档&#xff1a;https://docs.spring.io/spring-security/referen…

自动化办公01 smtplib 邮件⾃动发送

目录 一、准备需要发送邮件的邮箱账号 二、发送邮箱的基本步骤 1. 登录邮箱 2. 准备数据 3. 发送邮件 三、特殊内容的发送 1. 发送附件 2. 发送图片 3. 发送超文本内容 4.邮件模板内容 SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;即简单邮件传输协议…

单点11.2.0.3备份恢复到单点11.2.0.4

保命法则&#xff1a;先备份再操作&#xff0c;磁盘空间紧张无法备份就让满足&#xff0c;给自己留退路。 场景说明&#xff1a; 1.本文档的环境为同平台、不同版本&#xff08;操作系统版本可以不同&#xff0c;数据库小版本不同&#xff09;&#xff0c;源机器和目标机器部…

设计模式(二)工厂模式

文章目录 工厂模式简介简单工厂&#xff08;Simple Factory&#xff09;结构具体实现优缺点 工厂方法&#xff08;Factory Method&#xff09;结构具体实现优缺点 抽象工厂&#xff08;Abstract Factory&#xff09;结构具体实现优缺点 工厂模式简介 工厂模式是一种创建型模式…

[数据集][目标检测]焊接处缺陷检测数据集VOC+YOLO格式3400张8类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3400 标注数量(xml文件个数)&#xff1a;3400 标注数量(txt文件个数)&#xff1a;3400 标注…

能源SCI期刊,中科院4区,审稿快,IF=3.858

一、期刊名称 Frontiers in Energy Research 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;能源 影响因子&#xff1a;3.858 中科院分区&#xff1a;4区 三、期刊征稿范围 能源研究前沿出版了整个领域的严格同行评审研究&#xff0c;重点是可持续和环境…