1 muduo::net::EchoServer类 参考EchoServer类 参考

EchoServer 的协作图:

在这里插入图片描述

[图例]

Public 成员函数
EchoServer (EventLoop *loop, const InetAddress &listenAddr)
voidstart ()
Private 成员函数
voidonConnection (const TcpConnectionPtr &conn)
voidonMessage (const TcpConnectionPtr &conn, Buffer *buf, Timestamp time)
Private 属性
EventLoop *loop_
TcpServerserver_

在这里插入图片描述

  • 当事件到来,会调用channel::handleEvent()Channel::handleEventWithGuard(Timestamp receiveTime)处理,channel类负责对事件的注册和响应的封装
void Channel::handleEventWithGuard(Timestamp receiveTime) {
eventHandling_ = true; // 将状态置于处理事件中if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) // 如果已经半关闭并且没有数据要读{if (logHup_){LOG_WARN << "Channel::handle_event() POLLHUP";}if (closeCallback_)closeCallback_(); // 关闭的回调函数}if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) // 如果是读事件,需要多传递一个time时间戳对象进去{if (readCallback_)readCallback_(receiveTime);}if (revents_ & POLLOUT) // 如果是写事件{if (writeCallback_) // 并且事件已经被注册过了writeCallback_();}eventHandling_ = false;
}

在这里插入图片描述

  • 更新和注册通道void Channel::update()调用void EventLoop::updateChannel(Channel *channel)
void EventLoop::updateChannel(Channel *channel){assert(channel->ownerLoop() == this); // 断言通道所属的loop对象assertInLoopThread();	 			  // loop是否处于开启事件循环中poller_->updateChannel(channel);	  // 
}

调用void PollPoller::updateChannel(Channel *channel)是一个纯虚函数,重写了父类的updateChannel

void PollPoller::fillActiveChannels(int numEvents,ChannelList *activeChannels) const
{for (PollFdList::const_iterator pfd = pollfds_.begin();pfd != pollfds_.end() && numEvents > 0; ++pfd) // 遍历vector容器std::vector<struct pollfd>{if (pfd->revents > 0){--numEvents;ChannelMap::const_iterator ch = channels_.find(pfd->fd); // 通过文件描述符查找通道assert(ch != channels_.end());	  	// 断言是否能查找到通道Channel *channel = ch->second;	  	// 获取到事件类型assert(channel->fd() == pfd->fd); 	// 对通道的文件描述符进行赋值操作channel->set_revents(pfd->revents); // 设置事件类型activeChannels->push_back(channel);}}
}

EPollPoller事件的注册的机制

class EPollPoller : public Poller
{public:void update(int operation, Channel *channel);private:typedef std::vector<struct epoll_event> EventList;typedef std::map<int, Channel *> ChannelMap; // 文件描述符和管道事件的注册EventList events_;ChannelMap channels_;
};

我写的方法和该方法不同的是,ChannelMap用的是基于对象的回调函数注册方式,我自己使用的是虚函数的方法,论性能和效率,还是回调函数的方法要更好,只是从代码阅读上,比较难以理解。

  • EventLoop构造函数
EventLoop::EventLoop(): looping_(false),quit_(false),eventHandling_(false),threadId_(CurrentThread::tid()),poller_(Poller::newDefaultPoller(this)), // 会构建poller对象,会根据环境变量生成不同的io多路复用对象currentActiveChannel_(NULL){}
Poller* Poller::newDefaultPoller(EventLoop* loop)
{if (::getenv("MUDUO_USE_POLL")){return new PollPoller(loop); // 生成poll}else{return new EPollPoller(loop);// 生成epoll}
}

主要看epoll的封装

如果有返回就绪的事件fd,那么把这个返回集合和负责事件注册的管道压入函数中fillActiveChannels

说的不够清晰上一份伪代码

	int epfd = epoll_create(1);epollAddFd(sfd, epfd);epollAddFd(exitPipe[0],epfd); // 将管道读端纳入监听队列struct epoll_event evs[2]; // 事件的集合int newFd = 0;int sums = 0;while(1){ // 开启不间断的等待轮询-1就是不设置超时sums = epoll_wait(epfd, evs, 2, -1);for(int i = 0; i < sums; ++i){if(evs[i].data.fd == sfd) // evs[i].data.fd就是监听返回的就绪文件描述符跟需要关注的进行比较{newFd = accept(sfd, NULL, NULL);printf("成功建立连接\n");}}}   

struct epoll_event evs[2]就相当于& (*(events_.begin() ) )

sfd 就相当于ChannelList *activeChannels

Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{int numEvents = ::epoll_wait(epollfd_, & (*(events_.begin() ) ),static_cast<int>(events_.size()),timeoutMs);Timestamp now(Timestamp::now());if (numEvents > 0){fillActiveChannels(numEvents, activeChannels); // 在此函数中if (implicit_cast<size_t>(numEvents) == events_.size()) // 如果vector已经到了极限容量,就将其扩容两倍{events_.resize(events_.size() * 2);}}else if (numEvents == 0){LOG_TRACE << " nothing happended";}else{LOG_SYSERR << "EPollPoller::poll()";}return now;
}

fillActiveChannels函数

void EPollPoller::fillActiveChannels(int numEvents,ChannelList *activeChannels) const
{assert(implicit_cast<size_t>(numEvents) <= events_.size()); // 断言返回的就绪事件fd不能乱了套for (unsigned long i = 0; i < static_cast<unsigned>(numEvents); ++i) // 循环扫描就绪的文件描述符{Channel *channel = static_cast<Channel *>(events_[i].data.ptr);  // 返回注册的事件列表,ptr是可以附带在epoll红黑树上的一些关键信息,还需要判定哪些事件返回
#ifndef NDEBUG int fd = channel->fd();ChannelMap::const_iterator it = channels_.find(fd);assert(it != channels_.end());assert(it->second == channel);
#endifchannel->set_revents(static_cast<int>(events_[i].events)); 	// 设置每个管道注册的事件activeChannels->push_back(channel); 						// 把管道事件添加到管道集合中去}
}

那么在调用中是这样使用的,封装起来很复杂

while (!quit_){activeChannels_.clear();pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); // epoll返回的事件要保存到该列表当中if (Logger::logLevel() <= Logger::TRACE){printActiveChannels();}eventHandling_ = true;for (ChannelList::iterator it = activeChannels_.begin();it != activeChannels_.end(); ++it){currentActiveChannel_ = *it;currentActiveChannel_->handleEvent(pollReturnTime_);}currentActiveChannel_ = NULL;eventHandling_ = false; // 终止事件处理标识}
  • updateChannel函数,用于更新和添加事件
void EPollPoller::updateChannel(Channel *channel)
{Poller::assertInLoopThread(); // 断言是否处于io线程LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); // 输出文件描述符和事件类型const int index = channel->index();if (index == kNew || index == kDeleted){// a new one, add with EPOLL_CTL_ADDint fd = channel->fd();if (index == kNew){assert(channels_.find(fd) == channels_.end()); // 断言该文件描述符在管道事件中没有被注册过channels_[fd] = channel;        // 注册事件,跟我自己写的也是差不多的方法 ["cmd 命令"]="task_data* 父类指针"}else // index == kDeleted {assert(channels_.find(fd) != channels_.end());assert(channels_[fd] == channel); // 再次断言,是否已经注册成功}channel->set_index(kAdded);      // 设置这个通道当前状态是已经添加的状态update(EPOLL_CTL_ADD, channel);	 // epoll_ctr_add 添加到红黑树中}else{// …… EPOLL_CTL_MOD}
}
  • update将事件添加到红黑树中
void EPollPoller::update(int operation, Channel *channel)
{struct epoll_event event;bzero(&event, sizeof event);event.events = static_cast<uint32_t>(channel->events()); // epoll关注的读写,超时,以及各种事件,signalfd,管道fdevent.data.ptr = channel;   // 将channel通道作为携带的数据,把通道也给返回回来了,到时候就知道是哪个通道产生得事件int fd = channel->fd();     // 文件描述符// 注册到红黑树上if (::epoll_ctl(epollfd_, operation, fd, &event) < 0){if (operation == EPOLL_CTL_DEL){LOG_SYSERR << "epoll_ctl op=" << operation << " fd=" << fd;}else{LOG_SYSFATAL << "epoll_ctl op=" << operation << " fd=" << fd;}}
}
  • 删除红黑树上的事件关注
void EventLoop::removeChannel(Channel *channel)
{assert(channel->ownerLoop() == this);assertInLoopThread();if (eventHandling_){assert(currentActiveChannel_ == channel ||std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());}poller_->removeChannel(channel);
}
// 由poller重写的removeChannel来调用
void EPollPoller::removeChannel(Channel *channel)
{Poller::assertInLoopThread();int fd = channel->fd();LOG_TRACE << "fd = " << fd;assert(channels_.find(fd) != channels_.end());assert(channels_[fd] == channel);assert(channel->isNoneEvent());int index = channel->index();assert(index == kAdded || index == kDeleted); // 断言是否添加或者删除标识size_t n = channels_.erase(fd);(void)n;assert(n == 1);if (index == kAdded){update(EPOLL_CTL_DEL, channel);}channel->set_index(kNew);
}

2 定时器的实现

Timerld、Timer、TimerQueue

后两个类都是内部实现细节

muduo::net::Timer类 参考 还是对muduo/base/Timestamp.cc进行操作

#include <Timer.h>
Public 成员函数
Timer (const TimerCallback &cb, Timestamp when, double interval)
voidrun () const
Timestampexpiration () const
boolrepeat () const
int64_tsequence () const
voidrestart (Timestamp now)
静态 Public 成员函数
static int64_tnumCreated ()
Private 属性
const TimerCallbackcallback_ 定时器激活的回调函数
Timestampexpiration_ 下一次时间间隔
const doubleinterval_ 超时时间间隔,如果是一次性设置0
const boolrepeat_ 是否重复
const int64_tsequence_ 定时器序号
静态 Private 属性
static AtomicInt64s_numCreated_ 定时器的计数
#ifdef __GXX_EXPERIMENTAL_CXX0X__Timer(TimerCallback&& cb, Timestamp when, double interval): callback_(cb),expiration_(when),interval_(interval),repeat_(interval > 0.0),sequence_(s_numCreated_.incrementAndGet()) // 定时器的序号= 定时器计数+1,计数从0开始// incrementAndGet是一个一个加1的原子操作{ }
void run() const
{callback_(); // 执行回调函数
}
// 重置定时器
void Timer::restart(Timestamp now)
{if (repeat_) // 重新计算下一个超时时刻{expiration_ = addTime(now, interval_); // 将当前时间+时间间隔,得出下一个时刻// 这个对象只有一个64位整数,就懒得用引用传递了,直接把值放到8字节的寄存器当中}else{expiration_ = Timestamp::invalid(); // 如果不是一个重复的定时器,就意味着非法时间}
}

在对用户操作上,定时器并没有去直接调用而是在EventLoop

  1. runAt 在某个时刻运行定时器
  2. runAfter 过一段时间运行定时器
  3. runEvery 每隔一段时间运行定时器
  4. cancel 取消定时器

TimerQueue内部维护了一个定时器列表,Timer只是对定时逻辑的操作并没有直接调用定时器相关函数,是一种高层次的抽象封装

muduo::net::TimerQueue类 参考

#include <TimerQueue.h>

继承自 noncopyable .

muduo::net::TimerQueue 的协作图:
在这里插入图片描述

[图例]

Public 成员函数

Public 成员函数
TimerQueue (EventLoop *loop
~TimerQueue ()
TimerIdaddTimer (const TimerCallback &cb, Timestamp when, double interval)
voidcancel (TimerId timerId)
Private 类型
typedef std::pair< Timestamp, Timer * >Entry
typedef std::set< Entry >TimerList
typedef std::pair< Timer *, int64_t >ActiveTimer
typedef std::set< ActiveTimer >ActiveTimerSet
Private 成员函数
voidaddTimerInLoop (Timer *timer)
voidcancelInLoop (TimerId timerId)
voidhandleRead ()
std::vector< Entry >getExpired (Timestamp now) 返回超时的定时器列表
voidreset (const std::vector< Entry > &expired, Timestamp now)
boolinsert (Timer *timer)
Private 属性
EventLoop *loop_ 所属的eventloop
const inttimerfd_ 保存的是定时器的文件描述符
ChanneltimerfdChannel_ 定时器的通道,当定时器到期可读事件产生,回调函数 handleRead()
TimerListtimers_ 按照到期时间排序的定时器列表
ActiveTimerSetactiveTimers 按照对象地址进行排序的定时器列表
boolcallingExpiredTimers_ 是否处理调用处理超时定时器当中
ActiveTimerSetcancelingTimers_ 被取消的定时器列表

TimerQueue值对外提供了addTimercancel添加和取消定时器

typedef std::pair< Timestamp, Timer * > 存储定时器对象和定时器的地址

typedef std::set< Entry > 存储的是唯一的key值对

typedef std::pair<Timestamp, Timer*> Entry; typedef std::set<Entry> TimerList; // 存储定时器的列表typedef std::pair<Timer*, int64_t> ActiveTimer; // 定时器地址和序号typedef std::set<ActiveTimer> ActiveTimerSet;
// 存储的是一样的,这两个集合都是定时器列表,只是一个以时间排序一个以地址排序

可以被其他线程调用,意味着可以属于不同的eventloop调用

需要能够快速根据当前时间找到已到期的定时器,也要高效的添加和删除定时器,因此使用二叉树

两个对外暴露的接口addTimer添加一个定时器

/// 构造函数
TimerQueue::TimerQueue(EventLoop *loop): loop_(loop),timerfd_(createTimerfd()), // 创建一个定时器timerfdChannel_(loop, timerfd_),timers_(),callingExpiredTimers_(false)
{timerfdChannel_.setReadCallback(boost::bind(&TimerQueue::handleRead, this));timerfdChannel_.enableReading();
}

timerfdChannel_.enableReading();

将定时器的文件描述符添加到epoller类中

->update->UpdateChannel->updateChannel

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WG3jVKPu-1642384902146)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220114105139720.png)]

/*** @brief 增加一个定时器* * @param cb  定时器的回调函数* @param when 超时事件* @param interval 间隔时间* @return TimerId */
TimerId TimerQueue::addTimer(const TimerCallback &cb, Timestamp when,double interval)
{ Timer *timer = new Timer(cb, when, interval); // 构造定时器对象 是线程安全的,并且注册回调函数/* 原有写法,线程安全loop_->runInLoop(boost::bind(&TimerQueue::addTimerInLoop, this, timer));return TimerId(timer, timer->sequence());*/addTimerInLoop(timer);return TimerId(timer,timer->sequence());
}

构造函数中timerfd_(createTimerfd()) 创建一个被用于监听的定时器文件描述符

1 timerfd 介绍

timerfd是Linux为用户程序提供的一个定时器接口。
这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,所以能够被用于select/poll/epoll的应用场景。

man 2 timerfd_create

These system calls create and operate on a timer that delivers timer expiration notifications via a file descriptor. They provide an alternative to the use of setitimer(2) or timer_create(2), with the advantage
that the file descriptor may be monitored by select(2), poll(2), and epoll(7).

// 创建定时器文件描述符
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <atomic>#define handle_error(msg)   \do                      \{                       \perror(msg);        \exit(EXIT_FAILURE); \} while (0)
using namespace std;
atomic<int> fd_;
static void print_elapsed_time()
{static struct timespec start;struct timespec curr;static int first_call = 1;int secs, nsecs;if (first_call){first_call = 0;if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)handle_error("clock_gettime");}if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)handle_error("clock_gettime");secs = curr.tv_sec - start.tv_sec;nsecs = curr.tv_nsec - start.tv_nsec;if (nsecs < 0){secs--;nsecs += 1000000000;}printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
void printids(const char *s)
{uint64_t exp;pid_t pid;pthread_t tid;pid = getpid();tid = pthread_self();int fd = fd_.load();read(fd_.load(),&exp,0);cout<<"exp = "<<(unsigned long long)exp<<endl;printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,(unsigned int)tid, (unsigned int)tid);
}
void *thr_fn(void *arg)
{printids("new thread: ");return NULL;
}int main(int argc, char *argv[])
{itimerspec new_value;int max_exp, fd;struct timespec now;uint64_t exp, tot_exp;ssize_t s;if ((argc != 2) && (argc != 4)){fprintf(stderr, "%s init-secs [interval-secs max-exp]\n",argv[0]);exit(EXIT_FAILURE);}if (clock_gettime(CLOCK_REALTIME, &now) == -1)handle_error("clock_gettime");new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]); // 定时器的初始到期时间 秒new_value.it_value.tv_nsec = now.tv_nsec;           // 纳秒单位if (argc == 2){new_value.it_interval.tv_sec = 0; // 周期性间隔max_exp = 1;}else{new_value.it_interval.tv_sec = atoi(argv[2]); // 定时器的周期性间隔max_exp = atoi(argv[3]);        }new_value.it_interval.tv_nsec = 0;int t = timerfd_create(CLOCK_REALTIME, 0);fd_.exchange(t);fd = timerfd_create(CLOCK_REALTIME, 0); // 创建定时器文件描述符if (fd_.load() == -1)handle_error("timerfd_create");if (timerfd_settime(fd_.load(), TFD_TIMER_ABSTIME, &new_value, NULL) == -1) // 启动定时器文件描述符handle_error("timerfd_settime");int err;pthread_t ntid;print_elapsed_time(); // 打印当前时钟的值printf("timer started\n");for (tot_exp = 0; tot_exp < max_exp;){cout << "等待中" << endl;err = pthread_create(&ntid, NULL, thr_fn, NULL);s = read(fd_.load(), &exp, sizeof(uint64_t)); // 一旦文件描述符就绪了,才会解除阻塞状态if (s != sizeof(uint64_t))handle_error("read");tot_exp += exp;print_elapsed_time();printf("read: %llu; total=%llu\n",(unsigned long long)exp,(unsigned long long)tot_exp);}exit(EXIT_SUCCESS);
}
$g++ test_timer.cc -lpthread -std=c++11
$./a.out 2 5 2 
# 定时器测试用例
2 创建定时器,设置非阻塞模式
int createTimerfd(){int timerfd = ::timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK | TFD_CLOEXEC); // 创建一个定时器,非阻塞模式if (timerfd < 0){LOG_SYSFATAL << "Failed in timerfd_create";}return timerfd;}
3 插入定时器

该接口对外暴露的是addTimer 真正工作的是insert函数,具体实现并不暴露给用户

void TimerQueue::addTimerInLoop(Timer *timer)
{loop_->assertInLoopThread();bool earliestChanged = insert(timer); // 插入一个定时器时,它的过期时间可能会影响到最先到期的定时器,改变序列if (earliestChanged) // 是否需要改变序列,并且重置定时器的超时时刻{resetTimerfd(timerfd_, timer->expiration());}
}
1 对内封装的插入实现
bool TimerQueue::insert(Timer *timer)
{loop_->assertInLoopThread();assert(timers_.size() == activeTimers_.size()); // 断言,两个列表大小是否一样bool earliestChanged = false;					// 到期时间是否需要改变标志位Timestamp when = timer->expiration();			// 取出要添加的定时器的到期时间TimerList::iterator it = timers_.begin();		// 时间最早的定时器,set默认是从小到大排列的if (it == timers_.end() || when < it->first)	// 如果新插入的定时器到期时间小于 容器中定时器的最小时间 或者该容器为空的情况下{earliestChanged = true;						// 将标志置为1}{std::pair<TimerList::iterator, bool> result = timers_.insert(Entry(when, timer)); // 把新的定时器分别插入到两个容器中去assert(result.second);(void)result; // 写法还是为了取消wall}{std::pair<ActiveTimerSet::iterator, bool> result = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));assert(result.second);(void)result;}assert(timers_.size() == activeTimers_.size());return earliestChanged;
}
2 插入时需要重置超时时刻
void resetTimerfd(int timerfd, Timestamp expiration) // 把当前新插入的超时时间取出来{struct itimerspec newValue{};struct itimerspec oldValue{}; // 这里稍微改了一下,使用列表初始化为空就不需要调用memset或者bzero了newValue.it_value = howMuchTimeFromNow(expiration); // 是把muduo/base的类转成了一个可用于时间的类型int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); // 把新的超时时间设置进去if (ret){ LOG_SYSERR << "timerfd_settime()";}}
3 重置时需要对时间类和时间戳进行换算
struct timespec howMuchTimeFromNow(Timestamp when){int64_t microseconds = when.microSecondsSinceEpoch() - Timestamp::now().microSecondsSinceEpoch(); // 获取传入的时间戳-当前的时间戳if (microseconds < 100){microseconds = 100; // 精确到100}struct timespec ts;ts.tv_sec = static_cast<time_t>(microseconds / Timestamp::kMicroSecondsPerSecond); // 除以100W 换算到秒单位ts.tv_nsec = static_cast<long>((microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); // 换算到纳秒单位return ts;}

3 定时器回调事件

TimerQueue::TimerQueue(EventLoop *loop)中已经通过setReadCallback设置了事件回调函数:TimerQueue::handleRead把传入的所属eventloop和定时器文件描述符传入

void TimerQueue::handleRead()
{loop_->assertInLoopThread();Timestamp now(Timestamp::now()); // 获取当前时间readTimerfd(timerfd_, now);		 // 处理就绪的文件描述符std::vector<Entry> expired = getExpired(now); // 获取该时刻的所有定时器列表,即超时被就绪的定时器callingExpiredTimers_ = true; // 处于处理定时器到期状态中cancelingTimers_.clear();	  // 把已经被取消的定时器清空掉for (std::vector<Entry>::iterator it = expired.begin();it != expired.end(); ++it){it->second->run(); // 循环调用定时器处理的回调函数}callingExpiredTimers_ = false; // 处理完毕reset(expired, now); // 不是一次性定时器,需要重启
}
1 处理就绪定时器文件描述符
void readTimerfd(int timerfd, Timestamp now){uint64_t howmany;ssize_t n = ::read(timerfd, &howmany, sizeof howmany);LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();if (n != sizeof howmany){LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";}}
2 获取该时刻所有定时器,即超时的定时器

getExpired (Timestamp now) 返回超时的定时器列表

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{assert(timers_.size() == activeTimers_.size());std::vector<Entry> expired;Entry sentry(now, reinterpret_cast<Timer *>(UINTPTR_MAX));// UINTPTR_MAX设置最大地址// 防止有可能,now 和Timer是一样大的TimerList::iterator end = timers_.lower_bound(sentry);// lower_bound二分查找返回第一个值 >= sentry的迭代器,返回第一个大于当前时间片的迭代器位置assert(end == timers_.end() || now < end->first); // 断言当前时间片的定时器是最大的,跑到整个迭代器后面了// 或者当前时间小于返回的第一个大于当前的时间的位置// 如果now == *end(),会激发UINTPTR_MAX的作用,哨兵值std::copy(timers_.begin(), end, back_inserter(expired));// 把从begin()->第一个未到期的定时器的区间,用push_back()的方法拷贝到expired列表里面timers_.erase(timers_.begin(), end); // 把到期的定时器给移除for (std::vector<Entry>::iterator it = expired.begin();it != expired.end(); ++it){ActiveTimer timer(it->second, it->second->sequence());size_t n = activeTimers_.erase(timer); // 移除到期定时器assert(n == 1);(void)n; }assert(timers_.size() == activeTimers_.size()); // 断言两个容器是否保持一致性return expired;
}

queue只关注最早的定时器时间片
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBfNc2Pm-1642384902147)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220114162301154.png)]
在这里插入图片描述

3 使用实例
/// 探究是否像对源码分析一样定时器重置条件
void prints(const char *msg)
{printf("==========================\n,%s\n",msg);
}
int main()
{printTid();sleep(1);{EventLoop loop;g_loop = &loop;print("main");loop.runAfter(2,boost::bind(prints,"打我呀"));// 注册的回调函数和定时器,分别是2秒和1秒loop.runAfter(1,boost::bind(prints,"哈哈哈"));loop.loop(); // 开启事件轮询print("main loop exits");}
}

输出结果

因为1秒这个定时器,会导致之前2秒的定时器不再是最小的过期时间了

msg 1642152525.200913 main
插入一个定时器
需要重置定时器的超时时刻
插入一个定时器
需要重置定时器的超时时刻
20220114 09:28:45.200953Z 19142 TRACE loop EventLoop 0x7FFD2E1BA050 start looping - EventLoop.cc:70
20220114 09:28:46.200961Z 19142 TRACE poll 1 events happended - EPollPoller.cc:65
20220114 09:28:46.201379Z 19142 TRACE printActiveChannels {4: IN } - EventLoop.cc:163

4 重启非一次性定时器
void TimerQueue::reset(const std::vector<Entry> &expired, Timestamp now)
{Timestamp nextExpire;for (std::vector<Entry>::const_iterator it = expired.begin();it != expired.end(); ++it){ActiveTimer timer(it->second, it->second->sequence());// 如果是重复的定时器并且是未取消定时器,则重启该定时器if (it->second->repeat() && cancelingTimers_.find(timer) == cancelingTimers_.end()){it->second->restart(now);insert(it->second);}else{// 一次性定时器或者已被取消的定时器是不能重置的,因此删除该定时器delete it->second; // FIXME: no delete please}}if (!timers_.empty()){// 获取最早到期的定时器超时时间nextExpire = timers_.begin()->second->expiration();}if (nextExpire.valid()){// 重置定时器的超时时刻(timerfd_settime)resetTimerfd(timerfd_, nextExpire);}
}
5 取消定时器

TimerId一个可见的类,用于取消定时器

class TimerId : public muduo::copyable
{public:TimerId(): timer_(NULL),sequence_(0){}TimerId(Timer* timer, int64_t seq): timer_(timer),sequence_(seq){}friend class TimerQueue;private:Timer* timer_; // 定时器的地址int64_t sequence_;// 定时器的序号
};
}
}
void TimerQueue::cancelInLoop(TimerId timerId)
{loop_->assertInLoopThread();assert(timers_.size() == activeTimers_.size());ActiveTimer timer(timerId.timer_, timerId.sequence_);ActiveTimerSet::iterator it = activeTimers_.find(timer); // 查找要取消掉的定时器是否在正常列表中if (it != activeTimers_.end()) // 如果找到了{size_t n = timers_.erase(Entry(it->first->expiration(), it->first));assert(n == 1);(void)n;delete it->first; // FIXME: no delete pleaseactiveTimers_.erase(it); // 两个列表的都要移除}else if (callingExpiredTimers_) // 如果不在列表中,说明已经被移除了{cancelingTimers_.insert(timer); // 把定时器插入到取消列表中}assert(timers_.size() == activeTimers_.size());
}

pyable
{
public:
TimerId()
: timer_(NULL),
sequence_(0)
{
}

TimerId(Timer* timer, int64_t seq)
timer_(timer),
sequence_(seq)
{
}
friend class TimerQueue;
private:
Timer* timer_; // 定时器的地址
int64_t sequence_;// 定时器的序号
};
}
}

```cpp
void TimerQueue::cancelInLoop(TimerId timerId)
{loop_->assertInLoopThread();assert(timers_.size() == activeTimers_.size());ActiveTimer timer(timerId.timer_, timerId.sequence_);ActiveTimerSet::iterator it = activeTimers_.find(timer); // 查找要取消掉的定时器是否在正常列表中if (it != activeTimers_.end()) // 如果找到了{size_t n = timers_.erase(Entry(it->first->expiration(), it->first));assert(n == 1);(void)n;delete it->first; // FIXME: no delete pleaseactiveTimers_.erase(it); // 两个列表的都要移除}else if (callingExpiredTimers_) // 如果不在列表中,说明已经被移除了{cancelingTimers_.insert(timer); // 把定时器插入到取消列表中}assert(timers_.size() == activeTimers_.size());
}
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 信号系统与列车安全

    一、故障-安全技术 故障-安全是指系统任何部分发生故障时&#xff0c;系统的输出均处于安全状态。即信号系统或设备出现故障&#xff0c;不会危及行车安全。高铁信号系统安全相关部件全部按照故障-安全原则设计。 安全完整性级别&#xff08;Safety Integrity Level&#xff…...

    2024/5/6 1:44:15
  2. Android的websocket的应用

    最近要做一个基于车载设备的应用设计&#xff0c;需要使用基于H5的mui跨平台框架为客户端&#xff0c;通过服务器&#xff0c;连接车载的Android中控设备。想了一下&#xff0c;觉得可以用websocket尝试一下。 开发环境&#xff1a; 服务器&#xff1a;ideajavatomcat9.0 h5客户…...

    2024/4/28 4:11:04
  3. 【C语言】百钱买百鸡

    题目&#xff1a;百钱买百鸡问题。已知公鸡每只 5 元&#xff0c;母鸡每只 3 元&#xff0c;小鸡 1 元 3 只&#xff0c;要求 100 元钱正好买 100 只 鸡&#xff0c;则应买公鸡、母鸡的小鸡各多少只&#xff1f; 思路&#xff1a;三层嵌套循环&#xff0c;分别设三个未知数——…...

    2024/4/14 17:59:05
  4. ZMQ相关函数记录

    1. zmsg_first() zframe_t* frame zmsg_first(msg); // Set cursor to first frame in message. Returns frame, or NULL, if the // message is empty. Use this to navigate the frames as a list. // ​​​​​​​将光标设置为消息中的第一帧。 返回frame&#xff0c…...

    2024/4/14 18:00:10
  5. docker镜像的创建与dockerfile

    文章目录一、docker镜像的创建1、创建镜像的方法2、基于现有镜像创建3、基于本地模板创建4、基于dockerfile创建二、Dockerfile1、概述2、Dockerfile结构3、Dockerfile镜像结构的分层4、Dockerfile操作常用的指令5、在编写Dockerfile时&#xff0c;要遵循的格式三、Dockerfile案…...

    2024/4/14 17:59:40
  6. 记录TextView实现阴影效果(day02)

    你是不是也想让自己的文字显得与众不同&#xff1f;废话不多说&#xff0c;直接上代码&#xff1a; <TextViewandroid:layout_width"match_parent"android:layout_height"wrap_content"android:text"你好世界&#xff01;"android:textStyle&…...

    2024/4/7 4:03:35
  7. 什么是爬虫|Python爬虫的原理是什么

    前言 简单来说互联网是由一个个站点和网络设备组成的大网&#xff0c;我们通过浏览器访问站点&#xff0c;站点把HTML、JS、CSS代码返回给浏览器&#xff0c;这些代码经过浏览器解析、渲染&#xff0c;将丰富多彩的网页呈现我们眼前&#xff1b; 一、爬虫是什么&#xff1f; 如…...

    2024/4/14 17:59:50
  8. 高效时代,是时候过渡到自动化测试了

    由 OverOps 发布的《 2020 年软件质量状况调查》显示&#xff0c;70%的工程组织认为&#xff0c;软件质量的重要性胜过交付速度&#xff0c;与此同时&#xff0c;软件交付速度比以往任何时候都更快。超过一半的受访者表示&#xff0c;他们会在两周内多次发布新代码/功能&#x…...

    2024/4/14 18:00:05
  9. Mybatis 核心源码分析

    一、Mybatis 整体执行流程 二、Mybatis 具体流程源码分析 三、源码分析 写一个测试类&#xff0c;来具体分析Mybatis 的执行流程&#xff1a; public class MybatisTest {public static void main(String[] args) throws IOException {//1. 读取mybatis-config.xml 文件InputS…...

    2024/4/17 21:28:24
  10. Win10安装Python环境

    概述 由于家里的电脑刚好&#xff0c;刚装完了Java环境&#xff0c;现在要接着弄Python环境&#xff0c;我这次直接安装Anaconda来使用&#xff0c;而且Anaconda还自带Jupyter Notebook。 安装Anaconda 先去官网下载Anaconda安装包&#xff0c;装的是当前最新版本“Anaconda…...

    2024/4/15 5:07:52
  11. 低代码如何能占领企业数字化转型的C位?

    低代码平台正呈现百花齐放的局面&#xff0c;不少互联网公司为了解决生态伙伴、客户业务共创的需求&#xff0c;也在推出低代码平台。目前&#xff0c;低代码平台处在发展期&#xff0c;更多的是作为企业解决数字化落地问题的工具平台&#xff0c;未来可以预测到的是&#xff0…...

    2024/4/18 13:49:49
  12. 2022国内最安全的邮箱是哪个?注册公司企业邮箱如何选择

    首先&#xff0c;企业邮箱是个人邮箱的升级版&#xff0c;拥有更多的特权功能和服务&#xff0c;为企业公司提供邮箱服务&#xff0c;在新的一年中&#xff0c;TOM企业邮箱特意为不同的用户群体制定了邮箱注册方案。一起来看看吧。 2022年是全新的一年&#xff0c;不少公司为企…...

    2024/4/18 22:23:15
  13. 中国基站射频设备行业市场供需与战略研究报告

    出版商&#xff1a;贝哲斯咨询 获取报告样本&#xff1a; 企业竞争态势 该报告涉及的主要国际市场参与者有Sumitomo Electric Device Innovations、Wolfspeed、Qorvo、MACOM、Ampleon、RFHIC、Analog Devices、Broadcom Limited、TDK等。这些参与者的市场份额、收入、公司概况…...

    2024/4/18 19:48:43
  14. SpringCloud大型企业分布式微服务云架构源码之传统架构与分布式架构

    传统架构 思考&#xff1a;有什么问题&#xff1f; 模块之间耦合度太高&#xff0c;其中一个升级其他都得升级开发困难&#xff0c;各个团队开发最后都要整合一起系统的扩展性差不能灵活的进行分布式部署。 需要框架源码的朋友可以看我个人简介联系我&#xff0c;推荐分布式架…...

    2024/4/14 18:00:10
  15. IDEA必备插件

    Free MyBatis plugin 官方已经查不到了&#xff0c;一个免费的比收费的还好用&#xff0c;能不下架吗 FindBug 这个插件可以帮助我们查找隐藏的bug,比较重要的功能就是查找潜在的null指针。 在编写代码的过程中,我们可能不会一直记得检查空的引用,在我们测试时可能很难发现问…...

    2024/4/18 12:43:43
  16. Vue — 详解mixins混入使用

    前言 当我们的项目越来越大&#xff0c;我们会发现组件之间可能存在很多相似的功能&#xff0c;你在一遍又一遍的复制粘贴相同的代码段&#xff08;data&#xff0c;method&#xff0c;watch、mounted等&#xff09;&#xff0c;如果我们在每个组件中去重复定义这些属性和方法会…...

    2024/4/5 5:19:21
  17. ClickHouse插入数据重复

    使用spark&#xff0c;多线程写入clickhouse 第一次建表时设置ENGINE MergeTree&#xff0c;PARTITION BY和ORDER BY字段相同。 最终表中的数据发生重复。 CREATE TABLE dev.test_risk_full ( ) ENGINE MergeTree PARTITION BY ORGCD ORDER BY ID SETTINGS index_granulari…...

    2024/4/14 17:59:50
  18. 冰河在大学是如何度过的?

    大家好&#xff0c;我是冰河~~ 今天我们就不聊技术了&#xff0c;跟大家聊聊我有趣并充实的大学生活&#xff0c;以及我跟两个好基友&#xff1a;二神和波妞的故事。 我的两个好基友 说起我的大学生活&#xff0c;不得不说的就是我的两个好基友&#xff0c;二神和波妞&#…...

    2024/4/14 18:00:05
  19. 商标权如何换到新公司名下

    一、商标权如何换到新公司名下 1、依据我国商标法的规定&#xff0c;商标权人想将商标变更到新公司名下的&#xff0c;可以将商标权转让给新公司&#xff0c;签订转让协议&#xff0c;并且向商标管理部门登记。 2、法律规定&#xff1a;《中华人民共和国商标法》 第四十二条…...

    2024/4/27 9:46:10
  20. 【七】、Redis.conf配置文件分析

    七、Redis.conf 启动的时候就是通过配置文件来启动的 1.单位 容量单位不区分大小写&#xff0c;G和GB有区别 也可以使用include组合多个配置文件 2.网络NETWORK 网络配置 bind 127.0.0.1 网络 #绑定的ip protect-mode yes #保护模式 port 6379 #端口设置3.通用GENERAL daem…...

    2024/4/23 18:23:42

最新文章

  1. stm32单片机开发二、定时器-内部时钟中断和外部时钟中断、编码器

    定时器本质就是一个计数器 案例&#xff1a;定时器定时中断 内部时钟中断 Timer_Init(); //定时中断初始化 /*** 函 数&#xff1a;定时中断初始化* 参 数&#xff1a;无* 返 回 值&#xff1a;无*/ void Timer_Init(void) {/*开启时钟*/RCC_APB1PeriphClockCmd(RCC…...

    2024/5/6 2:36:23
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 前端三剑客 —— JavaScript (第四节)

    目录 内容回顾&#xff1a; 函数 *** 什么是函数 函数定义 函数调用 函数使用示例 匿名函数 无参函数 箭头函数 1、无参无返回值 2、无参有返回值 3、无参有返值&#xff0c;但函数体只有一条语句&#xff0c;则大括号可以省略&#xff0c; return 语句可以省略 4…...

    2024/5/4 7:36:19
  4. ssm框架中各层级介绍

    1、Spring&#xff08;业务逻辑层&#xff09;&#xff1a; Spring框架提供了依赖注入&#xff08;DI&#xff09;和面向切面编程&#xff08;AOP&#xff09;等功能&#xff0c;可以帮助管理Java应用程序中的对象依赖关系和提供横切关注点的支持。 在SSM框架中&#xff0c;S…...

    2024/5/3 3:42:05
  5. xv6项目开源—05

    xv6项目开源—05.md 理论&#xff1a; 1、设备驱动程序在两种环境中执行代码&#xff1a;上半部分在进程的内核线程中运行&#xff0c;下半部分在中断时执行。上半部分通过系统调用进行调用&#xff0c;如希望设备执行I/O操作的read和write。这段代码可能会要求硬件执行操作&…...

    2024/5/2 2:36:53
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/4 23:55:17
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57