【Linux —— 线程互斥】

1. 临界资源与临界区

  • 临界资源: 指的是多个线程或进程共享的资源,例如全局变量、文件、数据库等。由于这些资源的共享,可能会导致数据不一致或程序崩溃。
  • 临界区: 是指访问临界资源的代码段。 为了保护临界资源,必须控制对临界区的访问,确保在任何时刻只有一个线程或进程可以进入临界区。

2. 互斥的定义

 互斥是一种同步机制,旨在确保同一时刻只有一个执行流(线程或者进程)可以进入临界区。
互斥通常通过互斥量(mutex)来实现。 互斥量是一种锁,线程或进程在访问临界资源之前需要获取这个锁,完成后释放它。

3. 原子性

 原子性通俗讲就是指某个操作要么完全执行,要么就完全不执行,不会被其他的进程或线程打断。
原子性操作对于保证数据的一致性和安全性至关重要。比如:在抢票软件的抢票过程中,如果没有原子性则可能出现售出的票数大于总票数的情况。

4. 互斥量(Mutex)

  • 互斥量的使用:
    • 初始化: 只是用互斥量之前,必须对其进行初始化。可以使用pthread_mutex_init 函数来进行初始化。
    • 加锁: 线程或进程在进入临界区之前,需要调用pthread_mutex_lock 来获取互斥量的锁。如果锁已经被其他的线程占用了,当前线程将被阻塞,知道锁被释放。
    • 解锁: 完成对临界资源的访问之后,必须调用pthread_mutex_unlock 来释放互斥量的锁。以允许其他线程访问临界区。
    • 销毁: 在不再需要互斥量的时候,应该调用pthread_mutex_destroy来销毁互斥量,释放有关的资源。

5. 互斥的实现示例

下面简单使用自己封装的pthread库来进行演示:

//Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>

namespace ThreadMoudle
{
    typedef void (*func_t)(const std::string &name); // 函数指针

    class Thread
    {
    public:
        void Excute()
        {
            std::cout << _name << " is running ..." << std::endl;
            _isrunning = true;
            _func(_name);
            _isrunning = false;
        }

    public:
        Thread(std::string name, func_t func)
            : _name(name), _func(func)
        {
            std::cout << "create " << name << " done ..." << std::endl;
        }

        static void *ThreadRountine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);
            self->Excute();
            return nullptr;
        }
        bool Start()
        {
            int n = ::pthread_create(&_tid, nullptr, ThreadRountine, this);
            if (n != 0)
            {
                return false;
            }
            return true;
        }
        std::string Status()
        {
            if (_isrunning)
            {
                return "running...";
            }
            else
            {
                return "sleep...";
            }
        }

        void Stop()
        {
            if (_isrunning)
            {
                ::pthread_cancel(_tid);
                _isrunning = false;
                std::cout << _name << " Stop..." << std::endl;
            }
        }

        void Join()
        {
            ::pthread_join(_tid, nullptr);
            std::cout << _name << " Joined..." << std::endl;
        }

        std::string Name()
        {
            return _name;
        }

        ~Thread()
        {
        }

    private:
        std::string _name;
        pthread_t _tid;
        bool _isrunning;
        func_t _func; // 回调函数
    };
}

简单写一个抢票代码,总共有10000张票,创建出4个线程来同时抢票:

#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"

using namespace ThreadMoudle;

static int ticket = 10000;

void route(const std::string &name)
{
    while(true)
    {
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s get a ticket: %d \n ",name.c_str(),ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}

int main ()
{
    Thread t1("thread-1", route);
    Thread t2("thread-2", route);
    Thread t3("thread-3", route);
    Thread t4("thread-4", route);

    t1.Start();
    t2.Start();
    t3.Start();
    t4.Start();

    t1.Join();
    t2.Join();
    t3.Join();
    t4.Join();


    return 0;
}

在这里插入图片描述

我们可以看到出现了抢到负数的情况。这是为什么呢?


  • 这是因为在多线程环境中,操作系统会在多个线程之间进行切换和调度,每个线程都有其自己的程序计数器和寄存器,用于记录当前执行的位置和状态。操作系统会通过某种调度算法来决定哪个线程获得CPU时间片。
  • 然而当多个线程访问同一个共享资源的时候,他们共同会读取和修改该资源。
    假设有两个线程A和B,他们都要访问ticket变量。线程A和B的执行过程如下:
  1. 线程A获取ticket的值,发现是1,同时线程B也获取ticket的值,发现同意是2。
  2. 线程A将ticket的值减 1 ,结果变为0。
  3. 但是在线程B也将进行减操作的时候,此时ticket已经变为0了,再--就变成了负数。

- -的实现中,并不是直接- -的,而是分为三步,1. 重读数据, 2.- -数据, 3.写回数据。
所以才会出现ticket为负数的情况。

进行加锁处理:

//Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>

namespace ThreadMoudle
{
    class ThreadDate
    {
    public:
        ThreadDate(const std::string & name,pthread_mutex_t *lock)
        :_name(name),_lock(lock)
        {

        }
    public:
        std::string _name;
        pthread_mutex_t * _lock;
    };

    typedef void (*func_t)(ThreadDate* td); // 函数指针

    class Thread
    {
    public:
        void Excute()
        {
            std::cout << _name << " is running ..." << std::endl;
            _isrunning = true;
            _func(_td);
            _isrunning = false;
        }

    public:
        Thread(std::string name, func_t func,ThreadDate* td)
            : _name(name), _func(func),_td(td)
        {
            std::cout << "create " << name << " done ..." << std::endl;
        }

        static void *ThreadRountine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);
            self->Excute();
            return nullptr;
        }
        bool Start()
        {
            int n = ::pthread_create(&_tid, nullptr, ThreadRountine, this);
            if (n != 0)
            {
                return false;
            }
            return true;
        }
        std::string Status()
        {
            if (_isrunning)
            {
                return "running...";
            }
            else
            {
                return "sleep...";
            }
        }

        void Stop()
        {
            if (_isrunning)
            {
                ::pthread_cancel(_tid);
                _isrunning = false;
                std::cout << _name << " Stop..." << std::endl;
            }
        }

        void Join()
        {
            ::pthread_join(_tid, nullptr);
            std::cout << _name << " Joined..." << std::endl;
            delete _td;
        }

        std::string Name()
        {
            return _name;
        }

        ~Thread()
        {
        }

    private:
        std::string _name;
        pthread_t _tid;
        bool _isrunning;
        func_t _func; // 回调函数
        ThreadDate* _td;
    };
}

//LockGuard.hpp
#pragma once

#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;
};
//main.cc
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"

using namespace ThreadMoudle;

static int ticket = 10000;

// pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;

// void route(ThreadDate *td)
// {
//     std::cout << td->_name << " : " << "mutex address: " << td->_lock << std::endl;
//     sleep(1);
//     while (true)
//     {
//         ptherad_mutex_lock(td->_lock);
//         if (ticket > 0)
//         {
//             usleep(1000);
//             printf("%s get a ticket: %d \n ", name.c_str(), ticket);
//             ticket--;
//         }
//         else
//         {
//             break;
//         }
//     }
// }

void route(ThreadDate *td)
{
    while(true)
    {
        LockGuard lockguard(td->_lock);
        if(ticket > 0)
        {
            usleep(1000);
            printf("who %s, get a tickdt: %d\n",td->_name.c_str(),ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}

static int threadnum = 4;

int main()
{
    // Thread t1("thread-1", route);
    // Thread t2("thread-2", route);
    // Thread t3("thread-3", route);
    // Thread t4("thread-4", route);

    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,nullptr);

    std::vector<Thread> threads;
    for (int i = 0; i < threadnum; i++)
    {
        std::string name = "thread" + std::to_string(i+1);
        ThreadDate* td = new ThreadDate(name,&mutex);
        threads.emplace_back(name,route,td);
    }

    for(auto &thread:threads)
    {
        thread.Start();
    }
    
    for(auto &thread:threads)
    {
        thread.Join();
    }
 
    pthread_mutex_destroy(&mutex);
    return 0;
}

以上使用了POSIX线程(pthreads)模拟了C++的多线程模块,定义了一个Thread类来封装线程的管理,以及使用一个用于自动互斥锁和解锁的LockGuard类,以确保线程的安全。
main函数中初始化一个互斥锁并且创建多个线程,这些线程执行一个共享的函数route,该函数递减共享的票务计数器,同时确保使用LockGuard的互斥。
每个线程都会打印其名称和票的编号,知道票数被抢光。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值