Linux epoll 笔记(高并发事件处理机制)

wiki

Epoll优点;

Epoll工作流程;

Epoll实现机制:

  epollevent;

Epoll源码分析;

Epoll接口:

  epoll_create;

  epoll_ctl;

  epoll_close;

Epoll工作方式:

  LT(level-triggered);

  ET(edge-triggered);

Epoll应用模式;

 

Epoll优点:

<1>支持一个进程打开大数目的socket描述符(FD)

 select一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

 <2>IO效率不随FD数目增加而线性下降

epoll只会对"活跃"的socket进行操 作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。同时对于监听的fd很多,但是活跃的fd很少的情况下epoll相比select也有很高的效率。

 <3>使用mmap加速内核与用户空间的消息传递。

无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

 <4>内核微调

这一点其实不算 epoll 的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协 议栈使用内存池管理sk_buff结构,那么可以在运行期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网 卡驱动架构。

 <5>与select相比,不复用监听的文件描述集合来传递结果

这样不需要每次等待前对文件描述符集合重新赋值。

 

Epoll工作流程:

Epoll实现机制:

epoll fd有一个私有的struct eventpoll,它记录哪一个fd注册到了epfd上。eventpoll 同样有一个等待队列,记录所有等待的线程。还有一个预备好的fd列表,这些fd可以进行读或写。相关内核实现代码fs/eventpoll.c,判断是否tcp有激活事件吗:net/ipv4/tcp.c:tcp_poll函数;    

struct eventpoll {

    /* Protect the access to this structure */

    spinlock_t lock;

 

    /*

    * This mutex is used to ensure that files are not removed

    * while epoll is using them. This is held during the event

    * collection loop, the file cleanup path, the epoll file exit

    * code and the ctl operations.

    */

    struct mutex mtx;

 

    /* Wait queue used by sys_epoll_wait() */

    wait_queue_head_t wq;

 

    /* Wait queue used by file->poll() */

    wait_queue_head_t poll_wait;

 

    /* List of ready file descriptors */

    struct list_head rdllist;//调用epoll_wait的时候,readylist中的epitem出列,将触发的事件拷贝到用户空间.之后判断epitem是否需要重新添加回readylist.

 

    /* RB tree root used to store monitored fd structs */

    struct rb_root rbr;//红黑树的根,一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象.epitem被添加到rbr中。该结构保存了epoll监视的文件描述符。

 

    /*

    * This is a single linked list that chains all the "struct epitem" that

    * happened while transferring ready events to userspace w/out

    * holding ->lock.

    */

    struct epitem *ovflist;

 

    /* The user that created the eventpoll descriptor */

    struct user_struct *user;

};

 

 

epitem重新添加到readylist必须满足下列条件:

1) epitem上有用户关注的事件触发.

2) epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist

 

注意,如果epitem被设置为EPOLLONESHOT模式,则当这个epitem上的事件拷贝到用户空间之后,会将

这个epitem上的关注事件清空(只是关注事件被清空,并没有从epoll中删除,要删除必须对那个描述符调用

EPOLL_DEL),也就是说即使这个epitem上有触发事件,但是因为没有用户关注的事件所以不会被重新添加到

readylist.

 

epitem被添加到readylist中的各种情况(当一个epitem被添加到readylist如果有线程阻塞在epoll_wait,

个线程会被唤醒):

1)对一个fd调用EPOLL_ADD,如果这个fd上有用户关注的激活事件,则这个fd会被添加到readylist.

 2)对一个fd调用EPOLL_MOD改变关注的事件,如果新增加了一个关注事件且对应的fd上有相应的事件激活,

则这个fd会被添加到readylist.

 3)当一个fd上有事件触发时(例如一个socket上有外来的数据)会调用ep_poll_callback(eventpoll::ep_ptable_queue_proc),

如果触发的事件是用户关注的事件,则这个fd会被添加到readylist.

 

了解了epoll的执行过程之后,可以回答一个在使用边界触发时常见的疑问.在一个fd被设置为边界触发的情况下,

调用read/write,如何正确的判断那个fd已经没有数据可读/不再可写.epoll文档中的建议是直到触发EAGAIN

错误.而实际上只要你请求字节数小于read/write的返回值就可以确定那个fd上已经没有数据可读/不再可写.

最后用一个epollfd监听另一个epollfd也是合法的,epoll通过调用eventpoll::ep_eventpoll_poll来判断一个

epollfd上是否有触发的事件(只能是读事件).

 

Epoll源码分析:

涉及linux模块的编写;

<<Epoll源码分析.doc>>

Epoll module:

static int __init eventpoll_init(void){

//模块初始化函数

}

eventpoll_init函数源码

static int __init eventpoll_init(void)

{

int error;

 

init_MUTEX(&epsem);

 

/* Initialize the structure used to perform safe poll wait head wake ups */

ep_poll_safewake_init(&psw);

 

/* Allocates slab cache used to allocate "struct epitem" items */

epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),

0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,

NULL, NULL);

 

/* Allocates slab cache used to allocate "struct eppoll_entry" */

pwq_cache = kmem_cache_create("eventpoll_pwq",

sizeof(struct eppoll_entry), 0,

EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);

 

/*

 * Register the virtual file system that will be the source of inodes

 

 * for the eventpoll files

 */

/*注册了一个新的文件系统,叫"eventpollfs"(在eventpoll_fs_type结构里),然后挂载此文件系统*/

error = register_filesystem(&eventpoll_fs_type);

if (error)

goto epanic;

 

/* Mount the above commented virtual file system */

eventpoll_mnt = kern_mount(&eventpoll_fs_type);

error = PTR_ERR(eventpoll_mnt);

if (IS_ERR(eventpoll_mnt))

goto epanic;

 

DNPRINTK(3, (KERN_INFO "[%p] eventpoll: successfully initialized.\n",

current));

return 0;

 

epanic:

panic("eventpoll_init() failed\n");

}

epoll是个module,所以先看看module的入口eventpoll_init。这个module在初始化时注册了一个新的文件系统,叫"eventpollfs"(在eventpoll_fs_type结构里),然后挂载此文件系统。另外创建两个内核cache(在内核编程中,如果需要频繁分配小块内存,应该创建kmem_cahe来做“内存池”),分别用于存放struct epitemeppoll_entry

 

 

Epoll的接口:

epollLinux内核为处理大批句柄而作改进的pollLinux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。

1.工作函数

1>.int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目fd+1,每个epoll都会占用一个fd值,可以在/proc/进程id/fd/查看。记得close()

2>.int epoll_ctl(int epfd,int op,int fd ,struct epoll_event *event);

epoll的事件注册函数,epoll的控制函数;

这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fdepfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

 

typedef union epoll_data {

    void *ptr;//数据指针

    int fd;/*descriptor*/

    __uint32_t u32;

    __uint64_t u64;

} epoll_data_t;

 

struct epoll_event {

    __uint32_t events; /* Epoll events type */

    epoll_data_t data; /* User data variable */

};

 

epoll_event->data涵盖了调用epoll_ctl增加或者修改某指定句柄时写入的信息,epoll_event->event,则包含了返回事件的位域。

 

events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

 

enum EPOLL_EVENTS

  {

    EPOLLIN = 0x001,

#define EPOLLIN EPOLLIN

    EPOLLPRI = 0x002,

#define EPOLLPRI EPOLLPRI

    EPOLLOUT = 0x004,

#define EPOLLOUT EPOLLOUT

    EPOLLRDNORM = 0x040,

#define EPOLLRDNORM EPOLLRDNORM

    EPOLLRDBAND = 0x080,

#define EPOLLRDBAND EPOLLRDBAND

    EPOLLWRNORM = 0x100,

#define EPOLLWRNORM EPOLLWRNORM

    EPOLLWRBAND = 0x200,

#define EPOLLWRBAND EPOLLWRBAND

    EPOLLMSG = 0x400,

#define EPOLLMSG EPOLLMSG

    EPOLLERR = 0x008,

#define EPOLLERR EPOLLERR

    EPOLLHUP = 0x010,

#define EPOLLHUP EPOLLHUP

    EPOLLRDHUP = 0x2000,

#define EPOLLRDHUP EPOLLRDHUP

    EPOLLWAKEUP = 1u << 29,

#define EPOLLWAKEUP EPOLLWAKEUP

    EPOLLONESHOT = 1u << 30,

#define EPOLLONESHOT EPOLLONESHOT

    EPOLLET = 1u << 31

#define EPOLLET EPOLLET

  };

 

3>. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

 

工作方式:

LT/ET:

LT(level triggered):水平触发,缺省方式,同时支持blockno-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。

 

ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

 

应用模式:

那么究竟如何来使用epoll呢?其实非常简单。

通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。

 

首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

 

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:

nfds = epoll_wait(kdpfd, events, maxevents, -1);

其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

 

epoll_wait范围之后应该是一个循环,遍利所有的事件。

 

几乎所有的epoll程序都使用下面的框架(尤其是socket)

 

    for( ; ; )

    {

        nfds = epoll_wait(epfd,events,20,500);

        for(i=0;i<nfds;++i)

        {

            if(events[i].data.fd==listenfd) //有新的连接;我们可以注册多个FD,如果内核发现事件,就会载入events,如果有我们要的描述符也就是listenfd,说明某某套接字监听描述符所对应的事件发生了变化。每次最多监测20fd数。

            {

                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接

                ev.data.fd=connfd;

                ev.events=EPOLLIN|EPOLLET;//LT

                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中

            }

            else if( events[i].events&EPOLLIN ) //接收到数据,读socket,数据可读标志EPOLLIN

            {

                n = read(sockfd, line, MAXLINE)) < 0    //读

                ev.data.ptr = md;     //md为自定义类型,添加数据

                ev.events=EPOLLOUT|EPOLLET;

                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓

            }

            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket

            {

                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据

                sockfd = md->fd;

                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据

                ev.data.fd=sockfd;

                ev.events=EPOLLIN|EPOLLET;

                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据

            }

            else

            {

                //其他的处理

            }

        }

    }

1.Linux下多线程epoll编程

来自 <http://blog.csdn.net/susubuhui/article/details/37906287>

2.epoll + 多线程实现并发网络连接处理

来自 <http://www.cnblogs.com/iTsihang/archive/2013/05/23/3095775.html>

3.高并发的epoll+线程池,业务在线程池内

来自 <http://blog.chinaunix.net/uid-311680-id-2439722.html> 

转载于:https://www.cnblogs.com/ypwen/p/4725532.html

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

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

相关文章

Django请求响应对象

请求与响应对象 HttpRequest HttpRequest存储了客户请求的相关参数和一些查询方法。 path请求页面的全路径,不包括域名—例如, "/hello/"。 methodHttp请求方法&#xff0c;包括GET,POST。 GETQueryDict类实例&#xff0c;包含所有HTTP GET参数的字典对象。 POSTQuer…

matlab 作图 虚线太长,matlab 极坐标绘图 在matlab中,用polar画的图形,如何使虚线圆多显示几个?...

满意答案iredwood推荐于 2018.12.26采纳率&#xff1a;52% 等级&#xff1a;12已帮助&#xff1a;13535人打开polar.m 文件&#xff0c;路径可通过输入 which polar 命令得到。其中修改下面这段代码&#xff0c;可以控制虚线圆的显示个数。其中rticks 为控制显示个数的参量。…

《学习opencv》笔记——矩阵和图像处理——cvAnd、cvAndS、cvAvg and cvAvgSdv

矩阵和图像的操作 (1)cvAnd函数 其结构 void cvAnd( //将src1和src2按像素点取“位与运算”const CvArr* src1,//第一个矩阵const CvArr* src2,//第二个矩阵CvArr* dst,//结果矩阵const CvArr* mask NULL;//矩阵经行像素点与的“开关” );程序实例#include <cv.h> #inc…

Hibernate之加载策略(延迟加载与即时加载)和抓取策略(fetch)

假设现在有Book和Category两张表,表的关系为双向的一对多,表结构如下: 假设现在我想查询id为2的那本书的书名,使用session.get(...)方法: 1 Session sessionHibernateUtil.getSession(); 2 Book book (Book) session.get(Book.class,2); 3 System.out.println(book.getName());…

指纹图像方向图matlab,matlab指纹方向场方向图程序

function Fangxiangtu zhiwen_fangxiangtu( Zhiwentuxiang )%函数功能计算指纹方向图%函数参数指纹图像Zhiwentuxiang%函数返回值指纹方向图FangxiangtuSizeZhiwentuxiang size( Zhiwentuxiang ) ;Zhiwentuxiang double( Zhiwentuxiang ) ;W 4; % 窗口大小(2W1)*(2W1)W 4;…

怎样实现一个简单的jQuery编程

第一步&#xff1a;在head中载入jQuery框架 <script  type"text/javascript" src"jQuery文档所在的绝对路径"></script> 注&#xff1a; type——指定脚本的mime类型 src——规定外部脚本文件的URL jQuery是一个javascript库&#xff0c;相…

php多人点餐可以看到对方点的菜,千万不要小看你身边那个会点菜的人,因为

饭局上&#xff0c;你常常是负责点菜的那个人&#xff0c;还是只负责吃&#xff1f;拿起菜单点菜&#xff0c;你是很从容&#xff0c;还是不知道怎么点&#xff1f;事实上&#xff0c;饭局上那个会点菜的人&#xff0c;千万不能小看。某次随老板外出开会&#xff0c;跟去的几个…

gvim for php,转 : Gvim建立IDE编程环境 (Windows篇)

说明&#xff1a;本文是作者在完全按照著名的《手把手教你把Vim改装成一个IDE编程环境》一文&#xff0c;在Windows XP上用gvim建立IDE环境时所作的备忘。原作地址&#xff1a;http://blog.csdn.net/wooin/archive/2007/10/31/1858917.aspx。1.安装gvim7.2。运行gvim72.exe&…

1081. Rational Sum (20) -最大公约数

题目如下&#xff1a; Given N rational numbers in the form "numerator/denominator", you are supposed to calculate their sum. Input Specification: Each input file contains one test case. Each case starts with a positive integer N (<100), followe…

CRC8校验分析

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** CRC即循环冗余校验码&#xff08;Cyclic Redundancy Check&#xff09;&#xff1a;是…

insert mysql后加where,如何在MySQL Insert語句中添加where子句?

This doesnt work:這不起作用:INSERT INTO users (username, password) VALUES ("Jack","123") WHERE id1;Any ideas how to narrow insertion to a particular row by id?任何想法如何通過id縮小插入到特定行?8 个解决方案#120In an insert statement y…

阿里云使用笔记-Lrzsz上传下载文件-centos7

2019独角兽企业重金招聘Python工程师标准>>> 上传文件时提示&#xff1a; -bash: rz: command not found rz命令没找到&#xff1f; 执行sz&#xff0c;同样也没找到。 原来是要安装个叫 lrzsz 的东西&#xff0c;一查可以直接yum。 安装lrzsz&#xff1a;# yum -y …

C#中的DBNull、Null、String.Empty和“”

null可赋值任何变量,将变量置为空 DBNull只用于DataRow对象,表示数据库中的空值 String.Empty是0长度字串 Convert.IsDBNull判断是否为DBNull DBNull.Value与Null的区别 Null是.net中无效的对象引用。 DBNull是一个类。DBNull.Value是它唯一的实例。它指数据库中数据为空(&l…

matlab数值很小出错,求大神帮忙解决一下,用MATLAB求解动力学数据总是出错~ - 计算模拟 - 小木虫 - 学术 科研 互动社区...

CODE:function KineticsEst5 % 动力学ODE方程模型的参数估计%%%% The variables y here are y(1)xB, y(2)xoNB, y(3)xmNB,y(4)xpNB,y(5)xDNB .clear allclck0 [5 5 5 5 5]; % 参数初值lb [0 0 0 0 0]; % 参数下限ub [inf inf inf inf inf]; % 参数上限x0 [0 0 0 0 0 0];Kin…

iOS开发--验证码

第一步&#xff0c;拖两个空间textfiled和button到storyboard上的viewcontroller上。 第二步&#xff0c;拖线&#xff0c;链接到.h文件中代码如下&#xff1a; 1property (weak, nonatomic) IBOutlet UIButton *l_timeButton;第三步&#xff0c;在,m文件中为l_timeButton设置监…

Standard C Episode 8

C语言函数和程序结构 通过函数可以把大的计算任务分解成若干个较小任务&#xff0c;从而使得思路更加清晰&#xff0c;同时函数也大大提高了代码的复用率&#xff0c;提高了工作效率。要注意的是多函数之间应该尽可能地高聚合低耦合。另一方面&#xff0c;一个程序可以保存在一…

C# Socket 编程详解

Microsoft.Net Framework为应用程序访问Internet提供了分层的、可扩展的以及受管辖的网络服务&#xff0c;其名字空间System.Net和 System.Net.Sockets包含丰富的类可以开发多种网络应用程序。.Net类采用的分层结构允许应用程序在不同的控制级别上访问网络&#xff0c;开发人员…

java 线程池 wait,Java 多线程 之 wait等待 线程实例

package com.wait.notify;/**题目: 人们在火车站的售票窗口排队买火车票1. 北京西站开门2. 打开售票窗口3. 北京西站有10张去长沙的票4. 打开2个售票窗口,5 假设每个售票窗口每隔1秒钟买完一张票1. 根据 名词 找类人们(Person), 火车站(Station),火车票(Ticket) , 售票窗口e 是…

002 exercises

求列表全排列lst [1,2,3] l1 [(x,y,z) for x in lst for y in lst for z in lst if x ! y if y ! z if x ! z] print(l1)给定一个非负整数num,重复的加每一位,直到最后只剩下一位例如: num 38,计算过程如下:3 8 111 1 2最后输出结果为2#递归 def add(num):if len(str(num…

(线段树 点更新 区间求和)lightoj1112

链接&#xff1a; http://acm.hust.edu.cn/vjudge/contest/view.action?cid88230#problem/D &#xff08;密码0817&#xff09; Description Robin Hood likes to loot rich people since he helps the poor people with this money. Instead of keeping all the money togeth…