当前位置: 首页 > news >正文

Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和锁,线程同步和条件变量,线程其他知识点

Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和互斥锁,线程同步和条件变量,线程其他知识点

    • 1.前言
  • 一.模拟C++11线程库自己封装简易语言级线程库
    • 1.实现框架
    • 2.迅速把构造等等函数写完
    • 3.start和work
      • 1.尝试一
      • 2.尝试二
      • 3.最终版本
      • 4.给出代码
  • 二.模拟实现多线程(为编写线程池做准备)
    • 1.进程池部分代码修改一下
    • 2.代码走起
  • 三.线程互斥与互斥锁
    • 1.模拟多线程抢票的场景
      • 1.代码
    • 2.原子性
      • 1.介绍
      • 2.分析一下问题原因
    • 3.介绍并使用互斥锁解决问题
      • 1.介绍
      • 2.法一:全局锁
      • 互斥锁导致的线程饥饿问题
      • 3.法二
      • 4.RAII登场
        • 1.引入
        • 2.代码
      • 5.小小总结
    • 4.互斥锁的原理
      • 1.说明
      • 2.演示
      • 3.小总结
      • 4.申请释放锁一定不会陷入内核吗?
    • 5.频繁申请释放锁导致程序变慢的原因
  • 四.线程同步与条件变量
    • 1.线程同步与场景的引入
    • 2.代码与演示
    • 3.条件变量的理解
    • 4.接口
    • 5.signal使用
    • 6.伪唤醒结论
    • 7.伪唤醒演示
      • 1.broadcast导致伪唤醒
      • 2.signal导致伪唤醒
      • 3.解决伪唤醒的bug
        • 1.broadcast
        • 2.signal
  • 五.线程安全与可重入
    • 1.线程安全
    • 2.可重入
    • 3.两者的区别与联系
  • 六.死锁
    • 1.概念
    • 2.四个必要条件
    • 3.如何预防/解决死锁问题
  • 七.读者写者模型(读写锁)
    • 1.介绍
    • 2.接口
    • 3.原理
  • 八.自旋锁
    • 1.什么是自旋锁
    • 2.为何要有自旋锁/自旋锁的应用场景

1.前言

我们之前简单了解了C++11线程库的一部分,今天我们试着写一下C++11的线程库,并且模拟实现一下多线程(今天就是代码环节),写完一堆代码之后,我们进入线程互斥,锁,线程安全和可重入部分的学习,依旧是代码+理论

当然绝对没有写库的大佬们写的那么完整且周到,我们实现的版本能让我们很好的使用即可,我们模拟实现主要是为了

  1. 为了后续实现线程池做准备(我们以后的线程池就用这个我们自己写的线程库了,因为我们自己写的用起来更随心所欲,库里的只能按大佬设计的走
  2. 让我们更加熟悉线程与线程接口,更加适应多线程环境代码的编写
  3. 增强代码能力
  4. 体会编程的乐趣(写了一年多单执行流的代码[除了进程池和进程间通信让我们体会到了多执行流的乐趣]了,该换换口味了)

废话不多说,直接走起

一.模拟C++11线程库自己封装简易语言级线程库

在这里插入图片描述

1.实现框架

在这里插入图片描述
什么代码都是这样,有了框架,下面就是实现函数,并按需求更新框架的工作了

2.迅速把构造等等函数写完

代码:
在这里插入图片描述
验证
在这里插入图片描述
没任何问题

3.start和work

1.尝试一

在这里插入图片描述

2.尝试二

我们把work加上static修饰
在这里插入图片描述

3.最终版本

在这里插入图片描述

4.给出代码

#pragma once
#include <iostream>
using namespace std;
#include <string>
#include <functional>
#include <pthread.h>
//T是函数对象的参数类型
template<class T>
class Thread
{
public://创建线程对象,初始化成员变量Thread(const string& name,const function<void(T)>& func,const T& data):_threadName(name),_func(func),_data(data){#ifdef DEBUG//用一下条件编译cout<<"构造函数执行完毕: name: "<<_threadName<<endl;#endif}//封装_func为void*(*pf)(void*)的函数对象,因为pthread_create只能传入这个类型的函数对象static void* work(void* arg)//arg给我this!!!!,别给错了{Thread<T>* mythis=static_cast<Thread<T>*>(arg);//虽然我是静态成员函数,但是我有this,我想怎么玩就怎么玩mythis->_func(mythis->_data);return nullptr;}bool start()//启动线程{if(_isRunning==false){pthread_create(&_tid,nullptr,work,this);//没问题!!_isRunning=true;return true;}//如果线程已经被启动,就不能再被启动了return false;}bool joinable() const{return _isJoin;}bool running() const{return _isRunning;}bool join()//回收线程{if(!_isRunning) cout<<"该线程 "<<_threadName<<" 还未开始"<<endl;else if(!_isJoin) cout<<"该线程 "<<_threadName<<" 不可join"<<endl;elsereturn pthread_join(_tid,nullptr)==0;//返回join是否成功return false;//还未开始或者不可join,返回false}bool detach()//分离线程{if(!_isRunning) cout<<"该线程 "<<_threadName<<" 还未开始"<<endl;else if(pthread_detach(_tid)==0)//如果分离成功{_isJoin=false;return true;}return false;//分离失败}private:string _threadName;//线程名字function<void(T)> _func;//具体的函数对象T _data;//函数参数pthread_t _tid;//线程IDbool _isRunning=false;//该线程是否正在运行bool _isJoin=true;//该线程可分离
};

二.模拟实现多线程(为编写线程池做准备)

写完我们的线程库之后,我们玩一下特别小的线程池雏形,跟我们进程池的需求一样
此时,我们拿出进程池的User代码过来,改一下

1.进程池部分代码修改一下

User.h
#pragma once
const int task_num=5;
void printLog(int i);
void ConnectDatabase(int i);
void UserLogin(int i);
void GenerateReports(int i);
void TestSoftwarePerformance(int i);
User.cpp
#include <unistd.h>
#include <iostream>
#include <vector>
#include <string>
#include <chrono>  
#include <iomanip>
#include <sstream>
#include "User.h"
using namespace std;
// 打印日志的函数  
void printLog(int i) 
{  #ifdef DEBUGcout<<"void printLog(int i) i: "<<i<<endl;#endif// 获取当前时间  auto now = std::chrono::system_clock::now();  auto now_c = std::chrono::system_clock::to_time_t(now);  // 格式化时间戳  ostringstream oss;  oss << put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");  string timestamp = oss.str();  const vector<string>& message={"This is a log message.","Another log message with some information."};// 打印日志信息for(auto& e:message){cout << "[" << timestamp << "] " << e << endl;}usleep(1000);//休息1000微秒,也就是1ms
}
//下面的我就随便遍了,MySQL还没学....
//连接数据库
void ConnectDatabase(int i)
{#ifdef DEBUGcout<<"void ConnectDatabase(int i) i: "<<i<<endl;#endifcout<<"Connect to the database succeed"<<endl;usleep(1000);//休息1000微秒,也就是1ms
}//用户登录
void UserLogin(int i)
{#ifdef DEBUGcout<<"void UserLogin(int i) i: "<<i<<endl;#endifcout<<"User login succeed"<<endl;usleep(1000);//休息1000微秒,也就是1ms
}//报告生成
void GenerateReports(int i)
{#ifdef DEBUGcout<<"void GenerateReports(int i) i: "<<i<<endl;#endifcout<<"Generate reports succeed"<<endl;usleep(1000);//休息1000微秒,也就是1ms
}//测试软件性能
void TestSoftwarePerformance(int i)
{#ifdef DEBUGcout<<"void TestSoftwarePerformance(int i) i: "<<i<<endl;#endifcout<<"Test software performance succeed"<<endl;usleep(1000);//休息1000微秒,也就是1ms
}

2.代码走起

thread.hpp不用改
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
关于其他的对于多线程的玩法,有时间了我们再来玩吧,其实锁也挺好玩的,下面进入线程互斥与锁

三.线程互斥与互斥锁

记得我们之前介绍过的信号量吗?
我们当时说二元信号量就是一把互斥锁,并且我们用二元信号量实现了共享内存的互斥,下面我们就来学一下互斥锁和原子性啦
最后的时候我们会引出互斥的局限,此时就需要有不同来解决这一问题

1.模拟多线程抢票的场景

1.代码

这里直接给出代码,然后说明一下,这代码写起来没啥难的
在这里插入图片描述
理想状态: 最后一次ticket打印时是1,然后所有线程都退出
在这里插入图片描述
我们可以看到,整个运行逻辑是非常快的,这就是多线程的一大好处
在这里插入图片描述
记住: 此时我们没有加锁,整个代码运行很快,而且多个线程之间切换的频率也很快(并发性高)
但是:
在这里插入图片描述
我们先介绍一下原子性

2.原子性

1.介绍

在这里插入图片描述
在这里插入图片描述

我们目前的理解是: 只要一条代码不是一条汇编指令,那么这条代码就不是原子的

我们可以类比一下:

if(ticket > 0)这个逻辑判断也不是原子的
为何呢?
1. 将ticket在内存当中的值读取到xxx寄存器当中
2. 将xxx寄存器当中的值与0进行比较,设置条件标志
3. 根据比较结果进行跳转执行if语句或者else语句
至少是有着3步的

2.分析一下问题原因

下面我们利用原子性来分析一下ticket出现0,-1,-2的原因

首先我们要达成共识的是: 
线程在被CPU调度时,只要时间片到了,那么这个线程一定会被切换走,无论即将执行哪条指令
(不考虑线程/进程陷入内核态,因为那个时候OS会进行介入)

在这里插入图片描述
下面我们分析一下
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
只要出现数据不一致,那么就一定会有问题发生(尽管问题发生的概率较小,但是在如此高并发的调度下,次数变多,概率在小的事件发生的次数也会变大啊)

而我们刚才的多线程调度频率算是很快很平均的了
一直都是0 1 2 3 0 1 2 3这样调度
在这里插入图片描述
但是当线程A刚if判断成功进入if语句准备ticket–时,此时时间片到了,需要切换
而且此时ticket就是1,本来这个票应该是我的,因为我这个线程正准备减它,但是我被切换了

然后线程B被调度了,将ticket减为0,然后大部分线程判断的时候看到了ticket都变成0了,我们走else就退出吧
但是当线程A再次被调度时,它是直接执行上次被切换时刚进入的if语句,然后将ticket减为了-1 !!,
此时就导致卖出了比10000张更多的票,导致了很严重的问题

那怎么办呢?
需要利用互斥锁来解决这一问题

3.介绍并使用互斥锁解决问题

注意:ubuntu下man手册没有按照POSIX标准的文档,因此man手册查不到pthread_mutex_destroy
解决方法:sudo apt-get install manpages-posix-dev

mutex:就是互斥锁的意思

1.介绍

man 2 pthread_mutex_destroy
在这里插入图片描述
2.申请锁和释放锁
在这里插入图片描述

2.法一:全局锁

在这里插入图片描述
下面我们演示一下
在这里插入图片描述
在这里插入图片描述
ticket没有出现0,-1,-2,到1就结束了,可见锁成功保护了临界资源
而且: 我们明显发现: 1.运行速度变慢 2.多线程并发效率降低
在这里插入图片描述

互斥锁导致的线程饥饿问题

锁还有其他缺点:
如果某个线程申请锁的能力太强,一直频繁申请锁,释放锁,申请锁,释放锁,就会导致其他线程出现"饥饿"问题!!

我们模拟一下: 某个线程申请锁之后再也不释放与申请这个锁了,直到把票抢光之后在释放锁
在这里插入图片描述
在这里插入图片描述
只有0号线程一直在运行,其他线程都阻塞了

请注意:即使只有一个线程能够抢票,但是多线程的速度依然跟刚才加锁的情况一样

为何??

因为所有线程瓜分进程的时间片,某个线程时间片到了,就会轮到其他线程运行,而其它线程一直阻塞,永远无法抢票(饥饿)

刚才这种情况下倒也不会造成太大的问题: 至少你这个线程把任务都完成了嘛(而且你们这些线程完成的都是同一个任务)
但是如果极端情况下,你这个拥有锁的线程一直不干正事呢?? 比如陷入了某种意外的死循环等等…
或者你们这些线程完成不同任务,但是依然需要申请同一把锁(我总觉得这种情况应该不多吧…)

那就是一个大事故了,因为你这个线程不仅出了错误不干正事,你还导致其他线程饥饿,让其他线程也干不了正事

整个多线程环境就都被这个线程坑了,怎么解决??
利用线程同步(我们以后会重点介绍的,这里先埋一个伏笔,到时候就有场景可以玩了)

注意: 如果某个线程申请锁之后,没有释放锁,并且又申请锁了,那么这个线程就卡死了,这就是一个锁造成的最经典的死锁问题
死锁的时候我们会演示的

3.法二

在这里插入图片描述
在这里插入图片描述
验证成功

4.RAII登场

1.引入

总感觉刚才不是很优雅,而且万一写错了.....................
经过观察,我们发现了一个惊天动地的特点,从而引发了下面…
在这里插入图片描述

2.代码

在这里插入图片描述
演示:
在这里插入图片描述

5.小小总结

在这里插入图片描述
在这里插入图片描述

4.互斥锁的原理

下面我们来谈一下互斥锁的原理,互斥锁是如何保证原子性的(特别是申请锁的原子性),当然,互斥锁有很多实现方式
我们介绍的只是其中一种而已,但是大家理解一下这种保证原子性的方式即可

1.说明

首先要先说明的是:
在介绍进程切换的时候,我们曾经提到过,每个进程(现在是线程)都有自己独立的硬件上下文,而硬件上下文就是该线程被调度时需要存放在寄存器当中的内容

因此我们当时说单核CPU下寄存器只有一套,而寄存器当中的数据是有多套的,每个线程都私有一套寄存器当中的数据,因此才实现了线程切换
在这里插入图片描述
unlock就是把1写入mutex,表示该锁资源重新拥有了
因为每个线程在访问临界资源之前都要先申请锁,而申请锁时都会先把al寄存器当中的内容清为0,所以无需担心释放锁时没有修改持有锁时al寄存器当中的内容

2.演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
大家可以画图自己感受一下:

  1. xchgb的作用: 本质上就是将锁资源转移到线程自己的硬件上下文当中为该线程所私有
  2. 线程在执行完xchgb之后,谁的al当中是1,谁就有锁资源,否则就没有锁资源

3.小总结

可以看出这个互斥锁的设计是非常巧妙的
通过

  1. 只为锁设置一个1表示该锁的资源
  2. 每个线程申请锁时都先将自己的al寄存器当中的data清0,表示当前该线程没有锁资源
  3. 申请锁时,每个线程都将自己al寄存器当中的data跟mutex交换
  4. 因为整个环境下只有一个1,而且交换的指令是原子的(由计算机的体系结构提供的swap/exchange指令保证)
  5. 因此一定只有一个线程交换之后能拿到1,表示该线程申请锁成功
  6. 而其它线程交换之后只能拿到0,表示该线程申请锁失败
    =============================================================================================
    这一切的一切发生时,互斥锁还都只是一个内存当中用户级/进程级(因为进程是用户的代表嘛)的数据而已
    锁的设计巧妙归巧妙,但是锁终归是会将多线程执行加锁的代码由并发访问改成并行访问,从而降低并发效率
    但是互斥(锁)与同步在生产者消费者模型下就大有裨益了,到那时我们写代码再好好分析一下大佬设计的优雅之处

所以关于锁,希望大家辩证判断,仔细权衡自己的需求之后在妥善适用它们(临界资源的保护? 生产者消费者模型下的应用? …)

不过单拿出锁来说,它本身的确是无奈之举(因为要保护临界资源嘛),如果不加锁就能保证临界资源的安全,那谁还加锁…

4.申请释放锁一定不会陷入内核吗?

你给我说完了互斥锁的原理之后,你强调了mutex是用户空间的变量,而且pthread库也是用户空间的,
在这里插入图片描述

5.频繁申请释放锁导致程序变慢的原因

  1. 加锁代码块的串行化: 多线程执行临界区代码,加锁之后: 由并发执行变为串行执行,限制了多线程的高并发的优点
  2. 互斥锁的锁竞争: 导致多线程频繁陷入内核,回到用户态,陷入内核,回到用户态… 开销较大
  3. CPU缓存的无效化: 如果被锁保护的资源被频繁访问修改,会导致CPU缓存的无效化和重新加载,而CPU缓存的加载才是线程切换优于进程切换的最直接原因,因此这会限制多线程切换代价低的优点

又因为线程是瓜分进程时间片的,因此线程切换的频率更高,而加锁限制了多线程的很多优点,极端情况下会使得多线程还不如单线程不加锁呢…

四.线程同步与条件变量

经过刚才抢票的代码,我们看到了线程互斥完美的保护了临界资源的安全,但是由于多线程竞争锁的能力不同,
抢到锁并执行临界区的线程也就没有任何的顺序性
因此在某些情况下会导致线程饥饿,甚至引发更大的问题

此时就需要有线程同步来保证临界资源访问的顺序性和高效性(高效性大家可能不太好理解,但是我们写代码分析的时候大家就能够很好地理解了)

1.线程同步与场景的引入

在临界资源使用安全的前提下,让多线程访问临界资源具有一定的顺序性就叫做线程同步

在这里插入图片描述
其实不仅仅条件变量可以完成这一任务,信号量也可以,我们以后会使用的,我们以后会写一个信号量+环形队列实现生产者消费者模型的代码

下面我们改造一下抢票的需求:
票初始时只有1000张,每隔一段时间就会多发1000张
但是时间间隔有点长,导致新线程抢光票都退出了,票才补上,因此我们的else 当中就不能break了
所以我们让它打印"没票了…"

为了方便演示,我们用一下全局的互斥锁

2.代码与演示

在这里插入图片描述
在这里插入图片描述
下面我们介绍一下条件变量

3.条件变量的理解

记住锁🔒,条件变量🔔
在这里插入图片描述

4.接口

在这里插入图片描述
在这里插入图片描述

5.signal使用

我们这里就只用一下signal吧,因为broadcast在我们这个场景当中太容易导致伪唤醒了
而且为了方便演示,我们只给100张票,每1s加100张票
而且每次抢票只打印一次票数
在这里插入图片描述
在这里插入图片描述

6.伪唤醒结论

伪唤醒是指线程在等待条件满足时被唤醒,但是因为竞争锁失败,导致临界资源被其他线程所修改从而又使得等待条件不满足,
但是因为对应线程被唤醒之后没有继续检查临界资源是否符合条件而直接访问临界资源导致的一种bug

可能有点长且抽象,我们举一个例子
在这里插入图片描述

7.伪唤醒演示

1.broadcast导致伪唤醒

在这里插入图片描述
在这里插入图片描述
broadcast比起signal来说是一种类似于用户级文件缓冲区似的,将本来要多次陷入内核的操作减为了一次…
所以减少了线程陷入内核的次数,从而提高效率(因为每次唤醒线程都要将进入了阻塞状态的线程再次调度运行起来,势必需要陷入内核执行该操作)
因此一次唤醒所有线程比起一次唤醒一个线程,唤醒多次来说是更加高效的

因此signal适用于需要严格控制线程访问临界资源顺序的场景
而broadcast适用于无需严格控制线程访问临界资源顺序的场景

2.signal导致伪唤醒

在这里插入图片描述

3.解决伪唤醒的bug

将if改成while,让伪唤醒的线程看到自己不满足条件之后继续乖乖的到等待队列当中等

1.broadcast

在这里插入图片描述

2.signal

在这里插入图片描述

五.线程安全与可重入

1.线程安全

线程安全是指:多线程并发访问临界资源时,不会出现临界资源的数据不一致问题

常见的线程安全的情况:

  1. 每个线程对临界资源只读取,不修改
  2. 执行具有原子性的代码(比如定义变量)
  3. 多个线程之间的切换不会导致对应代码的执行结果存在二义性
  4. 加锁/使用信号量了

2.可重入

重入 : 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,
一个函数在重入的情况下,运行结果不会出现任何问题,则该函数被称为可重入函数,否则,是不可重入函数

常见不可重入的情况:

  1. 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  2. 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  3. 可重入函数体内使用了静态的数据结构

常见可重入的情况:

  1. 不使用全局变量或静态变量
  2. 不使用用malloc或者new开辟出的空间
  3. 不调用不可重入函数
  4. 不返回静态或全局数据,所有数据都有函数的调用者提供
  5. 使用本地数据,或者通过对全局数据进行本地拷贝来保护全局数据

3.两者的区别与联系

  1. 可重入函数是线程安全函数的一种
  2. 线程安全不一定是可重入的,而可重入函数则一定是线程安全的
  3. 可重入描述的是函数的特点,而线程安全描述的是多线程访问临界资源的特点

六.死锁

1.概念

多执行流在不释放自己所占有资源的情况下互相申请对方所占有的资源而处于的一种永久等待的状态

2.四个必要条件

  1. 互斥 : 即对应资源每次只能被一个执行流所使用
  2. 请求与保持 : 一个执行流在因请求资源而阻塞时,对已持有的资源保持不放
  3. 不剥夺 : 一个执行流已经获得的资源,在使用完毕之前不能被剥夺
  4. 循环等待条件 : 若干执行流之间形成的一种头尾相连的循环等待资源的关系

3.如何预防/解决死锁问题

解决死锁问题 : 破坏四个必要条件当中的任意一个即可
预防死锁问题:

  1. 申请资源时按序申请(加锁顺序一致)
  2. 及时释放锁
  3. 资源一次性分配

七.读者写者模型(读写锁)

在多线程环境当中,有一种情况非常常见:
公共资源被修改的场景较少,被读取的场景较多,读取公共资源并不会修改公共资源
此时这种场景被称为读者写者模型

它的特点是:

读者多,写者少,读场景较多,读数据不修改公共资源

1.介绍

1个交易场所(用特定的容器/数据结构申请的内存空间)
两种角色(读者和写者)
三种关系
读读之间: 不互斥(并发)
读写之间: 互斥+同步
写写之间: 互斥

  1. 为何读读之间不互斥呢?
    因为读者只会读数据,不会把数据给取走
  2. 如果读者一直存在,那么写者就会一直阻塞,也就是读者优先级高(默认情况)
    当然自己使用接口时,也可以自己调整为写者优先级高

写独占,读共享,读锁优先级高

2.接口

在这里插入图片描述
这个不是重点,大家感兴趣的话自己用一下测试测试

3.原理

读者之间不是并发吗? 读者之间又不互斥,为什么有读锁? 只搞一个写锁不就行了吗?
因为读写之间是互斥的,而且读者优先,写者要等到读者都退出之后才能进行写操作

必须为读者要维护一个计数,表示读者的个数,每个读者要进行读操作之前要对计数++,读操作结束之后要对计数–
而这个计数对于读者来说是共享资源,而且读者都要修改该共享资源,而且++和–操作不是原子的,因此读者修改共享资源就要加锁,因此读者也要有锁

用伪代码来解释:
在这里插入图片描述

八.自旋锁

1.什么是自旋锁

当申请锁失败之后,互斥锁会阻塞等待,当锁释放之后会重新唤醒竞争锁

而自旋锁在申请锁失败之后会继续申请锁(被称为自旋/忙等待),直到申请成功为止

2.为何要有自旋锁/自旋锁的应用场景

当执行流访问修改临界资源的速度较快时(比如我们上面的多线程抢票,就只有一个判断和一个–),适合使用自旋锁
速度较慢时,适合使用互斥锁,避免频繁无谓的申请锁操作

因为互斥锁的锁竞争会导致多线程频繁陷入内核,回到用户态,陷入内核,回到用户态(因为要不断切换状态,一会被OS放到运行队列,一会又被OS放到阻塞队列)… 开销较大

而自旋锁无需进行陷入内核,回到用户态的操作,因为锁是进程地址空间当中用户空间的一个内存级变量(我们介绍互斥锁原理的时候说的)

以上就是Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和互斥锁,线程同步和条件变量,线程其他知识点的全部内容,希望能对大家有所帮助!!

相关文章:

Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和锁,线程同步和条件变量,线程其他知识点

Linux多线程系列2: 模拟封装简易语言级线程库,线程互斥和互斥锁,线程同步和条件变量,线程其他知识点 1.前言 一.模拟C11线程库自己封装简易语言级线程库1.实现框架2.迅速把构造等等函数写完3.start和work1.尝试一2.尝试二3.最终版本4.给出代码 二.模拟实现多线程(为编写线程池做…...

VUE3-form表单保存附件与基本信息

element-ui代码 <el-dialog :title"上传附件" v-model"dialogAdds.visible" width"500px" append-to-body> <el-form-item label"唯一标识"> <dict-tag v-if"form.groupId" :options"unique_identifica…...

无线网络安全技术基础

无线网络安全技术基础 无线网络安全风险和隐患 随着无线网络技术广泛应用,其安全性越来越引起关注.无线网络的安全主要有访问控制和数据加密,访问控制保证机密数据只能由授权用户访问,而数据加密则要求发送的数据只能被授权用户所接受和使用。 无线网络在数据传输时以微波进…...

sheng的学习笔记-docker部署Greenplum

目录 docker安装gp数据库 mac版本 搭建gp数据库 连接数据库 windows版本 搭建gp数据库 连接数据库 docker安装gp数据库 mac版本 搭建gp数据库 打开终端&#xff0c;输入代码&#xff0c;查看版本 ocker search greenplum docker pull projectairws/greenplum docker…...

【投稿资讯】区块链会议CCF A -- SP 2025 截止6.6、11.14 附录用率

会议名称&#xff1a;46th IEEE Symposium on Security and Privacy( S&P&#xff09; CCF等级&#xff1a;CCF A类学术会议 类别&#xff1a;网络与信息安全 录用率&#xff1a;2023年 195/1147&#xff0c;2024年录用了17篇和区块链相关的论文 Topics of interest inc…...

C++哪些函数不能被声明为虚函数

在C中&#xff0c;某些函数不能被声明为虚函数。下面详细解释哪些函数不能被声明为虚函数&#xff0c;并通过代码示例进行说明。 C哪些函数不能被声明为虚函数 不能声明为虚函数的函数示例代码及解释一、构造函数不能是虚函数二、静态成员函数不能是虚函数三、友元函数不能是虚…...

vue中数据已经改变了,但是table里面内容没更新渲染!

解决方案&#xff1a; 给table或者el-table标签上添加一个动态key值&#xff0c;只要数据发生改变&#xff0c;key值变动一下即可 标签上&#xff1a; :key“timeStamp” 初始data&#xff1a;timeStamp:0, 更新数据&#xff1a;this.timeStamp 这样每次更新数据&#xff…...

头歌实践教学平台:Junit实训入门篇

第2关&#xff1a;Junit注解 任务描述 给出一个带有注解的Junit代码及其代码打印输出&#xff0c;要求学员修改注解位置&#xff0c;让输出结果变为逆序。 相关知识 Junit注解 Java注解&#xff08;(Annotation&#xff09;的使用方法是" 注解名" 。借助注解&a…...

matlab使用教程(80)—修改图形对象的透明度

1.更改图像、填充或曲面的透明度 此示例说明如何修改图像、填充或曲面的透明度。 1.1坐标区框中所有对象的透明度 透明度值称为 alpha 值。使用 alpha 函数设置当前坐标区范围内所有图像、填充或曲面对象的透明度。指定一个介于 0&#xff08;完全透明&#xff09;和 1&#x…...

mysql bin 日志转成sql

首先确定mysql binlog 服务开启 SHOW VARIABLES LIKE log_bin; 找到binlog日志 find / -name mysql-bin.* -type f 下载下来 本地找到mysql安装位置的bin目录 在窗口路径处直接输入cmd 执行 mysqlbinlog --no-defaults --base64-outputdecode-rows -v --start-datetime&…...

河南道路与桥梁乙级资质申请:注册证书与职称证书准备

在河南道路与桥梁乙级资质申请中&#xff0c;注册证书与职称证书的准备是不可或缺的环节。以下是关于如何准备这些证书的一些关键步骤和要点&#xff1a; 明确所需证书类型&#xff1a; 注册证书&#xff1a;这通常指的是相关专业的注册工程师证书&#xff0c;如注册土木工程师…...

3D工业视觉

前言 本文主要介绍3D视觉技术、工业领域的应用、市场格局等&#xff0c;主要技术包括激光三角测量、结构光、ToF、立体视觉。 一、核心内容 3D视觉技术满足工业领域更高精度、更高速度、更柔性化的需求&#xff0c;扩大工业自动化的场景。 2D视觉技术基于物体平面轮廓&#…...

使用auth_basic模块进行基础认证

在建立和维护Web服务器时&#xff0c;身份认证是一个至关重要的环节。Nginx作为一个高性能的Web服务器&#xff0c;支持许多认证方法&#xff0c;其中较为简单和常用的一种即是基础身份认证&#xff08;Basic Authentication&#xff09;&#xff0c;这需要借助auth_basic模块实…...

深度解析物联网平台:优化数据点位管理的实战策略

策略管理 策略&#xff0c;作为在物联网平台数据点位创建过程中可设定的规则&#xff0c;涵盖了多个重要方面&#xff0c;策略是在创建点位的时候&#xff0c;可以设置的规则&#xff0c;包括存储策略、告警策略、通知策略以及联动策略。这些策略都是通过专门的列表页面进行集…...

Spring常见问题

如何理解spring属于低侵入式设计&#xff1f; 在代码中不需要写明具体依赖对象&#xff0c;在运行时进行自动注入&#xff0c;降低了组件的耦合依赖的是接口&#xff0c;而接口的实现类具有拓展性 Spring IOC 实现了什么功能&#xff0c;谈谈你对IOC的理解。 负责创建对象&…...

MiniMax Golang2轮面试,期望薪资25K

一面 1、自我介绍 2、简单介绍一下你们成立了这个finance的财务中台之后&#xff0c;整体的服务架构是怎么样的吗&#xff1f; 3、就你提到的预算池项目&#xff0c;展开说说背景&#xff0c;以及解决了怎么样的问题&#xff1f; 4、为什么采用针对T-1订单的异步计算方案&a…...

MyBatis系统学习篇 - MyBatis的缓存

MyBatis的缓存实现原理主要基于三级缓存机制&#xff0c;包括一级缓存&#xff08;本地缓存&#xff09;、二级缓存&#xff08;全局缓存&#xff09;和三级缓存&#xff08;跨会话缓存&#xff09;。这个缓存在我们实际开发中可以避免我们查询重复的数据&#xff0c;在一定程度…...

K-means聚类模型

目录 1.定义 2.K-means聚类模型的优点 3.K-means聚类模型的缺点 4.K-means聚类模型的应用场景 5.对K-means聚类模型未来的展望 6.小结 1.定义 什么是 K-means 聚类模型&#xff1f;K-means 聚类模型是一种无监督学习算法&#xff0c;用于将数据划分为不同的组或簇&#…...

免费分享一套微信小程序旅游推荐(智慧旅游)系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序旅游推荐(智慧旅游)系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序旅游推荐(智慧旅游)系统(SpringBoot后端Vue管理端) Java毕业设计…...

Matlab 2023b学习笔记1——界面认识

下载安装好Matlab后&#xff0c;可以看到如下界面&#xff1a; 可以看到&#xff0c;这时只有命令行窗口。我们在上方工具栏中选择“布局”—— “默认”&#xff0c;即可看到左右两边多出来了“当前文件夹”与“工作区”两栏。 一、当前文件夹界面 这个界面显示的是当前目录下…...

C++ sort排序的总和应用题

第1题 sort排序1 时限&#xff1a;1s 空间&#xff1a;256m 输入n个数&#xff0c;将这n个数从小到大排序&#xff0c;输出。 输入格式 第1行&#xff0c;一个正整数n&#xff08;n<100&#xff09; 第2行&#xff0c;n个正整数&#xff0c;小于100 输出格式 n个整…...

[力扣]——231.2的幂

题目描述&#xff1a; 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 bool isPowerOfTwo(int n){ if(n0)retur…...

【css】引入背景图时候,路径写入@会报错

看报错信息 我的写法 解决办法 在前面加个~...

【有手就行】使用你自己的声音做语音合成,CPU都能跑,亲测有效

此文介绍在百度飞桨上一个公开的案例&#xff0c;亲测有效。 厌倦了前篇一律的TTS音色了吗&#xff1f;打开短视频听来听去就是那几个声音&#xff0c;快来试试使用你自己的声音来做语音合成吧&#xff01;本教程非常简单&#xff0c;只需要你能够上传自己的音频数据就可以(建议…...

《ESP8266通信指南》番外-(附完整代码)ESP8266获取DHT11接入(基于Lua)

前言 此篇为番外篇,是 ESP8266 入门的其他功能教程,包括但不限于 DHT11 驱动TCP 通信Thingsboard 平台的接入阿里云物联网云平台接入华为云平台接入 1. 小节目标 使用 Lua 驱动 DHT11 传感器,获取温湿度的值 2. 进入主题 NodeMCU 基于 LUA 相关资料 官方文档&#xff1a;…...

[IMX6ULL驱动开发]-Linux对中断的处理(一)

目录 中断概念的引入 ARM架构中断的流程 异常向量表 Linux系统对中断的处理 ARM对程序和中断的处理 Linux进程中断处理 中断概念的引入 如何理解中断&#xff0c;我们可以进行如下抽象。把CPU看做一个母亲&#xff0c;当它正在执行任务的时候&#xff0c;可以看为是一个母…...

PHP基础学习笔记(面向对象OOP)

类和对象 <?php //声明一个名为 Fruit 的类&#xff0c;它包含两个属性&#xff08;$name 和 $color&#xff09;以及两个用于设置和获取 $name 属性的方法 set_name() 和 get_name()&#xff1a; class Fruit {// Propertiespublic $name;public $color;// Methodsfuncti…...

Mysql超详细安装配置教程(保姆级图文)

MySQL是一种流行的开源关系型数据库管理系统&#xff0c;它广泛用于网站和服务的数据存储和管理。MySQL以其高性能、可靠性和易用性而闻名&#xff0c;是许多Web应用程序的首选数据库解决方案之一。 一、下载安装包 &#xff08;1&#xff09;从网盘下载安装文件 点击此处直…...

HR招聘测评,如何判断候选人的团队协作能力?

什么是团队协作能力&#xff1f; 团队协作能力&#xff0c;说的是在集体环境中&#xff0c;能同他人协同工作&#xff0c;为追求共同的目标而努力&#xff0c;其中包括沟通&#xff0c;表达&#xff0c;协调&#xff0c;尊重&#xff0c;信任&#xff0c;责任共担等一系列综合…...

[STM32-HAL库]Flash库-HAL库-复杂数据读写-STM32CUBEMX开发-HAL库开发系列-主控STM32F103C6T6

目录 一、前言 二、实现步骤 1.STM32CUBEMX配置 2.导入Flash库 3.分析地址范围 4.找到可用的地址 5.写入读取普通数据 6.写入读取字符串 6.1 存储相关信息 6.2 存取多个参数 三、总结及源码 一、前言 在面对需要持久化存储的数据时&#xff0c;除了挂载TF卡&#xff0c;我们…...