线程封装,互斥

文章目录

线程封装

C/C++代码混编引起的问题

此处pthread_create函数要求传入参数为void * func(void * )类型,按理来说ThreadRoutine满足,但是

这是在内类完成封装,所以ThreadRoutine函数实际是两个参数,第一个参数Thread* this不显示

解决方法:

第一种:写在类外,不推荐,封装性质降低

第二种:

首先将方法写为static方法,然后为了调用_func函数,将原本传入的参数由名称指针改为this指针

然后对this指针进行修改,用void*接受,利用static_cast<>可以更好的适应(自动变为我们需要的指针类型)

这边的t就是this,但是不能写为this,与内置冲突

多线程的创建和管理

c++11语法的使用

• push_back:需要一个已经构造好的对象,并将其复制或移动到容器中。

• emplace_back:直接在容器末尾构造对象,避免了不必要的复制或移动操作

利用模版参数进行传递修改代码,保障自己需要的内容是什么

这样就完成了一个带自定义参数的线程任务的创建执行

完整代码

Thread.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>

using namespace std;
template<class T>
using func_t = function<void(T)>;
template<class T>
class Thread
{
public:
    Thread(const string& name, func_t<T> func, T data)
        :_tid(0), _name(name), _isrunning(false), _func(func), _data(data)
    {}
    static void* ThreadRoutine(void* args)
    {
        Thread* t = static_cast<Thread*>(args);
        t->_func(t->_data);
        exit(0);
    }
    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
        if(n == 0)
        {
            _isrunning = true;
            return true;
        }
        else 
        {
            return false;
        }
    }
    bool join()
    {
        if(!_isrunning)
        {
            return true;
        }
        int n = pthread_join(_tid, nullptr);
        if(n == 0)
        {
            _isrunning = false;
            return true;
        }
        else 
        {
            return false;
        }
        
    }
    ~Thread()
    {
        cout << "~Thread()" << endl;
    }
    bool IsRunning()
    {
        return _isrunning;
    }
private:
    pthread_t _tid;
    string  _name;
    bool _isrunning;
    func_t<T> _func;
    T _data;
};

main.cc

cpp 复制代码
#include <iostream>
#include "Thread.hpp"
#include <vector>

void Print(int cnt)
{
    while(cnt--)
    {
        cout << "hello world" << endl;
        sleep(1);
    }
}
string GetThreadName()
{
    static int num = 0;
    return static_cast<string>("Thread-") + to_string(++num);
}

int main()
{
    Thread<int> t(GetThreadName(), Print, 10);
    t.Start();
    t.join();

    // int num = 5; 
    // vector<Thread> threads;
    // for(int i = 0; i < num; ++i)
    // {
    //     threads.emplace_back(Print, GetThreadName());
    // }
    // for(int i = 0; i < num; ++i)
    // {
    //     cout << "Is thread is running? " << threads[i].IsRunning() << endl;
    // }

    // for(int i = 0; i < num; ++i)
    // {
    //     threads[i].Start();
    // }
    // for(int i = 0; i < num; ++i)
    // {
    //     cout << "Is thread is running? " << threads[i].IsRunning() << endl;
    // }

    // for(int i = 0; i < num; ++i)
    // {
    //     threads[i].join();
    // }



    return 0;
}

makefile文件

bash 复制代码
test_Thread:main.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f test_Thread

线程互斥

单线程抢票逻辑:

多线程模拟实现:

多线程会出现线程同步访问一个数据的情况,这种情况是不允许的,也不希望出现这种情况

任何时刻,只允许一个线程正在访问共享资源,这样的资源叫做临界资源

互斥:任何时候只允许一个执行流进入临界区

访问临界资源,起到保护作用

原子性:不会被任何调度机制打断,该操作只有两态,要么完成,要么未完成

例如:

cpp 复制代码
int cnt = 10;
cnt++;

这串代码会在汇编情况下变为三条语句

他并不是原子的,多线程下,任何一句都可能被打断

计算机内硬件(寄存器)只有一套,但是数据是各自的私有的

多线程并发访问的时间片截止到时数据不一致问题:

对于线程 1:

第一步 从内存将数值10写入寄存器

第二步 加加操作

在这时,他的时间片到了,那么这个时候就会进行上下文的保存

然后回进行线程2的运行:

第一步,第二步,...循环进行到cnt的值假设到了100

这时轮到线程1,线程1会进行上下文内的替换

那么这个100会被替换为11,这样这个操作就付之东流了~~

这个现象说明了,多线程并发访问cnt,不是原子的

会有数据不一致的并发访问问题

那么ticket变为负数也是这样的,这完全就是混乱的,判断也是计算

CPU有四种功能,

算:算术运算

逻:逻辑运算

中:处理内外中断

控:控制单元(时钟单元,取指令,用指令的执行)

比如上述减到负数,是因为,刚开始是并行执行逻辑判断假设ticket == 1,但是到了--操作时变成了穿行执行,假设是同时是4个执行流进入,执行流同时进行减减操作,就会减为负数的情况

这是小概率的事件,但是本质就是如此

数据在内存中是被线程共享的.
数据被读到寄存器中,本质就变成了线程的上下文,属于线程的私有数据

此时就需要加锁进行保护

加锁、解锁

认识接口

linux中进行加锁:使用pthread_mutex_init (mutex:互斥量)

要使用全局定义一个锁,就需要进行初始化

类型为pthread_mutex_t

初始化为PTHREAD_MUTEX_INITIALIZER

也不需要对他进行销毁,全局变量会自动进行销毁

要定义为一个局部的锁,就需要使用函数

局部的锁要进行销毁操作

解决问题

创建之后的加锁解锁函数

理解锁

加锁(以牺牲效率为代价进行解决问题):

1.给尽可能少的代码块进行加锁

2.一般加锁,都是给临界区进行加锁

执行加锁代码:


可以看到虽然正常执行,但是代码的执行速度显著下降

去掉加锁后,速度又快了不少

1.锁被创建之后,虽然它是全局变量,但是它是安全的,本身是原子性的.

2.程序员自己必须保障给临界区加锁

3.根据锁的定义,加锁只允许一个执行流进入这个临界区,所以加锁操作也只存在在一个执行流中,这时多个线程申请锁失败,失败的线程在mutex上进行阻塞,即本质进行等待

所以上述就有了,

pthread_mutex_trylock()的使用,成功往下走,不成功返回一个值进行程序员自行发挥

4.加锁之后,线程也可能会切换,但是其他线程处于等待状态,所以相当于当前线程抱着锁走,等他解锁才会被完全切换到其他线程(这把锁被使用期间,不能别其他任何线程使用,这也很好的保证了他的原子性)

喜欢不妨来个三连~~~

相关推荐
中云DDoS CC防护蔡蔡1 小时前
微信小程序被攻击怎么选择高防产品
服务器·网络安全·微信小程序·小程序·ddos
HPC_fac130520678162 小时前
以科学计算为切入点:剖析英伟达服务器过热难题
服务器·人工智能·深度学习·机器学习·计算机视觉·数据挖掘·gpu算力
yaoxin5211233 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin3 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
sinat_384241097 小时前
使用 npm 安装 Electron 作为开发依赖
服务器
朝九晚五ฺ7 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream7 小时前
Linux的桌面
linux
xiaozhiwise8 小时前
Makefile 之 自动化变量
linux
Kkooe8 小时前
GitLab|数据迁移
运维·服务器·git