[zz]libev 简介

 

1 libev

libev所实现的功能就是一个强大的reactor,可能notify事件主要包括下面这些:

  • ev_io // IO可读可写
  • ev_stat // 文件属性变化
  • ev_async // 激活线程
  • ev_signal // 信号处理
  • ev_timer // 定时器
  • ev_periodic // 周期任务
  • ev_child // 子进程状态变化
  • ev_fork // 开辟进程
  • ev_cleanup // event loop退出触发事件
  • ev_idle // 每次event loop空闲触发事件
  • ev_embed // TODO(zhangyan04):I have no idea.
  • ev_prepare // 每次event loop之前事件
  • ev_check // 每次event loop之后事件

1.1 About The Code

代码风格相当严谨而且排版也非常工整,并且从域名看出作者是德国人。但是内部使用了大量的宏造成阅读代码并不是非常方便。 并且从代码角度分析,应该是一开始支持有一个默认的event_loop,但是随着多核产生实际应用中可能会使用到多个event_loop, 猜想作者应该是为了方便的话使用了很多宏进行替换。允许使用多个event_loop的宏是EV_MULTIPLICITY.比如下面这段代码

void noinline
ev_io_start (EV_P_ ev_io *w)
{int fd = w->fd;if (expect_false (ev_is_active (w)))return;assert (("libev: ev_io_start called with negative fd", fd >= 0));assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));EV_FREQUENT_CHECK;ev_start (EV_A_ (W)w, 1);array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);wlist_add (&anfds[fd].head, (WL)w);fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);w->events &= ~EV__IOFDSET;EV_FREQUENT_CHECK;
}

初次阅读这个代码会觉得非常难懂。

说明定义
EV_Pevent parameterstruct ev_loop *loop
EV_P_ EV_P,
EV_Aevent argumentloop
EV_A_ EV_A,
然后很多变量只要是ev_loop成员的话都被封装成为了宏。比如代码里面的anfds,实际上的宏定义是
#define anfds ((loop)->anfds)

事实上一个ev_loop里面的字段是相当多的,不过也很正常本身就是一个强大的reactor.但是这造成一个直接后果, 就是对于想要了解ev_loop的全貌比较困难,所以想要彻底地了解libev也比较麻烦,所以我们只能够从应用层面来尝试了解它。

1.2 EventLoop

首先我们关注一下reactor本身。在libev下面reactor对象称为event_loop.event_loop允许动态创建和销毁,并且允许绑定自定义数据

struct ev_loop * ev_loop_new (unsigned int flags);
void ev_loop_destroy (EV_P);
void ev_set_userdata (EV_P_ void *data);
void *ev_userdata (EV_P);

我们这里主要关注一下flags.这里面主要是选择使用什么backend来进行poll操作,可以选择的有:

  • EVBACKEND_SELECT
  • EVBACKEND_POLL
  • EVBACKEND_EPOLL // 通常我们选择这个
  • EVBACKEND_KQUEUE
  • EVBACKEND_DEVPOLL
  • EVBACKEND_PORT

但是还有三个比较重要选项:

  • EVFLAG_NOINOTIFY // 不适用inofity调用来使用ev_stat.这样可以减少fd使用。
  • EVFLAG_SIGNALFD // 使用signalfd来检测信号是否发生,同样这样可以减少fd使用。

大部分时候我们使用EVFLAG_AUTO(0)一般就足够满足需求了,从代码角度来看如果支持epoll的话那么首先会选择epoll. 因为在watcher的回调函数里面是可以知道当前event_loop的,这样就可以获得自定义数据。然后我们看看这个event_loop如何运行和停止的

void ev_run (EV_P_ int flags);
void ev_break (EV_P_ int how);

同样我们这里比较关注flags和how这两个参数。flags有下面这几个:

  • 0.默认值。一直循环进行处理,直到外部引用计数==0或者是显示退出。
  • EVRUN_NOWAIT.运行一次,poll时候不会等待。如果有pending事件进行处理,否则立即返回。
  • EVRUN_ONCE.运行一次,poll时候会等待至少一个event发生,处理完成之后返回。

而how有下面这几个:

  • EVBREAK_ONE.只是退出一次ev_run这个调用。通常来说使用这个就可以了。
  • EVBREAK_ALL.退出所有的ev_run调用。这种情况存在于ev_run在pengding处理时候会递归调用。

在backend/epoll底层每次epoll_wait时候,libev提供了接口回调可以在epoll_wait前后调用

void ev_set_loop_release_cb (loop, void (*release)(EV_P), void (*acquire)(EV_P))
static void
epoll_poll (EV_P_ ev_tstamp timeout)
{/* epoll wait times cannot be larger than (LONG_MAX - 999UL) / HZ msecs, which is below *//* the default libev max wait time, however. */EV_RELEASE_CB;eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax,epoll_epermcnt ? 0 : ev_timeout_to_ms (timeout));EV_ACQUIRE_CB;
}

在event_loop里面我们还关心一件事情,就是每次event_loop轮询的时间长短。通常来说这个不会是太大问题,但是在高性能情况下面我们需要设置

void ev_set_io_collect_interval (EV_P_ ev_tstamp interval);
void ev_set_timeout_collect_interval (EV_P_ ev_tstamp interval);

在ev_run里面有使用这些参数的代码比较麻烦。但是大意是这样,如果我们这是了timeout_interval的话,那么我们每次检查timeout时间的话必须 在timeout_interval,使用这段时间ev_sleep.但是这个又会影响到io_interval,所以内部做了一些换算,换算的结果作为epoll_wait超时时间。 不过同样在大部分时候我们不需要关心它,默认时候是0.0,系统会使用最快的响应方式来处理。

1.3 Watcher

然后我们关心一下EventHandler.在libev下面watcher相当于EventHandler这么一个概念,通常里面会绑定fd回调函数以及我们需要关注的事件。 然后一旦触发事件之后会触发我们使用的回调函数,回调函数参数通常有reactor,watcher以及触发的事件。这里不打算重复文档里面的watcher 相关的内容和对应的API,但是对于某些内容的话可能会提到并且附带一些注释。之前我们还是看看通用过程,这里使用TYPE区分不同类型watcher.

typedef void (*)(struct ev_loop *loop, ev_TYPE *watcher, int revents) callback; // callback都是这种类型
ev_init (ev_TYPE *watcher, callback); // 初始化watcher
ev_TYPE_set (ev_TYPE *watcher, [args]); // 设置watcher
ev_TYPE_init (ev_TYPE *watcher, callback, [args]); // 通常使用这个函数最方便,初始化和设置都在这里
ev_TYPE_start (loop, ev_TYPE *watcher); // 注册watcher
ev_TYPE_stop (loop, ev_TYPE *watcher); // 注销watcher
ev_set_priority (ev_TYPE *watcher, int priority); // 设置优先级
ev_feed_event (loop, ev_TYPE *watcher, int revents); // 这个做跨线程通知非常有用,相当于触发了某个事件。
bool ev_is_active (ev_TYPE *watcher); // watcher是否active.
bool ev_is_pending (ev_TYPE *watcher); // watcher是否pending.
int ev_clear_pending (loop, ev_TYPE *watcher); // 清除watcher pending状态并且返回事件

wacther的状态有下面这么几种:

  • initialiased.调用init函数初始化
  • active.调用start进行注册
  • pending.已经触发事件但是没有处理
  • inactive.调用stop注销。这个状态等同于initialised这个状态。

其实关于每个watcher具体是怎么实现的没有太多意思,因为大部分现有代码都差不多。会在下一节说说内部数据结构是怎么安排的, 了解内部数据结构以及过程之后很多问题就可以避免了,比如"The special problem of disappearing file descriptors"这类问题。

1.4 How it works

1.4.1 ev_run

最主要的还是看看ev_run这个部分代码。我们不打算仔细阅读只是看看梗概然后大体分析一下数据结构应该怎么样的

void
ev_run (EV_P_ int flags)
{assert (("libev: ev_loop recursion during release detected", loop_done != EVBREAK_RECURSE));loop_done = EVBREAK_CANCEL;EV_INVOKE_PENDING; /* in case we recurse, ensure ordering stays nice and clean */do{if (expect_false (loop_done))break;/* update fd-related kernel structures */fd_reify (EV_A);/* calculate blocking time */{ev_tstamp waittime  = 0.;ev_tstamp sleeptime = 0.;/* remember old timestamp for io_blocktime calculation */ev_tstamp prev_mn_now = mn_now;/* update time to cancel out callback processing overhead */time_update (EV_A_ 1e100);if (expect_true (!(flags & EVRUN_NOWAIT || idleall || !activecnt))){waittime = MAX_BLOCKTIME;if (timercnt){ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now + backend_fudge;if (waittime > to) waittime = to;}/* don't let timeouts decrease the waittime below timeout_blocktime */if (expect_false (waittime < timeout_blocktime))waittime = timeout_blocktime;/* extra check because io_blocktime is commonly 0 */if (expect_false (io_blocktime)){sleeptime = io_blocktime - (mn_now - prev_mn_now);if (sleeptime > waittime - backend_fudge)sleeptime = waittime - backend_fudge;if (expect_true (sleeptime > 0.)){ev_sleep (sleeptime);waittime -= sleeptime;}}}assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */backend_poll (EV_A_ waittime);assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect *//* update ev_rt_now, do magic */time_update (EV_A_ waittime + sleeptime);}/* queue pending timers and reschedule them */timers_reify (EV_A); /* relative timers called last */EV_INVOKE_PENDING;}while (expect_true (activecnt&& !loop_done&& !(flags & (EVRUN_ONCE | EVRUN_NOWAIT))));if (loop_done == EVBREAK_ONE)loop_done = EVBREAK_CANCEL;
}

我们可以总结一下大致步骤,其实和大部分的event loop写出来差不多。

  • 首先触发那些已经pending的watchers.
  • 判断是否loop_done
  • fd_reify.这个后面会单独说。
  • 计算出waittime并且进行必要的sleep.
  • backend_poll开始轮询,并且整理好pending事件
  • timers_reify.这个和fd_reify不同
  • 调用EV_INVOKE_PENDING来触发pending的io事件

非常简单。接下来我们看看fd_reify,backend_poll,timers_reify以及EV_INVOKE_PENDING.

1.4.2 fd_reify

下面是fd_reify代码片段.可以看出,这个部分就是在修改fd关注的events。

inline_size void
fd_reify (EV_P)
{int i;for (i = 0; i < fdchangecnt; ++i){int fd = fdchanges [i];ANFD *anfd = anfds + fd;ev_io *w;unsigned char o_events = anfd->events;unsigned char o_reify  = anfd->reify;anfd->reify  = 0;/*if (expect_true (o_reify & EV_ANFD_REIFY)) probably a deoptimisation */{anfd->events = 0;for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)anfd->events |= (unsigned char)w->events;if (o_events != anfd->events)o_reify = EV__IOFDSET; /* actually |= */}if (o_reify & EV__IOFDSET)backend_modify (EV_A_ fd, o_events, anfd->events);}fdchangecnt = 0;
}

而这个fdchanges这个是在哪里调用的呢。我们可以看到就是在ev_io_start这个部分。也就是说如果我们想要修改 fd关注事件的话,我们必须显示地ev_io_stop掉然后修正之后重新ev_io_start.底层调用fd_change的话底层维护 数组fdchanges来保存发生events变动的fd.

void noinline
ev_io_start (EV_P_ ev_io *w)
{int fd = w->fd;if (expect_false (ev_is_active (w)))return;assert (("libev: ev_io_start called with negative fd", fd >= 0));assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));EV_FREQUENT_CHECK;ev_start (EV_A_ (W)w, 1);array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);wlist_add (&anfds[fd].head, (WL)w);fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);w->events &= ~EV__IOFDSET;EV_FREQUENT_CHECK;
}inline_size void
fd_change (EV_P_ int fd, int flags)
{unsigned char reify = anfds [fd].reify;anfds [fd].reify |= flags;if (expect_true (!reify)){++fdchangecnt;array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);fdchanges [fdchangecnt - 1] = fd;}
}

1.4.3 backend_poll

backend_poll底层支持很多poll实现,我们这里仅仅看ev_epoll.c就可以.代码在这里面我们不列举了, 如果某个fd触发事件的话那么最终会调用fd_event(EV_A_,fd,event)来进行通知。所以我们看看fd_event.

inline_speed void
fd_event_nocheck (EV_P_ int fd, int revents)
{ANFD *anfd = anfds + fd;ev_io *w;for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next){int ev = w->events & revents;if (ev)ev_feed_event (EV_A_ (W)w, ev);}
}
void noinline
ev_feed_event (EV_P_ void *w, int revents)
{W w_ = (W)w;int pri = ABSPRI (w_);if (expect_false (w_->pending))pendings [pri][w_->pending - 1].events |= revents;else{w_->pending = ++pendingcnt [pri];array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2);// set the watcher and revents.pendings [pri][w_->pending - 1].w      = w_;pendings [pri][w_->pending - 1].events = revents;}
}

可以看到底层是一个ANFD的数组,根据fd进行偏移。如果fd过大的话似乎会影响性能没有hpserver里面的demuxtable实现方式好。 然后得到这个fd下面所有的watcher,然后在loop->pendings里面记录所有这些触发的watcher.

1.4.4 timers_reify

其中HEAP0就是最小堆下标。如果repeat的话说明需要重复发生,那么就会重新调整时间戳,如果不是repeat的话, 那么内部会调用ev_timer_stop这个方法将这个计时器移除。所有的定时任务都通过feed_reverse添加。feed_reverse 内部是维护一个动态数组来保存所有的定时器任务,然后在feed_reverse_done里面遍历这些任务来触发这些定时器任务。

inline_size void
timers_reify (EV_P)
{EV_FREQUENT_CHECK;if (timercnt && ANHE_at (timers [HEAP0]) < mn_now){do{ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);/*assert (("libev: inactive timer on timer heap detected", ev_is_active (w)));*//* first reschedule or stop timer */if (w->repeat){ev_at (w) += w->repeat;if (ev_at (w) < mn_now)ev_at (w) = mn_now;assert (("libev: negative ev_timer repeat value found while processing timers", w->repeat > 0.));ANHE_at_cache (timers [HEAP0]);downheap (timers, timercnt, HEAP0);}elseev_timer_stop (EV_A_ w); /* nonrepeating: stop timer */EV_FREQUENT_CHECK;feed_reverse (EV_A_ (W)w);}while (timercnt && ANHE_at (timers [HEAP0]) < mn_now);feed_reverse_done (EV_A_ EV_TIMER);}
}

1.4.5 EV_INVOKE_PENDING

这个宏最终调用的函数就是下面这个,遍历所有的pendings事件并且逐一触发。

void noinline
ev_invoke_pending (EV_P)
{int pri;for (pri = NUMPRI; pri--; )while (pendingcnt [pri]){ANPENDING *p = pendings [pri] + --pendingcnt [pri];p->w->pending = 0;EV_CB_INVOKE (p->w, p->events);EV_FREQUENT_CHECK;}
}

1.5 Example

尝试编写一个简单的带有超时的echo-server和echo-client就发现其实还有非常多的其他的工作量,比如buffer的管理状态机实现等。 所以我没有写出一个完整的example,只是简单地写了假设echo-client连接上server的话就简单地打印链接信息并且关闭。

1.5.1 common.h

#ifndef _COMMON_H_
#define _COMMON_H_#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <cstdlib>
#include <cstdio>
#include <cstddef>
#include <string>namespace common{#define D(exp,fmt,...) do {                     \if(!(exp)){                             \fprintf(stderr,fmt,##__VA_ARGS__);  \abort();                            \}                                       \}while(0)static void setnonblock(int fd){fcntl(fd,F_SETFL,fcntl(fd,F_GETFL) | O_NONBLOCK);
}
static void setreuseaddr(int fd){int ok=1;setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&ok,sizeof(ok));
}static void setaddress(const char* ip,int port,struct sockaddr_in* addr){bzero(addr,sizeof(*addr));addr->sin_family=AF_INET;inet_pton(AF_INET,ip,&(addr->sin_addr));addr->sin_port=htons(port);
}static std::string address_to_string(struct sockaddr_in* addr){char ip[128];inet_ntop(AF_INET,&(addr->sin_addr),ip,sizeof(ip));char port[32];snprintf(port,sizeof(port),"%d",ntohs(addr->sin_port));std::string r;r=r+"("+ip+":"+port+")";return r;
}static int new_tcp_server(int port){int fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);D(fd>0,"socket failed(%m)\n");setnonblock(fd);setreuseaddr(fd);sockaddr_in addr;setaddress("0.0.0.0",port,&addr);bind(fd,(struct sockaddr*)&addr,sizeof(addr));listen(fd,64); // backlog = 64return fd;
}static int new_tcp_client(const char* ip,int port){int fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);setnonblock(fd);sockaddr_in addr;setaddress(ip,port,&addr);connect(fd,(struct sockaddr*)(&addr),sizeof(addr));return fd;
}}; // namespace common#endif // _COMMON_H_

1.5.2 echo-client.cc

#include "ev.h"
#include "common.h"static void do_connected(struct ev_loop* reactor,ev_io* w,int events){close(w->fd);ev_break(reactor,EVBREAK_ALL);
}int main(){struct ev_loop* reactor=ev_loop_new(EVFLAG_AUTO);int fd=common::new_tcp_client("127.0.0.1",34567);ev_io io;ev_io_init(&io,&do_connected,fd,EV_WRITE);ev_io_start(reactor,&io);ev_run(reactor,0);close(fd);ev_loop_destroy(reactor);return 0;
}

1.5.3 echo-server.cc

#include "ev.h"
#include "common.h"static void do_accept(struct ev_loop* reactor,ev_io* w,int events){struct sockaddr_in addr;socklen_t addr_size=sizeof(addr);int conn=accept(w->fd,(struct sockaddr*)&addr,&addr_size);std::string r=common::address_to_string(&addr);fprintf(stderr,"accept %s\n",r.c_str());close(conn);
}int main(){struct ev_loop* reactor=ev_loop_new(EVFLAG_AUTO);int fd=common::new_tcp_server(34567);ev_io w;ev_io_init(&w,do_accept,fd,EV_READ);ev_io_start(reactor,&w);ev_run(reactor,0);close(fd);ev_loop_destroy(reactor);
}

本文来自:

http://www.dirlt.com/libev.html

 

主页http://software.schmorp.de/pkg/libev.html

文档http://software.schmorp.de/pkg/libev.html

 

转载于:https://www.cnblogs.com/zhangzhang/archive/2013/01/07/2848924.html

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

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

相关文章

我没有超能力,我只是用了这10个网站。

&#x1f345; 作者主页&#xff1a;不吃西红柿 &#x1f345; 简介&#xff1a;CSDN博客专家 & 总榜前十&#x1f3c6;、HDZ核心组成员。欢迎点赞、收藏、评论 &#x1f345; 粉丝专属福利&#xff1a;知识体系、面试题库、技术互助、简历模板。文末公众号领取 1、今日热…

干货 | 加速AI发展!一文了解GPU Computing

来源&#xff1a; 启迪之星上海摘要&#xff1a;英伟达的显卡对于游戏达人来说再为熟悉不过&#xff0c;并逐渐融入到我们的日常生活当中。英伟达的显卡对于游戏达人来说再为熟悉不过&#xff0c;并逐渐融入到我们的日常生活当中。近日&#xff0c;世界上第一款“光线追踪”GPU…

图解二叉树的Morris(莫里斯)遍历

二叉树的Morris(莫里斯)遍历 本文参考链接&#xff1a;https://leetcode.cn/problems/binary-tree-preorder-traversal/submissions/490846864/ 文章目录 二叉树的Morris(莫里斯)遍历模板代码前序遍历中序遍历后序遍历 Morris 遍历使用二叉树节点中大量指向 null 的指针&…

javaweb数据库操作

本文主要内容有C3P0数据库连接池&#xff0c;dbutils的使用&#xff0c;元数据的应用 在对数据库进行增删改查时&#xff0c;使用数据库连接池可以有效的提高效率&#xff0c;节省资源&#xff0c;C3P0是Apache组织提供的一个有效方式 C3P0的XML配置文件&#xff0c;文件名必…

PHP5.5中新增的参数跳跃和生成器功能介绍

生成器 目前&#xff0c;自定义迭代器很少使用&#xff0c;因为它们的实现&#xff0c;需要大量的样板代码。生成器解决这个问题&#xff0c;并提供了一种简单的样板代码来创建迭代器。 例如&#xff0c;你可以定义一个范围函数作为迭代器: <?phpfunction *xrange($sta…

【每日SQL打卡】​​​​​​​​​​​​​​​DAY 11丨产品销售分析 III【难度中等】

活动介绍&#xff1a; 「数据仓库技术交流群」已经正式启动每日SQL打卡&#xff0c;帮助大家扎实基础&#xff0c;努力工作之余&#xff0c;别忘了自我提升。另有超多CSDN 周边礼物相送。 欢迎报名和邀请小伙伴参与&#xff0c;一个人可能走得很快&#xff0c;但一群人会走得很…

瓜分340亿美元物联网芯片半导体市场!机会在这五大行业【附下载】| 智东西内参...

来源&#xff1a;智东西摘要&#xff1a;市场压力之下&#xff0c;物联网为工业、汽车、智慧城市、医疗健康和消费半导体提供了新的解决方案。半导体产业渗透了我们生活中的各个方面&#xff0c;从闹钟、微波炉到手机、笔记本。而现在&#xff0c;物联网技术正在为全球半导体市…

Android之数据库操作

安卓数据库帮助类 /*** 数据库帮助类&#xff0c;用于管理数据库* author Administrator**/ public class PersonSQLiteOpenHelper extends SQLiteOpenHelper {private String tag"PersonSQLiteOpenHelper";public PersonSQLiteOpenHelper(Context context) {//数据…

【每日SQL打卡】​​​​​​​​​​​​​​​DAY 11丨产品销售分析 II【难度简单】

活动介绍&#xff1a; 「数据仓库技术交流群」已经正式启动每日SQL打卡&#xff0c;帮助大家扎实基础&#xff0c;努力工作之余&#xff0c;别忘了自我提升。 欢迎报名和邀请小伙伴参与&#xff0c;一个人可能走得很快&#xff0c;但一群人会走得很远。 &#x1f345;题目汇总(…

用互联网大脑模型分析滴滴的战略意图和战术失误

作者&#xff1a;刘锋 互联网进化论作者&#xff0c;计算机博士滴滴出行与美国的Uber&#xff0c;本质上都是基于互联网的智能打车软件&#xff0c;让任何拥有家用汽车的司机都可以与出租车司机一样&#xff0c;服务于打车用户。 在滴滴&#xff0c;Uber出世之前的出租车行业…

Android数据存储之sharedpreferences与Content Provider

android中对数据操作包含有&#xff1a; file, sqlite3, Preferences, ContectResolver与ContentProvider前三种数据操作方式都只是针对本应用内数据&#xff0c;程序不能通过这三种方法去操作别的应用内的数据 其中sqlite3已经在上一节中讲述了&#xff0c;本节主要包含shar…

vtk类之vtkImageReslice:基本算法,对体数据沿着轴进行切片

沿着轴方向切割体数据。 vtkImageReslice 是几何图形过滤器中的瑞士军刀。他可以排列&#xff0c;旋转&#xff0c;翻转&#xff0c;缩放&#xff0c;重新采样&#xff0c;变形, 还有随意再任何效率与图像质量组合下&#xff0c;渲染图像。简单的操作&#xff0c;像排列&#x…

【每日SQL打卡】​​​​​​​​​​​​​​​DAY 12丨游戏玩法分析 V【难度困难】

活动介绍&#xff1a; 「数据仓库技术交流群」已经正式启动每日SQL打卡&#xff0c;帮助大家扎实基础&#xff0c;努力工作之余&#xff0c;别忘了自我提升。另有超多CSDN 周边礼物相送。 欢迎报名和邀请小伙伴参与&#xff0c;一个人可能走得很快&#xff0c;但一群人会走得很…

仅需1/5成本:TPU是如何超越GPU,成为深度学习首选处理器的

作者&#xff1a;Kaz Sato 来源&#xff1a;Google Cloud、机器之心摘要&#xff1a;张量处理单元&#xff08;TPU&#xff09;是一种定制化的 ASIC 芯片&#xff0c;它由谷歌从头设计&#xff0c;并专门用于机器学习工作负载。TPU 为谷歌的主要产品提供了计算支持&#xff0c;…

【每日SQL打卡】​​​​​​​​​​​​​​​DAY 12丨销售分析 III【难度简单】

活动介绍&#xff1a; 「数据仓库技术交流群」已经正式启动每日SQL打卡&#xff0c;帮助大家扎实基础&#xff0c;努力工作之余&#xff0c;别忘了自我提升。另有超多CSDN 周边礼物相送。 欢迎报名和邀请小伙伴参与&#xff0c;一个人可能走得很快&#xff0c;但一群人会走得很…

[唐胡璐]Excel技巧 - 使用Excel 2007完成多人协同录入工作

下面我们来介绍下Excel 2007的共享功能。 一、设置共享 启动Excel 2007&#xff0c;打开需要设置共享的工作薄文档&#xff0c;切换到“审阅”菜单选项卡中&#xff0c;单击“更改”组中的“共享工作薄”按钮&#xff0c;打开“共享工作薄”对话框&#xff0c;如下图所示。 …

Android之jni入门

jni即java native interface&#xff0c;使用jni我们可以在JAVA中调用C代码&#xff0c;提高了效率&#xff0c;可以复用代码&#xff0c;可以灵活的应用于各种场景 怎么使用JNI 安装软件 1.NDK 用于将C代码编译成so库 2.CygWin 在windows下模拟linux环境 3.CDT 在eclipse…

腾讯投资过 600 多家公司不惊奇,京东也有出手 260+ | 大公司投资并购盘点

来源&#xff1a;IT桔子A 股一片绿&#xff0c;这已经不是满屏绿色的第一天了&#xff0c;2018 年以来&#xff0c;A 股经历了起起落落落落落落……尤其在中美贸易战不断升级后&#xff0c;A 股的代表颜色就成了绿色&#xff0c;少数会有几家企业翻红&#xff0c;然而过不了几天…

埋点技术:“呵呵,你在网上的一举一动,都在我眼皮子底下”

&#x1f345; 作者主页&#xff1a;不吃西红柿 &#x1f345; 简介&#xff1a;CSDN博客专家 & 总榜前十&#x1f3c6;、HDZ核心组成员。欢迎点赞、收藏、评论 &#x1f345; 粉丝专属福利&#xff1a;知识体系、面试题库、技术互助、简历模板。文末公众号领取 1、什么是…

printf的格式控制的完整格式

printf的格式控制的完整格式 printf的格式控制的完整格式&#xff1a;% - 0 m.n l或h 格式字符下面对组成格式说明的各项加以说明&#xff1a;①%&#xff1a;表示格式说明的起始符号&#xff0c;不可缺少。②-&#xff1a;有-表示左对齐输出&#xff0c;如省略表示右对…