线程同步、生产者消费模型和POSIX信号量
gitee仓库:
1.阻塞队列代码:https://gitee.com/WangZihao64/linux/tree/master/BlockQueue
2.环形队列代码:https://gitee.com/WangZihao64/linux/tree/master/ringqueue
条件变量
概念
概念: 利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而避免饥饿问题,叫做同步
竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件,旨在描述一个系统或者进程的输出依赖于不受控制的事件出现顺序或者出现时机。
为什么存在线程同步?
线程同步使得每个线程都能够访问临界资源,多个线程协同高效完成某些任务。
条件变量如何与互斥锁结合使用?
条件变量是包含一个等待队列的。多个线程可以去竞争一把锁,没有得到锁资源的线程会在锁上继续挂起等待,当拥有锁的线程条件变量满足时,会先释放锁资源,然后进入到条件变量的等待队列去等待(等待其他线程唤醒),这样其他线程就可以获得锁资源**,如果此时唤醒的条件变量满足,该线程可以去唤醒等待队列中的第一个线程,自己释放锁资源,然后让第一个线程重新拥有锁资源**,依次如此,多个线程就是顺序地执行工作。这样就可以实现线程同步的操作
条件变量的接口
条件变量是一个类型为pthread_cond_t
的条件变量,通过定义变量的方式来定义一个条件变量
条件变量的初始化(和锁类似)
- 使用字段
PTHREAD_COND_INITIALIZER
进行初始化,全局或者static不需要初始化和销毁 - pthread_cond_init
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数:
restrict cond:要初始化的条件变量
restrict attr:不关心,置空
pthread_cond_destroy——条件变量的销毁
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
restrict cond:要销毁的条件变量
pthread_cond_wait——等待条件变量满足
pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数:
restrict cond:在这个条件条件变量下等待
restrict mutex:互斥量
为什么pthread_cond_wait需要互斥量?
条件变量是实现线程同步的一种手段,如果一个线程进入等待队列还不释放锁资源,这样其他线程也不能够得到锁资源,这样唤醒线程的条件变量永远不可能满足,那么这个线程也将一直等待下去。所以一个线程进入等待队列需要释放自己手中的锁资源来实现真正地同步,进入等待区会释放锁,如果被唤醒,又会加锁
唤醒条件变量满足:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
参数:
cond:第一个函数是唤醒在这个条件变量的等待队列中的所有线程;第二个条件变量是唤醒在这个条件变量的等待队列中的第一个线程
实验:主线程唤醒新线程
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
using namespace std;
//创建一个全局的锁
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
//创建一个全局的条件变量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
int tickets=100;
void* start_routine(void* args)
{long long id=(long long)args;while(1){pthread_mutex_lock(&lock); //加锁pthread_cond_wait(&cond,&lock); //进入等待队列,同时释放锁!cout << "id is" << id << " ticket left" << tickets << endl;tickets --;pthread_mutex_unlock(&lock);}
}
int main()
{#define NUM 5pthread_t t[NUM];//创建多线程for(int i=0;i<NUM;++i){pthread_create(&t[i], NULL, start_routine, (void*)i+1);}while(1){sleep(1);//唤醒线程pthread_cond_signal(&cond);cout << "main thread wakeup a thread" << endl;}//线程等待for(int i=0;i<NUM;++i){pthread_join(t[i],nullptr);}
}
运行结果如下:
按照排队的次序去执行
生产者消费者模型
概念: 生产者消费者模式就是通过一个容器(缓冲区)来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过一个容器(缓冲区)来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者消费者模型优点:
- 解耦:生产者和消费者是通过一个共享数据区域来进行通信。而不是直接进行通信,这样两个角色之间的依耐性就降低了(代码层面实现解耦),变成了角色与共享数据区域之间的弱耦合,一个逻辑出错不影响两一个逻辑,二者变得更独立。
- 支持并发:生产者负责生产数据,消费者负责拿数据。生产者生产完数据可以继续生产,大部分时间内是不需要等待消费者消费数据才继续生产。也就是说,在任一时刻,二者都是在正常处理任务的,进度都得以推进。
- 支持忙闲下不均:生产者生产了数据是放进容器中,消费者不必立即消费,可以慢慢地从容器中取数据。容器快要空了,消费者的消费速度就可以降下来,让生产者继续生产。
生产消费模型特征(简记321):
- 3种关系: 生产者与生产者(互斥)、生产者与消费者(同步(主要)和互斥)和消费者与消费者(互斥)
- 两个角色: 生产者和消费者
- 一个交易场所: 容器、共享资源等
**互斥关系:**指进程之间因相互竞争使用独占型资源(互斥资源)所产生的制约关系。
**同步关系:**指进程之间为协同工作需要交换信息、相互等待而产生的制约关系。本题中两个进程之间的制约关系是同步关系,进程B必须在进程A将数据放入缓冲区后才能从缓冲区中读出数据。此外,共享的缓冲区一定是互斥访问的,所以它们也具有互斥关系。
基于阻塞队列的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。
阻塞队列的特点:
- 队列: 使用STL中的queue来实现
- 容量: 阻塞队列的容量,由用户给定,我们也可以提供一个默认的容量
- 互斥量: 为了实现生产者和消费者的同步,我们需要使用条件变量和互斥量来实现同步的操作
- 生产者唤醒和等待的条件变量: 当队列满了,生产者等待条件满足,应该挂起等待,等待消费者唤醒
- 消费者唤醒和等待的条件变量: 当队列为空,消费者等待条件满足,应该挂起等待,等待生产者唤醒
BlockQueue.hpp
对阻塞队列的一些基本操作进行了封装,有以下几个处理动作(可以设置为私有方法):
- 判断队列为空或为满
- 唤醒生产者和唤醒消费者
- 生产者挂起等待和消费者挂起等待
#pragma once#include <iostream>
#include <queue>
#include <pthread.h>
using namespace std;
const int gmaxcap = 5;
template<class T>
class BlockQueue
{
public://构造函数BlockQueue(const int &maxcap=gmaxcap):_maxcap(maxcap){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_pcond,nullptr);pthread_cond_init(&_ccond,nullptr);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}void push(const T& in) //输入型参数 const &{//保证阻塞队列的安全 1.加锁pthread_mutex_lock(&_mutex);//2.判断//【细节】:充当条件判断的语法必须是while,不能用if// 如果消费者只有一个,生产者有10个// 消费者使用broadcast,同时唤醒,但是我们只缺少一个数据//所以必须使用while判断,否则会出错//并且只要是函数调用就可能失败while(is_full()){//【细节】第二个参数,必须是我们正在使用的互斥锁//该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起//该函数被唤醒返回的时候,会自动的重新获取你传入的锁pthread_cond_wait(&_pcond,&_mutex);}//走到这里一定没有满_q.push(in);//阻塞队列里面一定有数据//这时我们可以唤醒我们的消费者进行消费//唤醒线程可以自己设置策略//【细节这个函数可以放在临界区内部,也可以放在外部】pthread_cond_signal(&_ccond);//3.解锁pthread_mutex_unlock(&_mutex);// pthread_cond_signal(&_ccond);}void pop(T* out) //输出型参数{//保证阻塞队列的安全pthread_mutex_lock(&_mutex);while(is_empty()){pthread_cond_wait(&_ccond,&_mutex);}//走到这里一定不为空*out=_q.front();_q.pop();//绝对能保证,阻塞队列里面,至少有一个空的位置pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);}
private:bool is_empty(){return _q.empty();}bool is_full(){return _q.size()==_maxcap;}queue<T> _q;int _maxcap; //队列中元素的上限pthread_mutex_t _mutex; //锁pthread_cond_t _pcond; //生产者对应的条件变量pthread_cond_t _ccond; //消费者对应的条件变量
};
test.cpp
#include "BlockQueue.hpp"
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
void* consumer(void* bq_)
{BlockQueue<int>* bq=(BlockQueue<int>*)bq_;while(1){//消费活动int data;bq->pop(&data);cout << "消费数据:" << data << endl;sleep(1);}return nullptr;
}
void* productor(void* bq_)
{BlockQueue<int>* bq=(BlockQueue<int>*)bq_;while(1){int data=rand()%10+1;bq->push(data);cout << "生产数据" << data << endl;// sleep(1);}return nullptr;
}int main()
{#define NUM 5srand((unsigned long)time(nullptr) ^ getpid());pthread_t c,p;BlockQueue<int>* bq=new BlockQueue<int>();pthread_create(&c,nullptr,consumer,bq);pthread_create(&p,nullptr,productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}
运行结果如下:
这个是一个简单的小程序,那么我们来稍微做复杂一点的尝试一下,我们可以给队列中派发任务,而这个任务不再是简单的输出数字,任务是计算加减法,并且我们多一个保存的任务,所以原本的消费者又要多一个职位——生产者
task.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <functional>
class CalTask
{using func_t=std::function<int(int,int,char)>;// typedef std::function<int(int,int,char)> func_t;
public:CalTask(){}CalTask(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_func(func){}std::string operator()(){int res=_func(_x,_y,_op);char buffer[64];snprintf(buffer,sizeof(buffer),"%d %c %d = %d",_x,_op,_y,res);return buffer;}std::string toTaskstring(){char buffer[64];snprintf(buffer,sizeof(buffer),"%d %c %d = ?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;func_t _func;
};
class SaveTask
{using func_t=std::function<void(const std::string&)>;
public:SaveTask(){}SaveTask(std::string& message,func_t func):_message(message),_func(func){}void operator()(){_func(_message);}
private:std::string _message;func_t _func;
};
void save(const std::string& message)
{const std::string target="log.txt";FILE* fp=fopen(target.c_str(),"a+");if(fp==nullptr){perror("fopen");return ;}fputs(message.c_str(),fp);fputs("\n",fp);fclose(fp);
}
const std::string oper = "+-*/%";
int mymath(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}elseresult = x % y;}break;default:// do nothingbreak;}return result;
}
BlockQueue.hpp和之前一样
test.cpp
#include "BlockQueue.hpp"
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include "task.hpp"
//中间的既是生产者,也是消费者
// c:计算
// s:存储
template<class c,class s>
class BlockQueues
{
public:BlockQueue<c>* c_bq;BlockQueue<s>* s_bq;
};
void* consumer(void* bqs_)
{//这里的强制转换不要写错!BlockQueue<CalTask>* bq=(static_cast<BlockQueues<CalTask, SaveTask> *>(bqs_))->c_bq;BlockQueue<SaveTask>* bq_s=(static_cast<BlockQueues<CalTask, SaveTask> *>(bqs_))->s_bq;while(1){//消费活动CalTask cal;bq->pop(&cal);string res=cal();cout << "计算完成:" << res << endl;//生产活动SaveTask sv(res,save);bq_s->push(sv);// sleep(1);}return nullptr;
}
void* productor(void* bqs_)
{BlockQueue<CalTask>* bq=(static_cast<BlockQueues<CalTask, SaveTask> *>(bqs_))->c_bq;while(1){int x=rand()%100;int y=rand()%10;int operCode=rand()%oper.size();CalTask cal(x,y,oper[operCode],mymath);bq->push(cal);cout << "生产计算" << cal.toTaskstring() << endl;sleep(1);}return nullptr;
}
//这也是消费者,他把阻塞队列中的任务进行消费,不过是消费到文件上
void* saver(void* bqs_)
{BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask> *>(bqs_))->s_bq;while(1){//处理存储的任务SaveTask save;save_bq->pop(&save);save();cout << "存储成功" << endl;}return nullptr;
}int main()
{
#define NUM 5srand((unsigned long)time(nullptr) ^ getpid());pthread_t c,p,s;// BlockQueue<CalTask>* bq=new BlockQueue<CalTask>();BlockQueues<CalTask,SaveTask> bqs;bqs.c_bq=new BlockQueue<CalTask>();bqs.s_bq=new BlockQueue<SaveTask>();pthread_create(&c,nullptr,consumer,&bqs);pthread_create(&p,nullptr,productor,&bqs);pthread_create(&s,nullptr,saver,&bqs);pthread_join(c,nullptr);pthread_join(p,nullptr);pthread_join(s,nullptr);delete bqs.c_bq;delete bqs.s_bq;return 0;
}
多生产者和多消费者
- 生产者之间需要互斥,也就是生产者和生产者之间需要组内竞争一把锁,消费者也是如此
- 生产者和消费者之间用互斥量和条件变量做到同步和互斥
疑问:多生产多消费者高效在哪里?
虽然在阻塞队列中是串行执行,但是在生产之前和消费之后是并行执行!所以高效并不是在阻塞队列中
信号量
在介绍信号量之前,我们之前代码不足的地方在哪里?
我们每一次操作都要先加锁,再检测,再操作,再解锁,在没有访问之前我们无法得知,所以只能先加锁
**POSIX信号量:**该信号量允许进程和线程同步对共享资源的访问。同时也可以用于实现线程间同步。
疑问:
- 是什么? 信号量本质是一个计数器,描述临界资源的有效个数。申请一个资源就对信号量减1(P操作,必须保证操作的原子性),释放一个资源就对信号量加1(V操作,必须保证操作的原子性)
- 为什么? 临界资源可以看成很多份,互相不冲突且高效
- 怎么用? 可以使用信号量的相关接口,来申请信号量和释放信号量(下面详细介绍)
申请信号量的本质:对临界资源中特定小块资源的预定机制
接口介绍
POSIX信号量相关接口都是在semaphore.h
的头文件中。信号量是一个类型为sem_t
的变量
1.sem_init——初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
- sem:信号量
- pshared:0表示线程间共享,非零表示进程间共享
- value:信号量初始值
返回值:
成功返回0,失败返回-1
2.sem_destroy——销毁信号量
int sem_destroy(sem_t *sem);
参数:
sem:信号量
返回值:
成功返回0,失败返回-1
3.sem_wait——等待信号量
int sem_wait(sem_t *sem);
功能:
等待信号量,会将信号量的值减1
参数:
sem:信号量
返回值:
成功返回0,失败返回-1
4.sem_post——发布信号量
int sem_post(sem_t *sem);
功能:
发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1
参数:
sem:信号量
返回值:
成功返回0,失败返回-1
基于环形队列的生产消费模型
环形队列介绍
**环形队列:**环形队列和普通队列的区别就是,这种队列是一种环形的结构(是数组抽象出来的),有一个头指针和一个尾指针维护环中一小段队列。(如下图)
我们可以使用环形队列去模拟生产者消费者模型,如果不为空或不为满的时候,生产者和消费者可以并发执行,但如果为空的时候应该让生产者先执行,如果为满的时候应该让消费者先执行
实现
概述
一个交易场所: 循环队列
两个角色:
- 生产者:需要申请空间资源(P操作),然后释放数据资源(V操作)
- 消费者:需要申请数据资源(P操作),然后释放空间资源(V操作)
三种关系: 生产者与生产者(互斥)、生产者与消费者(同步(主要)和互斥)和消费者与消费者(互斥)
几个变量成员:
- 队列:数组模拟
- 容量:由用户给定
- 空间资源信号量:队列的容量大小
- 数据资源信号量:开始为0
- 生产者的下标位置:开始为0
- 消费者的下标位置:开始为0
以下是基于多生产者多消费者的循环队列
代码如下:
#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
using namespace std;
const int gcap=5;
// static const int gcap = 5;
template<class T>
class ringqueue
{
public://构造函数ringqueue(const int &cap = gcap):_queue(cap),_cap(cap),_productorStep(0),_consumerStep(0){// _queue.resize(cap);sem_init(&_spaceSem,0,cap);sem_init(&_dataSem,0,0);}//PV操作void P(sem_t& sem){sem_wait(&sem);}void V(sem_t& sem){sem_post(&sem);}//生产者//多生产者多消费者需要加锁//多生产者只有一个可以进入环形队列//多消费者也只有一个可以进入环形队列//普通情况下【进入环形队列的消费者和生产者】可以并发执行//不过环形队列空时,只有生产者可以进入//环形队列满的时候,只有消费者可以进入//生产者与生产者互斥关系,消费者与生产者互斥与同步关系,消费者与消费者互斥关系void push(const T& in){P(_spaceSem); // 申请到了空间信号量,意味着,我一定能进行正常的生产//多消费者多生产者需要对环形队列资源进行加锁pthread_mutex_lock(&p_mutex);_queue[_productorStep++]=in;_productorStep%=_cap;pthread_mutex_unlock(&p_mutex);V(_dataSem);}//消费者void pop(T* out){P(_dataSem);pthread_mutex_lock(&c_mutex);*out=_queue[_consumerStep++];_consumerStep%=_cap;pthread_mutex_unlock(&c_mutex);V(_spaceSem);}//析构函数~ringqueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);}
private:vector<T> _queue; //vector模拟环形队列int _cap; //容量sem_t _spaceSem; //生产者要生产,要的是空间资源sem_t _dataSem; //消费者要消费,要的是数据资源int _productorStep; //生产者和消费者的下标int _consumerStep;pthread_mutex_t c_mutex; //消费者的锁pthread_mutex_t p_mutex; //生产者的锁
};
注意:这里不需要判断队列是否满了,因为有信号量作计数器,空间信号量资源为0,生产者如果继续申请就会挂起等待。所以,队列中满了这个状态我们不必关心了,有信号量在其中作用
主函数代码和之前一致
相关文章:

线程同步、生产者消费模型和POSIX信号量
gitee仓库: 1.阻塞队列代码:https://gitee.com/WangZihao64/linux/tree/master/BlockQueue 2.环形队列代码:https://gitee.com/WangZihao64/linux/tree/master/ringqueue 条件变量 概念 概念: 利用线程间共享的全局变量进行同…...

(六)实现好友管理:教你如何在即时通信系统中添加好友
文章目录 一、引言1.1 即时通信系统中用户增加好友功能的重要性和应用场景1.2 TCP连接传输用户增加好友请求的基本原理 二、实现用户增加好友功能2.1 实现用户好友列表的展示和管理2.1.1 使用QListWidgetItem控件展示好友列表客户端关键代码展示服务端关键代码展示 三、效果展示…...

使用循环数组和环形链表实现双端队列
本文主要介绍了两种实现双端队列的数据结构 —— 基于环形链表和循环数组。两种实现方式的基本原理和特点,以及详细的Java代码实现和分析。 引言 双端队列(Deque, Double-ended queue)是一种具有队列和栈的性质的数据结构。它允许在两端插入和删除元素,…...

谁想和我一起做低代码平台!一个可以提升技术,让简历装x的项目
序言 正如文章标题所述,最近一段时间低代码这个概念非常的火,但其实在不了解这个东西的时候觉得它真的很炫酷,从那时就萌生了做一个低代码平台的想法。 但随着时间的变化,现在市面上低代码各个业务方向的平台都有了,可…...

知识推理——CNN模型总结(一)
记录一下我看过的利用CNN实现知识推理的论文。 最后修改时间:2023.05.12 目录 1.ConvE 1.1.解决的问题 1.2.优势 1.3.贡献与创新点 1.4.方法 1.4.1 为什么用二维卷积,而不是一维卷积? 1.4.2.ConvE具体实现 1.4.3.1-N scoring 1.5.…...

OpengES中 GLSL优化要点
本文整理一些日常积累的可以优化的方向 一.延迟vector计算 在进行float与vector计算的时候,可以先确定float再计算,不要多个float一起计算 如: highp float f0,f1;highp vec4 v0,v1;v0 (v1 * f0) * f1;优化为 highp float f0,f1;highp vec…...

项目集角色定义
一、项目集经理的角色 项目集经理是由执行组织授权、领导团队实现项目集目标的人员。项目集经理对项目集的领导、 实施和绩效负责,并负责组建一支能够实现项目集目标和预期项目集效益的项目集团队。项目集经 理的角色与项目经理的角色不同。二者之间的差异是基于项…...

Unreal Engine11:触发器和计时器的使用
写在前面 主要是介绍一下触发器和计时器的使用; 一、在Actor中使用触发器 1. 新建一个C类 创建的C类也是放在Source文件夹中的Public和Private文件夹中;选择Actor作为继承的父类;头文件包括一个触发器和两个静态网格,它们共同…...

Qt之信号槽原理
Qt之信号槽原理 一.概述 所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这…...

【MySqL】 表的创建,查看,删除
目录 一.使用Cmd命令执行操作 1.使用( mysql -uroot -p)命令进入数据库 2.创建表之前先要使用数据库 3.创建表之前要先确定表的名称,列名,以及每一列的数据类型及属性 4.创建表 注意: 5.查看所有已创建的表 6.查看单表 …...

Python 字典修改对应的键值
将 key ‘1’ 的值 ‘1’, ‘3’, ‘5’ 字符,修改为 ‘2’, ‘4’, ‘5’ 。 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅是基础那么简单………...

【JFace】ComboViewer 设置了默认值,但没有效果
问题 在数据回显时,明明在代码中通过comboViewer.setSelection设置了默认值,但没有生效(回显),是怎么回事呢 ? 分析 如果comboViewer.setSelection(new StructuredSelection(items[1]))不起作用…...

基于Redis的Stream结构作为消息队列,实现异步秒杀下单
文章目录 1 认识消息队列2 基于List实现消息队列3 基于PubSub的消息队列4 基于Stream的消息队列5 基于Stream的消息队列-消费者组6 基于Redis的Stream结构作为消息队列,实现异步秒杀下单 1 认识消息队列 什么是消息队列:字面意思就是存放消息的队列。最…...

ePWM模块-时基模块(2)
ePWM模块(2) 时基模块的使用 TBPRD:周期寄存器 (设置的时钟周期存入此,可通过阴影寄存器缓冲后写入,也可通过活动寄存器立即写入) TBCTR:时基计数变值寄存器 (时基当前所计数的值存入,用于和所设定周期值比较) TBPHS:时基相位寄存器 TBSTS:时基状态寄存器 …...

让GPT对话写小说
## 来自你的消息: 写个小说 ## 来自ChatAI的消息: 很好,我可以为您编写一个小故事。以下是一个短小精干的故事: 《夜行者》 夜,是个寂静而神秘的时间,很多人都选择睡眠。但在这个城市,有一群人——夜行者,他…...

Docker 应用部署-MySQL
一、安装MySQL 1搜索mysql镜像 docker search mysql 2拉取mysql镜像 docker pull mysql:8.0.20 3创建容器 通过下面的命令,创建容器并设置端口映射、目录映射 #在用户名目录下创建mysql目录用于存储mysql数据信息 mkdir /home/mysql cd /home/mysql #创建docker容…...

电容笔哪个厂家的产品比较好?苹果平板的电容笔推荐
从目前来说,这个苹果的正版电容笔,售价真的是太贵了,一支就要接近上千元。事实上,对于那些没有很多预算的人来说,平替电容笔是一个很好的选择。一支苹果电容笔,价格是四支平替电容笔的四倍,但平…...

今年的面试难度有点大....
大家好,最近有不少小伙伴在后台留言,又得准备面试了,不知道从何下手! 不论是跳槽涨薪,还是学习提升!先给自己定一个小目标,然后再朝着目标去努力就完事儿了! 为了帮大家节约时间&a…...

【PWN · ret2libc】ret2libc2
ret2libc1的略微进阶——存在systemplt但是不存在“/bin/sh”怎么办? 目录 前言 python3 ELF 查看文件信息 strings 查看寻找"/bin/sh" IDA反汇编分析 思路及实现 老规矩,偏移量 offset EXP编写 总结 前言 经过ret2libc1的洗礼&a…...

深度学习01-tensorflow开发环境搭建
文章目录 简介运行硬件cuda和cuddntensorflow安装。tensorflow版本安装Anaconda创建python环境安装tensorflow-gpupycharm配置配置conda环境配置juypternotebook 安装cuda安装cudnn安装blas 云服务器运行云服务器选择pycharm配置代码自动同步远程interpreter 简介 TensorFlow是…...

linux相关操作
1 系统调用 通过strace直接看程序运行过程中的系统调用情况 其中每一行为一个systemcall ,调用write系统调用将内容最终输出。 无论什么编程语言都必须通过系统调用向内核发起请求。 sar查看进程分别在用户模式和内核模式下的运行时间占比情况, ALL显…...

PMP项目管理-[第十章]沟通管理
沟通管理知识体系: 规划沟通管理: 10.1 沟通维度划分 10.2 核心概念 定义:通过沟通活动(如会议和演讲),或以工件的方式(如电子邮件、社交媒体、项目报告或项目文档)等各种可能的方式来发送或接受消息 在项目沟通中,需要…...

13个UI设计软件,一次满足你的UI设计需求
UI设计师的角色是当今互联网时代非常重要的一部分。许多计算机和移动软件都需要UI设计师的参与,这个过程复杂而乏味。这里将与您分享13个UI设计软件,希望帮助您正确选择UI设计软件,节省工作量,创建更多优秀的UI设计作品。 1.即时…...

sentinel介绍
介绍 官网地址 Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源&…...

手把手教你怎么搭建自己的ChatGPT和Midjourney绘图(含源码)
AI程序采用NUXT3LARAVEL9开发(目前版本V1.1.7) 授权方式:三个顶级域名两次更换 1.AI智能对话-对接官方和官方反代(markdown输出)PS:采用百度与自用库检测文字 2.AI绘图-根据关键词绘图-增加dreamStudio绘画-增加mid…...

继承多态经典笔试题
注:visual studio复制当前行粘贴到下一行: CTRLD 杂项 调用子类重写的虚函数(带默认参数),但参数用的是基类的虚函数中的默认参数: 这是由于参数是在编译时压入 试题一 交换两个基类指针指向的对象的vf…...

如何使用Typeface-Helper-自定义字体
随着科技的不断发展,人们对于视觉效果的要求也越来越高。在设计领域中,字体设计是非常重要的一环,因为它直接影响了整个设计的风格和品质。因此,越来越多的设计师开始寻找能够帮助他们自定义字体的工具。在这个过程中,…...

SubMain CodeIt.Right 2022.2 Crack
CodeIt.Right,从源头上提高产品质量,在编写代码时获取有关问题的实时反馈,支持最佳实践和合规性,自动执行代码审查,轻松避免与您的群组无关的通知,一目了然地了解代码库的运行状况 自动执行代码审查 使用自…...

文艺复兴的核心是“以人为本”:圣母百花大教堂(Duomo)
文章目录 引言I 圣母百花大教堂的建筑技术故事1.1 布鲁内莱斯基1.2 表现三维立体的透视画法II 美第奇家族的贡献2.1 科西莫德美第奇2.2 洛伦佐美第奇III 历史中的偶然性与必然性。3.1 文艺复兴的诞生其实是必然的事情3.2 文艺复兴的偶然性引言 从科技的视角再次理解文艺复兴,…...

校招失败后,在小公司熬了 2 年终于进了百度,竭尽全力....
其实两年前校招的时候就往百度投了一次简历,结果很明显凉了,随后这个理想就被暂时放下了,但是这个种子一直埋在心里这两年除了工作以外,也会坚持写博客,也因此结识了很多优秀的小伙伴,从他们身上学到了特别…...