信号量线程池读者写者模型

前言

大家好,我是jiantaoyab,本篇文章接着给大家介绍线程有关的信号量及线程池的基本理解。

信号量

在计算机中,信号量就是个 0 以上的整数值,当为 0 时表示己无可用信号 ,或者说条件不再允许,因此它表示某种信号的累积“ 量飞故称为信号量。

信号量是种同步机制。同步一般是指合作单位之间为协作完成某项工作而共同遵守的工作步调,强调的是配合时序,就像十字路口的红绿灯,只有在绿灯亮起的情况下司机才能踩油门把车往前开,这就是一种同步,同步简单来说就是不能随时随意工作,工作必须在某种条件具备的情况下才能开始,工作条件具备的时间顺序就是时序。

信号量就是个计数器,它的计数值是自然数,用来记录所积累信号的数量。

既然信号量是计数也必然要有对计数增减的方法。P、V 操作来表示信号量的减、增,这两个都是荷兰语中的单词的缩写。P是指Proberen表示减少, V是指单词 Verhogen,表示增加。

V操作

  1. 将信号量的值加 1
  2. 唤醒在此信号量上等待的线程

P操作:

  1. 判断信号量是否大于 0 。
  2. 若信号量大于 0,则将信号量减 1 。
  3. 若信号量等于 0,当前线程将自己阻塞,以在此信号量上等待。

信号量是个全局共享变量,up 和 down 又都是读写这个全局变量的操作,而且它们都包含一系列的子操作,因此它们必须都是原子操作。
信号量的初值代表是信号资源的累积量,也就是剩余量,若初值为1的话,它的取值就只能为0和1,这便称为二元信号量,我们可以利用二元信号量来实现锁。

在二元信号量中,down 操作就是获得锁,up操作就是释放锁。我们可以让线程通过锁进入临界区,可以借此保证只有一个线程可以进入临界区,从而做到互斥。

大致流程为:

  1. 线程 A 进入临界区前先通过 down 操作获得锁(我们有强制通过锁进入临界区的手段),此时信号量的值便为 0 。
  2. 后续线程 B 再进入临界区时也通过 down 操作获得锁,由于信号量为 0,线程 B 便在此信号量上等待,也就是相当于线程 B 进入了睡眠态
  3. 当线程 A 从临界区出来后执行 up 操作释放锁,此时信号量的值重新变成 1 ,之后线程 A 将线程 B唤醒 。
  4. 线程 B 醒来后获得了锁,进入临界区 。

信号量接口

初始化

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享 value:信号量初始值

基本操作

//销毁
int sem_destroy(sem_t *sem);
//等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
//发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

基于环形队列的生产者消费者模型

ring_queue.hpp

#pragma once
#include <vector>
#include <pthread.h>
#include <iostream>
#include <semaphore.h>using namespace std;
namespace ns_ring_queue
{template <class T>class RingQueue{private:vector<T> _ring_queue; // 环形队列int _cap;              // 容量sem_t _black_sem;      // 生产者关系空位置资源sem_t _data_sem;       // 消费者关心数据资源int _c_step;           // 消费者走到哪int _p_step;pthread_mutex_t _c_mtx;pthread_mutex_t _p_mtx;public:RingQueue(int cap = 3): _ring_queue(cap), _cap(cap){sem_init(&_black_sem, 0, cap);sem_init(&_data_sem, 0, 0);_c_step = _p_step = 0; // 从0开始走pthread_mutex_init(&_c_mtx, nullptr);pthread_mutex_init(&_p_mtx, nullptr);}~RingQueue(){sem_destroy(&_black_sem);sem_destroy(&_data_sem);pthread_mutex_destroy(&_c_mtx);pthread_mutex_destroy(&_p_mtx);}public:void Push(const T& in){//生产接口sem_wait(&_black_sem); //并行的先分配好信号量pthread_mutex_lock(&_p_mtx);//再分配锁_ring_queue[_p_step]=in;//生产到p_step位置上_p_step++;_p_step%=_cap;pthread_mutex_unlock(&_p_mtx);sem_post(&_data_sem);}void Pop(T* out){//消费接口sem_wait(&_data_sem);pthread_mutex_lock(&_c_mtx);*out=_ring_queue[_c_step]; //拿c_step上的数据_c_step++;_c_step%=_cap;pthread_mutex_unlock(&_c_mtx);sem_post(&_black_sem);}};
}

ring_cp.cc

#include"ring_queue.hpp"
#include<pthread.h>
#include<time.h>
#include<unistd.h>using namespace ns_ring_queue;void* consumer(void* args)
{RingQueue<int>* rq = (RingQueue<int>*)args;while(true){int data = 0;rq->Pop(&data);std::cout << "消费数据是: " << data << std::endl;//  sleep(1);}
}void* producter(void* args)
{RingQueue<int>* rq = (RingQueue<int>*)args;while(true){int data = rand()%20 + 1;std::cout << "生产数据是:  " << data << std::endl;rq->Push(data);sleep(1);}
}
int main()
{srand((long long)time(nullptr));RingQueue<int>* rq = new RingQueue<int>();pthread_t c0,c1,c2,c3,p0,p1,p2;pthread_create(&c0, nullptr, consumer, (void*)rq);pthread_create(&c1, nullptr, consumer, (void*)rq);pthread_create(&c2, nullptr, consumer, (void*)rq);pthread_create(&c3, nullptr, consumer, (void*)rq);pthread_create(&p0, nullptr, producter, (void*)rq);pthread_create(&p1, nullptr, producter, (void*)rq);pthread_create(&p2, nullptr, producter, (void*)rq);pthread_join(c0, nullptr);pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(c3, nullptr);pthread_join(p0, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);return 0;
}

线程的阻塞和唤醒

线程能运行是因为调度器将线程从就绪队列中摘出来放到处理器上,如果不让线程在就绪队列出现就能实现线程的阻塞。阻塞是线程自己发出的动作,也就是线程自己阻塞自己,并不是被别人阻塞的,阻塞是线程主动的行为。己阻塞的钱程是由别人来唤醒的,唤醒是被动的。

当线程被换上处理器运行后,在其时间片内,线程将主宰自己的命运。阻塞是一种意愿,表达的是线程运行中发生了一些事情,这些事情通常是由于缺乏了某些运行条件造成的,以至于线程不得不暂时停下来,必须等到运行的条件再次具备时才能上处理器继续运行。因此,阻塞发生的时间是在线程自己的运行过程中,是线程自己阻塞自己,并不是被谁阻塞。

己被阻塞的线程是无法运行的,属于睡梦中,因此它只能让别人唤醒它,否则它永远没有运行的机会。这个别人便是锁的持有者,它释放了锁之后便去唤醒在它后面因获取该锁而阻塞的线程。

因此唤醒己阻塞的线程是由别的线程,通常是锁的持有者来做的。值得注意的是线程阻塞是线程执行时的“动作”,因此线程的时间片还没用完,在唤醒之后,线程会继续在剩余的时间片内运行,调度器并不会将该线程的时间片“充满”,也就是不会再用线程的优先级priority 为时间片 ticks 赋磕。

因为阻塞是线程主动的意愿,它也是“迫于无奈”才“慷慨”地让出处理器资源给其他线程,所以调度器没必要为其“大方”而“赏赐”它完整的时间片。

线程池

线程池就是事先创建若干个可执行的线程放入一个池中,需要的时候从池中取线程而不用自行创建,使用完毕后不用销毁线程而是返回池中,从而减少线程对象创建和销毁的开销。这种做法可以大大提高服务器的性能,因为线程的创建和销毁成本相对较高,而线程池通过复用线程来降低这种开销。

线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池的种类

  1. FixedThreadPool(定长线程池)
    • 线程数量固定,不会随着任务的增加而增加。
    • 任务队列为链表结果的有界队列。
    • 适用于任务量较稳定、预计执行时间较长的情况。
  2. CachedThreadPool(可缓存线程池)
    • 线程数量不固定,根据任务的数量和执行时间自动调整。
    • 任务队列为不存储元素的阻塞队列。
    • 适用于执行大量、耗时少的任务。
    • 线程闲置超过60秒会被回收。
  3. SingleThreadExecutor(单线程化线程池)
    • 只有一个核心线程,无非核心线程。
    • 任务队列为链表结果的有界队列。
    • 适用于需要保证任务顺序执行的场景。
  4. ScheduledThreadPool(定时线程池)
    • 核心线程数量固定,非核心线程数量无限。
    • 任务队列为延时阻塞队列。
    • 执行定时或周期性任务。
    • 线程闲置超过10秒会被回收。
  5. ForkJoinPool(分治任务线程池)
    • 将任务拆分成更小的子任务,并行执行,并合并结果。
    • 适用于处理大规模的计算任务。
  6. WorkStealingPool(工作窃取线程池)
    • 每个线程都有自己的工作队列。
    • 当某个线程完成自己的任务后,会从其他线程的队列中偷取任务来执行。
    • 这种机制可以提高任务执行效率。

普通版本的线程池

thread_pool.hpp

#pragma once
#include<iostream>
#include<string>
#include<queue>
#include<unistd.h>
#include<pthread.h>namespace ns_threadpool
{template<class T>class ThreadPool{private:int _num;std::queue<T> _task_queue; //临界资源pthread_mutex_t _mtx;pthread_cond_t _cond;public:void Lock(){pthread_mutex_lock(&_mtx);}void Unlock(){pthread_mutex_unlock(&_mtx);}void Wait(){pthread_cond_wait(&_cond,&_mtx);}void Wakeup(){pthread_cond_signal(&_cond);}bool IsEmpty(){return _task_queue.empty();}public:ThreadPool(int num=3):_num(num){pthread_mutex_init(&_mtx,nullptr);pthread_cond_init(&_cond,nullptr);} //想要线程在类中执行类中的函数是不可以行的,因为只能传一个参数//要设置成static,让线程执行静态方法static void* Rountine(void* args){pthread_detach(pthread_self());//分离不用等ThreadPool<T> *tp=(ThreadPool<T> *)args; //this指针while(true){tp->Lock();while(tp->IsEmpty()){tp->Wait(); //没任务等待}int data = 0;tp->PopTask(&data);tp->Unlock();//处理任务std::cout << "消费数据是: " << data << std::endl;}}void InitThreadPool(){pthread_t id;for(int i=0;i<3;i++){pthread_create(&id,nullptr,Rountine,(void*)this);}}void PushTask(const T& in){Lock();_task_queue.push(in);Unlock();Wakeup();}void PopTask(T* out){*out=_task_queue.front();_task_queue.pop(); }~ThreadPool(){pthread_mutex_destroy(&_mtx);pthread_cond_destroy(&_cond);}};
}

main.cc

#include"thread_pool.hpp"
#include<ctime>
#include<cstdlib>using namespace ns_threadpool;int main()
{ThreadPool<int>* tp= new ThreadPool<int>(3);tp->InitThreadPool();srand((long long)time(nullptr));while(true){sleep(1);tp->PushTask(rand()%5);}
}

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一般我们有2种方法实现单例模式,饿汉模式和懒汉模式。

饿汉式:全局的单例实例在类装载时构建,急切实例化。这种方式比较简单,因为单例的实例被声明为static和final变量,第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

template <typename T>
class Singleton {static T data;
public:static T* GetInstance(){return &data;}
};

懒汉式:懒汉式是类加载进内存的时候,并不立即初始化这个单例,只有在第一次调用getInstance()方法时才初始化。需要加上双重检查锁定保证线程安全。

//存在线程安全问题
template <typename T>
class Singleton {static T* inst;
public:static T* GetInstance() {if (inst == NULL) {inst = new T();}return inst;}
};

懒汉下单例模式线程池

thread_pool.hpp

#pragma once#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>namespace ns_threadpool
{const int g_num = 5;template <class T>class ThreadPool{private:int num_;std::queue<T> task_queue_; //该成员是一个临界资源pthread_mutex_t mtx_;pthread_cond_t cond_;static ThreadPool<T> *ins;private:// 构造函数必须得实现,但是必须的私有化ThreadPool(int num = g_num) : num_(num){pthread_mutex_init(&mtx_, nullptr);pthread_cond_init(&cond_, nullptr);}ThreadPool(const ThreadPool<T> &tp) = delete;//赋值语句ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;public:static ThreadPool<T> *GetInstance(){static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 当前单例对象还没有被创建if (ins == nullptr) //双判定,减少锁的争用,提高获取单例的效率!{pthread_mutex_lock(&lock);if (ins == nullptr){ins = new ThreadPool<T>();ins->InitThreadPool();std::cout << "首次加载对象" << std::endl;}pthread_mutex_unlock(&lock);}return ins;}void Lock(){pthread_mutex_lock(&mtx_);}void Unlock(){pthread_mutex_unlock(&mtx_);}void Wait(){pthread_cond_wait(&cond_, &mtx_);}void Wakeup(){pthread_cond_signal(&cond_);}bool IsEmpey(){return task_queue_.empty();}public:// 在类中要让线程执行类内成员方法,是不可行的!// 必须让线程执行静态方法static void *Rountine(void *args){pthread_detach(pthread_self());ThreadPool<T> *tp = (ThreadPool<T> *)args;while (true){tp->Lock();while (tp->IsEmpey()){tp->Wait();}//该任务队列中一定有任务了T t;tp->PopTask(&t);tp->Unlock();t();}}void InitThreadPool(){pthread_t tid;for (int i = 0; i < num_; i++){pthread_create(&tid, nullptr, Rountine, (void *)this /*?*/);}}void PushTask(const T &in){Lock();task_queue_.push(in);Unlock();Wakeup();}void PopTask(T *out){*out = task_queue_.front();task_queue_.pop();}~ThreadPool(){pthread_mutex_destroy(&mtx_);pthread_cond_destroy(&cond_);}};template <class T>ThreadPool<T> *ThreadPool<T>::ins = nullptr;
} 

main.cc

#include "thread_pool.hpp"
#include "Task.hpp"#include <ctime>
#include <cstdlib>using namespace ns_threadpool;
using namespace ns_task;int main()
{std::cout << "当前正在运行我的进程其他代码..." << std::endl;std::cout << "当前正在运行我的进程其他代码..." << std::endl;std::cout << "当前正在运行我的进程其他代码..." << std::endl;std::cout << "当前正在运行我的进程其他代码..." << std::endl;std::cout << "当前正在运行我的进程其他代码..." << std::endl;std::cout << "当前正在运行我的进程其他代码..." << std::endl;std::cout << "当前正在运行我的进程其他代码..." << std::endl;sleep(5);srand((long long)time(nullptr));while(true){sleep(1);//网络Task t(rand()%20+1, rand()%10+1, "+-*/%"[rand()%5]);ThreadPool<Task>::GetInstance()->PushTask(t);//单例本身会在任何场景,任何环境下被调用//GetInstance():被多线程重入,进而导致线程安全的问题std::cout << ThreadPool<Task>::GetInstance() << std::endl;}return 0;
}

读者写者模型

读者写者模型是操作系统中的一种同步与互斥机制,它与消费者和生产者模型有相似之处,但也有其独特的特点。在读者写者模型中,主要涉及到两种角色:读者和写者。

读者:在读者写者模型中,读者是指那些只需要读取数据的角色。多个读者之间可以同时读取数据,不会发生冲突,因此读者之间是并行的关系。

写者:写者是指那些需要修改数据的角色。由于数据在修改时不能被其他写者或读者访问,因此写者之间、以及写者与读者之间是互斥的关系。

读者写者模型的特点

  1. 多读少写:在多数应用中,读者的数量通常远多于写者。读者写者模型适用于这种多读少写的情况,能够有效地提高数据的并发访问性能。
  2. 读者并行:多个读者可以同时访问数据,实现并行读取,提高了数据的访问效率。
  3. 写者互斥:当有写者需要修改数据时,其他写者和读者都不能访问数据,保证了数据的一致性和完整性。
  4. 优先级策略:根据不同的应用场景,可以设定不同的优先级策略,如读者优先、写者优先或公平策略等。

读写锁接口

设置读写优先

默认是读锁优先级高

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
//初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);//销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

和生产者消费者的区别

读者写者模型

读者:只读取数据,不进行写操作。

写者:对数据进行修改或写入。

生产者消费者模型

  • 生产者:负责生成数据并放入缓冲区。
  • 消费者:从缓冲区取出数据进行处理。

读者和消费者最大的区别就是读者只是读并不会对数据进行取走处理。读者写者模型主要关注于如何协调读者和写者对共享数据的并发访问,而生产者消费者模型则主要解决生产者与消费者之间的数据传递和同步问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/7326.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重庆事业编5月7号开始报名⚠️报名照需审核

24年第二季度重庆事业编招聘 本次招聘实行网上报名。考生应按照招聘岗位要求&#xff0c;诚信、准确填写报考信息&#xff0c;并按网页提示上传电子材料&#xff08;含电子登记照&#xff0c;jpg格式&#xff0c;20kb以下&#xff09;。招聘方按照岗位报考要求&#xff0c;对考…

深度学习中的优化算法:选择现有的还是自创?

深度学习中的优化算法 深度学习中的优化算法&#xff1a;选择现有的还是自创&#xff1f;现有优化算法的优势**优点包括**&#xff1a; 开发新的优化算法的考虑**开发新算法的原因**&#xff1a;**开发新算法的风险**&#xff1a; 实用建议结论 深度学习中的优化算法&#xff1…

Mac跑llama.cpp过程中遇到的问题

原repo 在华为手机上安装termux、下载库&#xff1a;顺利在电脑上安装Android NDK&#xff1a;先下载Android Studio&#xff0c;再在里面下载Android SDK 安装Android Studio时&#xff0c;SDK的某些组件总是下载不成功。后来关了梯子、改了hosts&#xff0c;重新安装就成功了…

Ansible---自动化运维工具

一、Ansible概述 1.1 Ansible简介 Ansible是一款自动化运维工具&#xff0c;通过ssh对目标主机进行配置、应用部署、任务执行、编排调度等操作。它简化了复杂的环境管理和自动化任务&#xff0c;提高了工作效率和一致性&#xff0c;同时&#xff0c;Ansible的剧本(playbooks)…

53. 【Android教程】Socket 网络接口

Socket 网络接口 大家在学习计算机网络的时候一定学习过 TCP/IP 协议以及最经典的 OSI 七层结构&#xff0c;简单的回忆一下这 7 层结构&#xff1a; 从下到上依次是&#xff1a; 物理层数据链路层互联层网络层会话层表示层应用层 TCP/IP 协议对这 7 层了做一点精简&#xff…

三岁孩童被家养大型犬咬伤 额部撕脱伤达10公分

近期&#xff0c;一名被家养大型犬咬伤了面部的3岁小朋友&#xff0c;在被家人紧急送来西安国际医学中心医院&#xff0c;通过24小时急诊门诊简单救治后&#xff0c;转至整形外科&#xff0c;由主治医师李世龙为他实施了清创及缝合手术。 “患者额部撕脱伤面积约为10公分&…

Python3中Richdem包遇到问题

Python3中Richdem包遇到问题 文章目录 Python3中Richdem包遇到问题问题一报错解决 问题二报错解决 参考 问题一 报错 RichDEM 是一套数字高程模型 &#xff08;DEM&#xff09; 水文分析工具&#xff0c;这次打算用richdem进行地形分析&#xff0c;尝试在conda里面安装richde…

【华为】NAT的分类和实验配置

【华为】NAT的分类和实验配置 NAT产生的技术背景IP地址分类NAT技术原理NAT分类静态NAT动态NATNAPTEasy IP&#xff08;PAT&#xff09;NAT Server 配置拓扑静态NAT测试抓包 动态NAT测试抓包 NAPT测试抓包 PAT测试抓包 NAT Server检测抓包 PC1PC2服务器 NAT产生的技术背景 随着…

【管理篇】管理三步曲:团队建设(二)

目录标题 如何着手团队建设提升个人能力1、要提升员工的什么能力2、提升员工个人能力的初衷是什么&#xff1f;3、如何达成上述目标4、应该如何激发员工学习的动力和意愿呢5、关于提升员工的能力&#xff0c;有两个信念特别重要&#xff1a; 提升员工的工作意愿和积极性1、管理…

继承与组合

【一】什么是继承 继承就是创建新类的一种方式&#xff0c;这个新类可以继承一个或者多个其他类的属性 新的类如果有自己属性&#xff0c;那就叫派生 【二】继承的优点 可以继承父类的所有属性和方法&#xff0c;这样就可以实现代码去重。 【三】继承方式 单继承&#xff…

Spring与AI结合-spring boot3整合AI组件

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 目录 写在前面 spring ai简介 单独整合al接口 整合Spring AI组件 起步条件 ​编辑 进行必要配置 写在最后 写在前面 本文介绍了springboot开发后端服务中&#xff0c;AI组件(Spring A…

软件工程案例学习-图书管理系统-面向对象方法

文档编号&#xff1a;LMS_1 版 本 号&#xff1a;V1.0 ** ** ** ** ** ** 文档名称&#xff1a;需求分析规格说明书 项目名称&#xff1a;图书管理系统 项目负责人&#xff1a;计敏 胡杰 ** ** …

使用Docker安装MySql数据库

大家好&#xff0c;今天给大家分享一下如何使用docker安装MySql数据库&#xff0c;关于docker的安装和常用命令&#xff0c;大家可以参考下面两篇文章&#xff0c;本文中不做过多描述。 Docker在Windows与CentOS上的安装 Docker常用命令 一、拉取MySql数据库镜像 docker pul…

IIS部署vue项目 IIS重写URL

【第一步】安装IIS {1&#xff09;打开控制面板 -> 打开程序和功能 -> 打开启用或关闭windows功能 &#xff08;2&#xff09;找到 Internet Information Services 勾选【web管理工具】和【万维网服务】&#xff0c;然后 确定 【第二步】安装URL重写模块 1). 安装URL …

2.开始学习C++

开始学习C 写在前面创建C程序1.输入输出2.main()函数3.头文件4.名称空间 C其他语句1.cin2.类3.函数4.自定义函数 写在前面 每次写读书笔记之前我都打算先写一会自己的心得&#xff0c;或者一些前情提要。先说说为什么要写这个读书心得吧。 首先是自己摆烂太久了&#xff0c;从…

C++ | Leetcode C++题解之第73题矩阵置零

题目&#xff1a; 题解&#xff1a; class Solution { public:void setZeroes(vector<vector<int>>& matrix) {int m matrix.size();int n matrix[0].size();int flag_col0 false;for (int i 0; i < m; i) {if (!matrix[i][0]) {flag_col0 true;}for …

【笔试训练】day20

1.经此一役小红所向无敌 默认小红血量无限。直接计算出经过几轮攻击后&#xff0c;会出现人员伤亡。 对于对立来说他最多承受n轮光的攻击&#xff0c;对于光来说&#xff0c;他最多承受立得m轮攻击。 所以在经过min(n,m)轮回合之后&#xff0c;他们两个人至少死一个。活下来的…

html实现网页插入音频

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文主要介绍html中 如何插入音乐和视频 视频插入 标签:<video></video> 兼容格式:mp4,因为别的浏览器都有不兼容的格式&#xff0c;唯一对mp4全都兼容。所以尽量使用mp4格式。 属性: 属性属性值…

深度学习学习日记(5.6)

在 ViT&#xff08;Vision Transformer&#xff09;模型中&#xff0c;输入图像被分割成固定大小的图块&#xff0c;然后通过一个线性变换&#xff08;通常是一个卷积层&#xff09;来将每个图块投影到一个较低维度的特征空间。这些投影后的图块被视为序列&#xff0c;然后输入…

【linux软件基础知识】-kobject结构

设备驱动模型中的一个基本结构kobject kobject结构是Linux内核设备驱动模型中的一个基本结构。设备驱动模型中的各种对象其内部都会包含一个kobject ,地位相当于面向对象思想中的总基类, 它充当设备驱动程序模型中各种对象的公共基础,并为这些对象提供基本功能。 以下是有…