自学内容网 自学内容网

线程的互斥

见一见 

见一见多线程访问的问题--抢票代码。复用线程控制的.hpp代码,只需修改main.cc代码

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

using namespace std;
int tickets = 10000;//共享资源,造成了资源不一致的问题
using namespace threadmodel; // 使用自定义的命名空间

void route(const string &name)
{
    while (true)
    {
        // cout << name << "is running " << endl;
        // sleep(1);
        if (tickets > 0)
        {                 // 有票才能抢
            usleep(1000); // 1ms->抢票花费的时间
            printf("who: %s,get a ticket:%d\n",name.c_str(),tickets);//%s转化c风格
            tickets--;
        }
        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();
}

 1.  大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

2.  但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

3.  多个线程并发的操作共享变量,会带来一些问题。  

问题 

认识和分析为什么会出现抢到负数的问题

如何解决呢?加锁

认识锁和他的接口

Ubuntu 本身没有“锁”的函数

所以从其他系统里找

未来呢,多线程都会统一的访问共享资源,我们要把这个资源进行保护,这个保护的资源就叫临界资源 ;我们把访问临界资源的代码叫做临界区;其他的代码不属于访问共享资源的代码叫做非临界区;
所谓的对临界资源的保护,本质是对临界区代码进行保护;

我们对所有资源进行访问,本质都是通过代码进行访问的;所以保护资源,本质就是想办法把访问资源的 代码保护起来;

1. 加锁的范围,粒度一定要小 ;
2. 任何线程,要进行抢票(临界区)都得先申请锁,原则上不应该有例外;

3. 所有线程申请锁,前提是所有线程都得看到这把锁,锁本身也是共享资源--加锁的过程必须是原子的;

4. 原子性:要么不做,要做就昨完,没有中间状态,就是原子性(要么抢到要么不抢,没有正在抢);

5. 如果线程申请锁失败了,那么我的线程就会阻塞,直到有人把锁释放了我才会唤醒区重新竞争这把锁;

6. 如果线程申请成功了,会继续向后运行;

7. 如果线程申请成功了,执行临界区的代码,执行临界区代码期间,可以切换么?可以的,因为在cpu眼里没有区别,都是代码。那么一个线程在临界区被切换了,那么其他线程无法进入临界区,因为虽然被切换了但是我被加锁了还没解锁了--我可以放心地执行,无人打扰;

结论:所以对于其他线程,要么我没有申请锁,要么我释放了锁,对其他线程才有意义!------我访问临界区,对其他线程是原子的! 

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

using namespace std;
int tickets = 10000;         // 共享资源,造成了资源不一致的问题
using namespace threadmodel; // 使用自定义的命名空间

pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER; // 先定义一个全局的锁

void route(const string &name)
{
    while (true)
    {
        pthread_mutex_lock(&gmutex); // 加锁
        if (tickets > 0)
        {                                                               // 有票才能抢
            usleep(1000);                                               // 1ms->抢票花费的时间
            printf("who: %s,get a ticket:%d\n", name.c_str(), tickets); //%s转化c风格
            tickets--;
            pthread_mutex_unlock(&gmutex); // 解锁,不解锁其他线程进不来
        }
        else
        {
            pthread_mutex_unlock(&gmutex); // 解锁,不解锁其他线程进不来
            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();
}

局部性的锁,让全部线程看到
main.cc:

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

using namespace std;
int tickets = 10000;         // 共享资源,造成了资源不一致的问题
using namespace threadmodel; // 使用自定义的命名空间

//pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER; // 先定义一个全局的锁

void route(threaddate *td)
{
    while (true)
    {
        pthread_mutex_lock(td->_lock); // 加自己的锁,以参数的形式传递
        if (tickets > 0)
        {                                                               // 有票才能抢
            usleep(1000);                                               // 1ms->抢票花费的时间
            printf("who: %s,get a ticket:%d\n", td->_name.c_str(), tickets); //%s转化c风格
            tickets--;
            pthread_mutex_unlock(td->_lock); // 解锁,不解锁其他线程进不来
        }
        else
        {
            pthread_mutex_unlock(td->_lock); // 解锁,不解锁其他线程进不来
            break;
        }
    }
}
static int num=4;
int main()
{
    pthread_mutex_t mutex;//定义局部的锁
    pthread_mutex_init(&mutex,nullptr);//初始化

    vector<thread>threads;
    for(int i=0;i<num;i++){//线程创建
        string name="thread-"+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;
}

thread.hpp:

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional> //回调方法

using namespace std;
namespace threadmodel // 构造一个命名空间
{
    class threaddate
    {
    public:
        threaddate(string &name, pthread_mutex_t *lock) : _name(name), _lock(lock) {}
    public:
        string _name;//线程名
        pthread_mutex_t *_lock;//锁
    };
    typedef void (*func_t)(threaddate *td);
    //    typedef void (*func_t)(const string &name); // func_t 是一个指向返回类型为 void 且有参数的函数的指针。
    //     // func_t 现在可以用作一个函数指针类型的别名,表示任何指向带字符串参数且返回类型为 void 的函数的指针。
    class thread
    {
    public:
        void excute()
        {
            cout << _name << " is running ! " << endl;
            _running = true;
            _func(_td);       // 回调不仅回调运行,他还得结束
            _running = false; // 回调完就结束了
        }

    public:
        thread(const string &name, func_t func, threaddate *td) : _name(name), _func(func), _td(td)
        {
            cout << "create: " << name << " done! " << endl;
        }
        static void *threadroutine(void *agv)          // 只要线程启动,新线程都会启动这个方法
        {                                              // 因为是类内定义的方法,所以会隐含一个this指针参数,加了static就可以,这是因为 static 成员函数不与类的实例相关联,因此它不需要 this 指针。
            thread *self = static_cast<thread *>(agv); // 获得当前对象。因为要调用_func,但_func是动态的,静态函数无法访问所以传this指针访问
            self->excute();
            return nullptr;
        }
        bool start()
        { // 线程启动方法
            int n = ::pthread_create(&_tid, nullptr, threadroutine, this);
            // 使用::pthread_create确保调用的是全局命名空间中的pthread_create函数,避免当前命名空间内可能存在的同名函数的影响。
            // 直接使用 pthread_create 会根据当前命名空间查找,如果找到了同名函数,就会调用那个函数。
            if (n != 0)
                return false;
            return true;
        }
        string status()
        {
            if (_running)
            {
                return "running";
            }
            else
                return "sleep";
        }
        void stop()
        { // 线程停止方法
            if (_running)
            {                     // 得先有线程才能停止
                _running = false; // 状态停止
                ::pthread_cancel(_tid);
                cout << _name << " stop ! " << endl;
            }
        }
        void join()
        { // 线程等待方法
            if (!_running)
            { // 没有running才值得join
                ::pthread_join(_tid, nullptr);
                cout << _name << " join ! " << endl;
            }
            delete _td;
        }
        string threadname()
        {
            return _name;
        }
        ~thread()
        {
        }

    private:
        string _name;   // 线程的名字
        pthread_t _tid; // 线程的id
        bool _running;  // 是否处于工作状态
        func_t _func;   // 线程要执行的回调函数
        threaddate *_td;//线程的参数
    };
};

解决历史问题

原理角度理解这个锁

从实现角度理解锁


原文地址:https://blog.csdn.net/yiqizhuashuimub/article/details/142904289

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!