首页 > 系统相关 >LINUX系统编程:多线程互斥

LINUX系统编程:多线程互斥

时间:2024-06-30 19:57:22浏览次数:3  
标签:std name int pthread 互斥 线程 LINUX mutex 多线程

目录

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,哪个线程能继续执行代码,否则就要挂起等待。

标签:std,name,int,pthread,互斥,线程,LINUX,mutex,多线程
From: https://blog.csdn.net/W2155/article/details/140081119

相关文章

  • 2,linux服务器使用学习
    目录服务器使用-SSH介绍使用OpenSSH-LinuxFinalShell-Windows阿里云服务器使用示例领取免费账号进行登录服务器使用-SSH介绍SecureShell(SSH)是由IETF(TheInternetEngineeringTaskForce)制定的建立在应用层基础上的安全网络协议。它是专为远程登录会话(甚至......
  • linux安装中文字体
    1.从windows复制宋体字体2.linux系统下/usr/share/fonts3.创建simsun路径,将字体文件放进去4.改一下字体权限cd/usr/share/fonts/sudochmod-Rmyfonts7555.安装依赖yuminstallmkfontscaleyuminstallfontconfig6.执行以下命令mkfontscalemkfontdirfc-cache......
  • Linux系统之部署linkding书签管理器
    Linux系统之部署linkding书签管理器一、linkding介绍1.1linkding简介1.2linkding特点二、本地环境介绍2.1本地环境规划2.2本次实践介绍三、检查本地环境3.1检查本地操作系统版本3.2检查系统内核版本四、部署Node.js环境4.1下载Node.js......
  • 多线程详解
    多线程详解1.线程简介任务,进程,线程,多线程多任务吃饭的时候玩手机,,,现实之中大多这样同时做多件事情的例子,看起来是多个任务都在做,其实本质上我们的大脑只做了一件事多线程原来是一条路,慢慢因为车多了,道路堵塞,效率极低,为了提高使用的效率,能够充分利用道路,于是加了多个车道。......
  • Linux如何远程访问?
    远程访问是现代计算机网络中非常重要的一个功能,它允许用户通过网络连接到远程计算机,并在远程计算机上执行操作。对于使用Linux操作系统的用户来说,Linux远程访问是非常常见的需求。本文将介绍如何实现Linux远程访问,并简要介绍一款名为【天联】的组网产品。Linux远程访问的基础......
  • linux 内存映射 与 内存共享
     一,内存映射对于磁盘文件和进程:将一个文件或其它对象映射到进程地址空间,实现文件在磁盘的存储地址和进程地址空间中一段虚拟地址的映射关系。有了这样的映射,进程利用指针直接读写虚拟地址就可以完成对文件的读写操作。这样可以避免进行read/write函数操作。文件的内存映射示......
  • SUSE linux的启动过程介绍
    引导Linux系统涉及不同的组件和任务。在固件和硬件初始化过程(取决于机器的架构)之后,内核通过引导加载程序GRUB2启动。此后,引导过程完全由操作系统控制并由systemd处理。systemd提供了一组“target”,用于为日常使用、维护或紧急情况启动配置。1术语init有两种不同的进程会......
  • linux笔记10--编辑器之神VIM
    文章目录1.简单介绍①为什么叫vim②linux常见的编辑器③注意事项④其它2.操作模式的划分①两种--国际上普通模式(命令操作模式)插入模式②三种--国内普通模式如何进入与退出界面插入模式如何进入与退出界面命令模式如何进入与退出界面常见的命令模式③......
  • linux三剑客工具使用及硬盘知识介绍
    文本处理工具,文件查找工具,文本处理三剑客,文本格式化命令(printf)的相关命令及选项,示例。文本处理工具cat:连接文件并打印到标准输出。catfile1file2tac:反向连接文件并打印到标准输出。tacfilenl:给文件的每一行添加行号。nlfilemore:分页显示文件内容。morefilele......
  • linux基础知识
    总结计算机发展相关,并且总结服务器硬件相关知识计算机发展相关计算机的发展可以分为以下几个阶段:第一代计算机(1940s-1950s):使用电子管作为主要元件。体积庞大,功耗高。主要用于科学计算和军事用途。代表:ENIAC、UNIVAC。第二代计算机(1950s-1960s):使用晶体管代替电子管。体......