V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xing393939
V2EX  ›  PHP

关于多线程多协程的疑惑

  •  
  •   xing393939 · 2020-07-13 16:55:15 +08:00 · 2877 次点击
    这是一个创建于 1585 天前的主题,其中的信息可能已经有所发展或是发生改变。

    用 php 的 ptheads 扩展写了一个跑多线程的 demo,如下:

    <?php
    
    class TestMysql extends Thread
    {
        public function run()
        {
            $conn = new mysqli('localhost', 'root', '', 'test');
            $query = $conn->query("select sleep(1) as t" . $this->getThreadId());
            $result = $query->fetch_assoc();
            echo json_encode($result) . "\r\n";
        }
    }
    
    $t = microtime(1);
    $t1 = new TestMysql();
    $t2 = new TestMysql();
    $t1->start();
    $t2->start();
    $t1->join();
    $t2->join();
    
    var_dump(microtime(1) - $t);
    

    demo 的总运行时间是 1 秒,说明线程是并发执行的。我目前对底层知识了解不多,对比 swoole 的多协程开发,有一些疑惑,希望有人可以解惑下:

    1. 这个 demo 的 2 个线程用到了 cpu 的多核了吗,怎么去验证?
    2. 如果没有用到 cpu 的多核,说明线程遇到 io 阻塞的时候,会切换线程(同时 io 操作仍在执行,且执行完后会异步回调)。这样的话就和 swoole 的协程的处理思路是一样的了,只是 swoole 的协程对 io 阻塞的函数还有讲究,需要这些函数可以支持异步。swoole 为了实现这个,早期还专门针对 sleep/mysql/redis 等提供了支持异步的 sdk,后期通过底层的 hook 技术实现了这些原生函数一键协程支持。但是上面的多线程 demo 可以说明,多线程根本不需要多此一举。为什么协程需要这样处理呢?
    8 条回复    2020-07-14 09:28:29 +08:00
    brazz
        1
    brazz  
       2020-07-13 17:01:49 +08:00   ❤️ 1
    你应该看看 线程和协程的区别吧,线程切换是需要开销的,而且多线程还会有同步锁的问题.
    DJQTDJ
        2
    DJQTDJ  
       2020-07-13 17:02:47 +08:00   ❤️ 1
    php 写不出来,用过 linux 来看,看完了就明白了
    yc8332
        3
    yc8332  
       2020-07-13 17:44:09 +08:00   ❤️ 1
    协程是语言层面去切换的。如果你的协程管理和运行不能在多个线程 /进程上进行,那它就没法使用多核。。。就是要像 golang 的协程那样,运行的时候底层是多个线程或者进程在跑这些协程的。swoole 那个协程就是在单线程上切换而已,所以没有办法使用多核,也就是说只有特定场景才有提升,否则没用。
    wangritian
        4
    wangritian  
       2020-07-13 18:10:34 +08:00   ❤️ 1
    1.验证方法,多创建几个线程,跑死循环,用 top 命令看看
    2.协程=用户态线程,不能和多线程共用,建议找一找线程和协程的文章,顺便了解下操作系统线程和 cpu 线程的区别
    gantleman
        5
    gantleman  
       2020-07-13 21:06:41 +08:00   ❤️ 1
    首先要说明协程和线程的区别。当两个线程交替使用 CPU 时,一个线程中断工作让出 CPU 时需要保存所有当前寄存器的状态。以便在下次获得 CPU 使用权时可以恢复原来的工作。这种保存当前所有状态的操作耗时巨大。也就是通常所说的线程切换的高额代价。既然线程的中断操作耗时巨大。哪么等待线程运行完毕再切换 CPU 到下一个线程。因为不用保存原来线程状态。所以切换协程的代价就几乎为零,所以速度比线程要快。这种等待运行完毕再切换的方法就是协程或则叫纤程。

    因为协程没有标准在每个语言的实现方式都有不同,甚至在操作系统的实现都不同。所以区分线程和协程的方法就看是否能够中断并保存状态。凡是能中断和保存状态的就是线程。反之就是协程。

    因为协程不能中断,所以协程在执行IO的时候如果被阻塞,哪么就相当于整个程序全部都被阻塞了。swoole 要求在协程内使用异步IO,就是要求协程不能被阻塞。

    我们回到你的第一个问题,线程是否使用了多核。操作系统并不保证线程会运行在多核。因为线程可以随时中断,所以操作系统可以根据核心的负载调度线程。也就是如果当前核心负载高哪么线程就会被调整到其他比较空闲的核心。如果其他核心都很忙,两个线程也可能只运行在一个核心。

    如何验证是否多核呢?只有在极少数特殊情况下可以验证。因为线程切换代价非常高是和正常运行的指令比较的。这个切换的代价对于应用程序员来说也是非常非常快的,这种切换快到我们无法察觉。也就是说你以为线程执行过程的切换是“噼---啪”。其实线程执行过程的切换是“噼啪噼啪噼啪”。
    wysnylc
        6
    wysnylc  
       2020-07-13 21:49:34 +08:00
    @gantleman #5 我有一个观点,协程这种替 CPU 去调度的方式简直就是在革 CPU 的命,CPU 必然会参考协程或者自己更新调度算法达到语言级别的优化,这时候语言的所谓"替 CPU"去调度就显得很多余而 CPU 重新保住了饭碗
    xing393939
        7
    xing393939  
    OP
       2020-07-14 08:30:18 +08:00
    @gantleman 感谢,我大概理解的就是:协程是等待运行完再切换(在用户态),线程是 io 阻塞 /结束时切换(在内核态)。而 php 协程的实现是基于单线程的,不能遇到同步的 io 阻塞,所以需要异步。

    好奇的提一个问题,多线程时,线程遇到 io 阻塞可以切换线程,那单线程的时候遇到 io 阻塞会怎么样呢? cpu 一直傻傻的等吗,还是切换进程了?
    gantleman
        8
    gantleman  
       2020-07-14 09:28:29 +08:00   ❤️ 1
    @xing393939 在硬件中 cpu 是根据中断来处理 io 的。所谓中断就是 cpu 执行一个指令后就会检查中断寄存器有没有中断请求。如果有中断请求就是切过去处理 io 。所以在内核没有阻塞的概念,用户态的阻塞在内核叫挂起。所谓挂起就很形象了,就是内核调度程序每隔一段时间就会查一下有没有 io 要处理。如果处理好了就返回给用户态,继续执行用户态的代码。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2740 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 15:39 · PVG 23:39 · LAX 07:39 · JFK 10:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.