网络编程实战
盛延敏
前大众点评云平台首席架构师
立即订阅
6034 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 学好网络编程,需要掌握哪些核心问题?
免费
第一模块:基础篇 (9讲)
01 | 追古溯源:TCP/IP和Linux是如何改变世界的?
02 | 网络编程模型:认识客户端-服务器网络模型的基本概念
03丨套接字和地址:像电话和电话号码一样理解它们
04 | TCP三次握手:怎么使用套接字格式建立连接?
05 | 使用套接字进行读写:开始交流吧
06 | 嗨,别忘了UDP这个小兄弟
07 | What? 还有本地套接字?
08 | 工欲善其事必先利其器:学会使用各种工具
09丨答疑篇:学习网络编程前,需要准备哪些东西?
第二模块:提高篇 (10讲)
10 | TIME_WAIT:隐藏在细节下的魔鬼
11 | 优雅地关闭还是粗暴地关闭 ?
12 | 连接无效:使用Keep-Alive还是应用心跳来检测?
13 | 小数据包应对之策:理解TCP协议中的动态数据传输
14丨UDP也可以是“已连接”?
15 | 怎么老是出现“地址已经被使用”?
16 | 如何理解TCP的“流”?
17 | TCP并不总是“可靠”的?
18 | 防人之心不可无:检查数据的有效性
19丨提高篇答疑:如何理解TCP四次挥手?
期中复习周 (2讲)
期中大作业丨动手编写一个自己的程序吧!
免费
期中大作业丨题目以及解答剖析
免费
第三模块:性能篇 (12讲)
20 | 大名⿍⿍的select:看我如何同时感知多个I/O事件
21 | poll:另一种I/O多路复用
22 | 非阻塞I/O:提升性能的加速器
23 | Linux利器:epoll的前世今生
24 | C10K问题:高并发模型设计
25 | 使用阻塞I/O和进程模型:最传统的方式
26 | 使用阻塞I/O和线程模型:换一种轻量的方式
27 | I/O多路复用遇上线程:使用poll单线程处理所有I/O事件
28 | I/O多路复用进阶:子线程使用poll处理连接I/O事件
29 | 渐入佳境:使用epoll和多线程模型
30 | 真正的大杀器:异步I/O探索
31丨性能篇答疑:epoll源码深度剖析
第四模块:实战篇 (4讲)
32 | 自己动手写高性能HTTP服务器(一):设计和思路
33 | 自己动手写高性能HTTP服务器(二):I/O模型和多线程模型实现
34 | 自己动手写高性能HTTP服务器(三):TCP字节流处理和HTTP协议实现
35 | 答疑:编写高性能网络编程框架时,都需要注意哪些问题?
结束语 (1讲)
结束语丨我相信这不是结束,让我们江湖再见
网络编程实战
登录|注册

26 | 使用阻塞I/O和线程模型:换一种轻量的方式

盛延敏 2019-10-07
你好,我是盛延敏,这里是网络编程实战第 26 讲,欢迎回来。
在前面一讲中,我们使用了进程模型来处理用户连接请求,进程切换上下文的代价是比较高的,幸运的是,有一种轻量级的模型可以处理多用户连接请求,这就是线程模型。这一讲里,我们就来了解一下线程模型。
线程(thread)是运行在进程中的一个“逻辑流”,现代操作系统都允许在单进程中运行多个线程。线程由操作系统内核管理。每个线程都有自己的上下文(context),包括一个可以唯一标识线程的 ID(thread ID,或者叫 tid)、栈、程序计数器、寄存器等。在同一个进程中,所有的线程共享该进程的整个虚拟地址空间,包括代码、数据、堆、共享库等。
在前面的程序中,我们没有显式使用线程,但这不代表线程没有发挥作用。实际上,每个进程一开始都会产生一个线程,一般被称为主线程,主线程可以再产生子线程,这样的主线程 - 子线程对可以叫做一个对等线程。
你可能会问,既然可以使用多进程来处理并发,为什么还要使用多线程模型呢?
简单来说,在同一个进程下,线程上下文切换的开销要比进程小得多。怎么理解线程上下文呢?我们的代码被 CPU 执行的时候,是需要一些数据支撑的,比如程序计数器告诉 CPU 代码执行到哪里了,寄存器里存了当前计算的一些中间值,内存里放置了一些当前用到的变量等,从一个计算场景,切换到另外一个计算场景,程序计数器、寄存器等这些值重新载入新场景的值,就是线程的上下文切换。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《网络编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(15)

  • 沉淀的梦想
    关于队列满的情况,额外加一个cond,表示队列未满条件就可以了。

    typedef struct {
        int number;
        int *fd;
        int front;
        int rear;
        // 队列中当前元素数目
        int count;
        pthread_mutex_t mutex;
        pthread_cond_t not_empty;
        // 队列未满条件
        pthread_cond_t not_full;
    } block_queue;

    void block_queue_init(block_queue *blockQueue, int number) {
        blockQueue->number = number;
        blockQueue->fd = calloc(number, sizeof(int));
        blockQueue->count = blockQueue->front = blockQueue->rear = 0;
        pthread_mutex_init(&blockQueue->mutex, NULL);
        pthread_cond_init(&blockQueue->not_empty, NULL);
        pthread_cond_init(&blockQueue->not_full, NULL);
    }

    void block_queue_push(block_queue *blockQueue, int fd) {
        pthread_mutex_lock(&blockQueue->mutex);
        while (blockQueue->count == blockQueue->number){ //队列满
            pthread_cond_wait(&blockQueue->not_full, &blockQueue->mutex);
        }

        blockQueue->fd[blockQueue->rear] = fd;
        if (++blockQueue->rear == blockQueue->number) {
            blockQueue->rear = 0;
        }
        blockQueue->count++;
        printf("push fd %d", fd);
        pthread_cond_signal(&blockQueue->not_empty);
        pthread_mutex_unlock(&blockQueue->mutex);
    }

    int block_queue_pop(block_queue *blockQueue) {
        pthread_mutex_lock(&blockQueue->mutex);
        while (blockQueue->front == blockQueue->rear) // 空队列
            pthread_cond_wait(&blockQueue->not_empty, &blockQueue->mutex);
        int fd = blockQueue->fd[blockQueue->front];
        if (++blockQueue->front == blockQueue->number) {
            blockQueue->front = 0;
        }
        blockQueue->count--;
        printf("pop fd %d", fd);
        pthread_cond_signal(&blockQueue->not_full);
        // 解锁
        pthread_mutex_unlock(&blockQueue->mutex);
        return fd;
    }

    作者回复: 不错的想法。

    2019-10-07
    2
    5
  • 安排
    1.没位置可用可以选择丢弃,取出来直接关闭,等待对方重连,或者先判断队列是否有位置,没位置的话直接就不取出套接字,让它留在内核队列中,让内核处理。
    2.存在竞态条件,需要加锁,不加锁可能大部分时间也能得到正确结果。
    2019-10-07
    2
  • Steiner
    请问block_queue_pop的pthread_cond_wait为什么要放在while而不是if中

    作者回复: 好问题。

    这是为了确保被pthread_cond_wait唤醒之后的线程,确实可以满足继续往下执行的条件。如果没有while循环的再次确认,可能直接就往下执行了。

    2019-10-14
    1
    1
  • 星辰
    1. (just talk)可以拒绝 扩容 或者 加一个信号量来变为阻塞队列叭

    2. 绝大多数情况下这个值小于2000哇
    2019-10-09
    1
  • 刘丹
    block_queue_push未考虑队列满的情况,可以在本函数里先判断是否队列满了,如果满就按某个策略扩容,例如扩大1.5或2倍。扩容失败或者容量超过最大值,就返回失败。

    作者回复: 很好的方法。

    2019-10-07
    1
  • godtrue
    1:阻塞IO+多进程——实现简单,性能一般
    2:阻塞IO+多线程——相比于阻塞IO+多进程,减少了上下文切换所带来的开销,性能有所提高。
    3:阻塞IO+线程池——相比于阻塞IO+多线程,减少了线程频繁创建和销毁的开销,性能有了进一步的提高。
    2019-11-24
  • 阿尔卑斯
    我自定义阻塞队列(多了个cnt计数元素个数,和条件变量分开来,实现起来清晰明了简单多了)
    typedef struct
    {
      int size; //队列容量
      int *pFd; //存储队列元素,动态分配
      int cnt; //队列当前元素个数
      int pushIdx; //入队元素索引
      int popIdx; //出对元素索引
      pthread_mutex_t mutex; //锁
      pthread_cond_t noFull; //非满,即队列元素个数cnt从size值,变成size - 1时的触发条件
      pthread_cond_t noEmpty;//非空,即队列元素个数cnt从0值,变成1时的触发条件
    }BlockQueue;

    作者回复: 可以的。

    2019-11-06
  • Steiner
    请问我想验证这个阻塞队列的正确性,那我的代码逻辑应该怎么写

    作者回复: 我的main函数已经在验证了,开多个线程,有往队列里扔对象的,有从队列里取对象处理的,看看这个程序是否可以处理高并发的场景,这就是你的代码逻辑需要关注的点。

    2019-10-21
  • Steiner
    请问thread_run函数里这个int fd = (int) arg 合法吗,我觉得是int fd = *(int *)arg吧

    作者回复: 合法的,传值还是传地址,是自己在程序里面定义的。

    2019-10-18
    1
  • 传说中的成大大
    pthread_create(&(thread_array[i].thread_tid), NULL, &thread_run, (void *) &blockQueue);
    pthread_create函数调用这里有问题 应该是
    void* thread_run(void *arg) {
        pthread_t tid = pthread_self();
        pthread_detach(tid);

        block_queue *blockQueue = (block_queue *) arg;
        while (1) {
            int fd = block_queue_pop(blockQueue);
            printf("get fd in thread, fd==%d, tid == %d", fd, tid);
            loop_echo(fd);
        }
    }
    我编辑代码的时候总是报错 后来通过man函数和编译才差出来

    作者回复: 提个MR或者issue?我看大家都正常啊,你的系统是啥?

    2019-10-14
  • 传说中的成大大
    看了git上的代码,我又思考了第一题 一般来说肯定数量一定会被限定,不可能无线扩容,并且还要回收利用,比如某个连接关闭了,就应该将其移除,而不是再对其进行读写等操作,不然会浪费很多资源,还容易出现很多莫名其妙的tcp层面错误,最简单的例子 很多游戏都设置了一个排队,例如之前看见英雄联盟在登录界面的排队,还有魔兽也有类似的操作,所以如果本来描述符数量已经设置到合理大小了就不要再扩容而是采取排队进入的方式

    作者回复: 你的例子是说accetpor不要无限制的接收客户端的连接,相当于做了一个限流,这也是比较常见的一种手段。

    2019-10-14
  • 传说中的成大大
    第一问 连接字队列不可能说无限扩容,当某个连接断开时应该是要从连接字队列里面移除
    第二问 那是因为没有出现多线程竞争的情况也就是异步事件,也可以通过加锁来解决异步问题

    作者回复: 第一个问题可以提个MR给我哈

    2019-10-14
  • 赖阿甘
    分离线程中的“而一个分离的线程不能被其他线程杀死或回收资源。”这句话有误?虽然你调用pthread_detach(),但是其他线程还是可以通过pthread_cancel()结束这个线程吧!还望老师指点

    作者回复: 我理解pthread_detach以后就不能了,你可以尝试一下。

    2019-10-09
  • 我来也
    1.队列满可以关闭fd拒绝服务,也可以扩容等待队列,但都不太好。毕竟一个线程服务一个socket的开销还是有点大。还是多路复用加异步比较划算。
    2.就是并发会产生不确定性。在这个循环次数从1000变大些,出现的概率可能就更大了。
    2019-10-07
  • 程序水果宝
    thread_array = calloc(THREAD_NUMBER, sizeof(Thread));这个Thread是哪里来的,是C库的线程结构体吗?

    作者回复: typedef struct {
        pthread_t thread_tid; /* thread ID */
        long thread_count; /* # connections handled */
    } Thread;

    因为篇幅原因,不是所有代码都放到文稿里了,请到这里看详细的代码
    https://github.com/froghui/yolanda

    2019-10-07
    2
收起评论
15
返回
顶部