第九周总结03

多路复用

  • 使用一个进程(且只有主线程),监控若干个文件描述符的读写操作,并作出对应的响应,这种读写模式称为多路复用

使用场景

  • 多用于TCP的服务端,用于监控客户端的连接和收发数据
  • 适合并发量大,但任务量短小的情景,例如Web服务器

优点

  • 不需要频繁地创建、销毁进程,从而节约了时间资源、内存资源,也避免了进程之间的资源竞争、等待

缺点

  • 单个客户端的任务不能太耗时,否则其它客户端会感知到卡顿

select

  • fd_set 数据类型,是文件描述符的集合,使用以下函数进行操作:

  • #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    void FD_SET(int fd, fd_set *set);
    // 功能:添加fd到集合set中
    void FD_CLR(int fd, fd_set *set);
    // 功能:从集合set中删除fd
    int FD_ISSET(int fd, fd_set *set);
    // 功能:判断fd是否存在于集合set中
    // 返回值:存在返回非零,不存在返回0
    void FD_ZERO(fd_set *set);
    // 功能:清空集合set
    
    int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
    // 功能:监控多个文件描述符的读、写、异常等操作
    // nfds:被监控的文件描述中最大值+1
    // readfds:监控读操作的文件描述符集合
    // writefds:监控写操作的文件描述符集合
    // exceptfds:监控异常操作的文件描述符集合
    /* 
    	timeout:设置超时时间
        	NULL   	   一直阻塞,直到监控的某些文件描述符发生了对应的变化
        	0秒0微秒    非阻塞
        	秒数>0      最多等待超时时间,超时后会返回0
    */
    /*	
    	struct timeval
    	{
    		long tv_sec;         // seconds
            long tv_usec;        // microseconds
        };
    */
    /* 返回值:
        1、监控到发生了对应操作的文件描述符数量
        2、超时返回0
        3、错误返回-1
    */
    /* 注意:
        readfds、writefds、exceptfds 这三个集合参数既是输入也是输出,所以当调用select函数时需要往集合中存放被监控的文件描述符,当函数监控成功返回后,会把发生了操作的文件描述符存入到集合中,需要读取
    */
    
  • 缺点

    • 1、每次调用select时都需要重新向它传递被监控的集合
    • 2、调用结束后若想知道具体是哪些文件描述符发生了对应的操作,需要把所有监控的文件描述符都通过FD_ISSET测试一遍
  • 优点

    • 它是最早的多路复用函数,几乎所有的操作系统都支持,程序的兼容性很高

pselect

  • #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
    // 功能:功能与select大致相同,多一些增加的功能,相当于select的增强版,但是本质没区别,缺点一致
    
  • 区别

    • 1、超时时间结构类型不同,pselect精度更高11

    • 2、pselect的timeout参数,还可以输出剩余时间

    • 3、pselect监控时可以通过sigmask参数设置要屏蔽的信号,可以保障pselect监控时不受某些信号的干扰

    • 缺点一致


poll

  • #include <poll.h>
    
    int poll(struct pollfd *fds,nfds_t nfds,int timeout);
    // 功能:监控一些文件描述符
    // fds:struct pollfd结构数组
    // nfds:fds结构数组的长度
    // timeout:超时时间
    /* 返回值:
            1、监控到发生了对应操作的文件描述符数量
            2、超时返回0
            3、错误返回-1
    */
    
    // 一个pollfd对应一个被监控的文件描述符
    struct pollfd
    {
    	int fd;           // 被监控的文件描述符
        short events;     // 要监控的事件
        short revents;    // 实际监控到的事件
    };
    /* events\revents可选参数:
          POLLIN  	普通优先级的读事件 001
          POLLOUT 	普通优先级的写事件 010  r:110 & 001
          POLLPRI 	高优先级的读事件
          POLLRDHUP 对方socket关闭事件
          POLLERR 	通信错误事件,只能在revents中获取
          POLLHUP 	对方挂起事件
          POLLNVAL 	非法描述符,只能在revents中获取
    */ 
    

epoll

  • #include <sys/epoll.h>
    
    int epoll_create(int size);
    // 功能:创建epoll对象,该对象可以用于保存被监控的文件描述符
    // size:epoll对象监控文件描述符的数量
    // 返回值:返回一个epoll对象的描述符
    
    int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
    // 功能:控制epoll对象,如:添加、删除监控的文件描述符、修改监控事件
    // epfd:epoll对象描述符
    /* op:
        EPOLL_CTL_ADD  添加描述符
        EPOLL_CTL_DEL  删除描述符
        EPOLL_CTL_MOD  修改描述符要监控的事件 
       fd:要操作的描述符 
       event:
         要监控的事件结构体
        struct epoll_event
        {
            uint32_t events;     // 要监控的事件,参考poll的events 名字前加E
            epoll_data_t data;   // 监控的fd 赋给 data.fd
        };
    */
    // 返回值:成功返回0 失败返回-1
        
    
    int epoll_wait(int epfd,struct epoll_event *events,int maxevents, int timeout);
    // 功能:监控文件描述符,直接返回产生对应事件的文件描述符
    // epfd:epoll对象描述符
    // events:输出型参数,用于存储发生了事件的描述符数组
    // maxevents:用于声明监控可以返回的最多描述符数量
    // timeout:超时时间
    /* 返回值:
        1、监控到发生了对应操作的文件描述符数量
        2、超时返回0
        3、错误返回-1
    */
    
  • 与select对比的优点

    • 1、只需要准备一次要监控的描述符
    • 2、会把发生了事件的描述符返回不需要像select遍历所有的描述符
    • 3、编程结构更简洁

epoll的条件触发和边缘触发

  • 条件触发:当文件缓冲区中有需要读取的数据时就会触发事件
    • epoll默认的模式
    • 类似按键盘
  • 边缘触发:当数据产生发送动作时就会触发一次事件,如果缓冲区中还有需要读取的数据时不再触发事件
    • 1、被监控的描述符的事件增加EPOLLET 边缘触发
    • 2、要循环读取数据直到读取完毕
    • 3、recv读取时必须以非阻塞MSG_DONTWAIT读取
    • 4、当recv的返回值为**-1时读取完毕**,0表示连接断开
    • 优点某些情况下大大地降低事件触发的次数提高程序运行效率
    • 类似鼠标单击

线程管理

基本概念

  • 线程是进程的执行路线,它是进程内部的控制序列是进程的一部分
  • 进程是一个资源单位,线程是一个执行单位线程是进程的实体,是真正负责执行
  • 线程轻量级的,没有自己独立的代码段数据段bss段堆区环境变量命令行参数文件描述符信号处理函数当前工作目录等资源一个进程中所有线程都是共享以上资源
  • 每个线程都自己独立的栈区线程ID错误码权限掩码程序计数器独立的调度优先级等必不可少的资源
  • 一个进程中同时可以有多个线程(多条执行路线),称第一个进程中的线程为主线程,并且一个进程至少要拥有一个线程
  • 查看线程命令:ps -T -p 进程号或者htop程序命令
  • 线程也有不同的状态,系统提供了线程的控制接口函数,例如:创建、销毁、控制等
  • 进程中所有的线程都是在同一个地址空间活动,进程中的所有资源对于线程而言都是共享的,虽然线程有属于自己的栈区等资源,但是默认下没有添加保护机制,对于其它线程而言是可见,因此当多个线程协同工作时,需要解决资源竞争的问题(线程同步问题)[加锁]
  • 多个线程协同工作时,它们之间不需要间接数据交换,也就不需要类似IPC的通信机制,并且开辟线程系统开销小切换任务路线更快,因此线程的使用更简单而高效
  • 线程之间有优先级的差异
  • 从表面上看,当主线程结束时,其它所有线程也随之结束,但并不是因为主线程结束导致的,而是因为主线程执行了进程的main函数的return n语句导致整个进程结束,而所有线程都属于进程的一部分,所以才会随着进程结束而结束如果主线程是通过调用pthread_exit()结束的,那么就没执行return n语句的话,其他线程不受影响不会结束

POSIX线程

  • 早期的UNIX和Linux系统是没有线程概念的,微软的Windows系统首先提出使用的线程,之后UNIX和Linux逐渐增加了线程技术
  • 早期各个厂商提供私有的线程库,但是接口和实现差异较大,不易于移植,IEEE组织于1995年指定了一套统一的线程接口规范标准,遵循该标准的线程称为POSIX线程,简称 pthread
  • pthread线程包含一个头文件 pthread.h 和一个共享库 libpthread.so ,编译参数加 -pthread

线程管理

  • #include <pthread.h>
    
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
    // 功能:创建一个线程
    // thread:输出型参数,用于获取创建出的线程ID
    // attr:线程属性,一般写NULL以默认属性创建即可
    // start_routine:线程的入口函数,相当于线程的main函数
    // arg:传递给线程入口函数的参数,不需要传递时写NULL即可
    // 注意:入口函数的参数和返回值要确保它们的持久化,不适合传递栈内存,比较适合的是全局变量
    // 返回值:成功返回0,失败返回错误码
    
    int pthread_join(pthread_t thread, void **retval);
    // 功能:等待某个线程结束,并获取该线程结束时的返回值,然后回收释放线程资源
    // thread:要等待结束的线程ID
    // retval:用于存储入口函数返回值的地址的指针
    // 返回值:成功返回0,失败返回错误码,线程还没结束会阻塞等待
    
    pthread_t pthread_self(void);
    // 功能:获取调用者的线程ID
    
    int pthread_equal(pthread_t t1, pthread_t t2)
    // 功能:比较两个线程ID是否相等
    // 返回值:相等返回非零,不相等返回0
    // 注意:部分系统的线程ID是以结构形式实现的,因此不能直接使用==比较,也不适合用0初始化,使用此函数代码可移植性更高
    

线程的执行轨迹

同步方式

  • 可结合态(默认)
  • 在使用默认属性创建一个线程时,线程状态为可结合态,也称为joinable态,处于此状态的线程可以被另一个线程使用pthread_join()等待其结束并释放线程资源
    • 如果一个joinable态的线程在结束时,没有被pthread_join进行释放资源,该线程变成"僵尸线程",每个僵尸线程都会消耗一些系统资源,如果有大量的僵尸线程出现时,可能会导致创建线程失败

异步方式

  • 分离状态

  • 把线程状态设置为detach态,detach态的线程无需经过pthread_join来回收资源,当线程终止后会由系统自动释放资源

    • 如果调用pthread_join去等待detach态的线程,则不会等待也不会回收资源立即返回
    • 如果在分离前调用了pthread_join,然后再分离,那么pthread_join会等到该线程结束后才返回,但不会去回收资源
  • 注意:为了避免资源泄漏,要么对每个joinable态的线程显式地调用pthread_join来回收资源;要么设置成detach态,让系统来回收资源

  • int pthread_detach(pthread_t thread);
    // 功能:设置线程thread为detach态
    
  • 注意:两种分离用法

    • 1、线程自己调用:pthread_detach(pthread_self());
    • 2、创建者\其它线程调用:pthread_detach(tid);

线程的终止

  • 1、线程执行完入口函数的最后一行代码

  • 2、线程执行入口函数的return 语句

  • 3、线程调用了pthread_exit函数

    • void pthread_exit(void *retval);
      // 功能:结束当前调用的线程并返回retval给pthread_join的调用者
      
  • 4、如果进程结束,那么进程中所有的线程都会随之结束

    • 线程调用exit函数,会杀死所有线程和进程
  • 5、向指定的线程发送取消请求

    • int pthread_cancel(pthread_t thread);
      // 功能:向指定的线程发送取消请求,默认情况下都会响应请求
      int pthread_setcancelstate(int state,int *oldstate);
      // 功能:设置本线程是否要响应取消请求,并获取之前的状态
      /* state:
      	PTHREAD_CANCEL_ENABLE 允许响应
      	PTHREAD_CANCEL_DISABLE 禁止响应
      */
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值