什么是计算机程序?

一条一条的按照语法规则排列起来的指令及其数据的序列,告诉计算机完成的一连串的操作。 0101010101011

什么是进程?

程序默认的保存的外存(硬盘)中的,程序执行的时候、操作系统把程序装载到内存,然后cpu从内存中一条一条的读取并执行这些指令、程序被装载到内存并且被执行的时候,这个程序就称为一个进程(正在进行的程序)。

进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程与线程

什么是线程?

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。

线程也有就绪阻塞运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。

每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

简略的说:在一个进程中同时执行的过个操作就是线程

线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

书中的比喻

比喻:进程就像一个大容器。在程序被运行之后,就相当于程序装进了这个容器

线程的比喻:线程属于进程的一部分,这相当于在进程这个容器中又划分出了许多的小隔间。这些小隔间将进程这个大容器中的程序划分成了很多独立的部分、线程之间可以相互调用。

线程之间的通信与协调是由操作系统提供,一般都会提供:互拆锁条件变量信号量等机制,或者相似的机制。

进程通信机制和协调也是操作系统来提供,一般都会提供:管道I/O重定向套接字等机制,或者相似的机制。

系统是如何做到多线程/进程同时执行的?

php扩展地址:https://github.com/krakjoe/pthreads

<?php
class RequestOne extends Thread {
    public $num;
    public function __construct($num) {
        $this->num = $num;
    }
    public function run() {

        for($i=0;$i<$this->num;$i++) {
            echo '线程One: '.$i."<br/>\n";
        }

        echo "线程ONE: 任务完成\n";
    }
}

class RequestTwo extends Thread{
    public $num;
    public function __construct($num) {
        $this->num = $num;
    }
    public function run() {

        for($i=0;$i<$this->num;$i++) {
            echo '线程Two: '.$i."<br/>\n";
        }

        echo "线程TWO: 任务完成\n";
    }
}


class running {

    function start(){
        $one=new RequestOne(1000);
        $two=new RequestTwo(1000);
        $one->start();$two->start();
    }
}
$start= new running();
$start->start();

代码示例执行结果 :

线程1

命令行模式数字1000进行测试

线程2

进程和线程的区别

  • 系统会为每个进程分配各自独立的内存空间;系统不会给线程单独分配空间。

换句话说,进程是可以单独执行的,而线程是不能单独执行的。

  • 线程必须要在运行的进程之内。如果没有进程,就不会有线程
  • 线程实际上就是进程中的一段可以单独执行的代码。线程可以共享进程的内存空间

进程的特点

  1. 进程是一个实体。每一个进程都有它自己独立的地址空间。

一般情况下要包括:代码区数据区堆栈
代码区的内容就是CPU执行的代码;
数据区存储变量和进程执行期间使用的动态分配的内存,c程序员比较喜欢管这个叫堆;堆栈区存储过程调用的指令和局部变量,c程序员比较喜欢管这个叫栈。

  1. 进程是一个“执行中的程序”。程序是一个没有生命的实体,CPU赋予了程序有时限的生命,这样它就成为了一个“活”的实体,我们称它为进程。
  2. 虽然程序的“生命”是CPU赋予的,但是这个机会却是人给的。人往往需要使用另外一个进程(或者执行中的程序)才能让程序执行起来。那么让程序执行的进程,我们称它为父进程。
  3. **进程会继承父进程的一些资源,这就如同孩子要继承父亲的基因一样。

当一个进程的父进程走完了它的“人生路”,那么这个进程就成了一个“孤儿”,我们称它为“孤儿进程”**

归结起来,进程与线程项目拥有十分显著的特点,那就是:资源独立、主从分明。

PHP里的多进程

php多进程需要扩展pcntl 默认基本上是没有的、安装后最好是在linux环境上使用、最开始用我的mac sierra 10.12.5系统运行、运行后不显示结果、我不知道具体什么原因(差点怀疑人生)

Linux下环境代码示例

<?php
$parentPid = getmypid();
$pid = pcntl_fork(); // 一旦调用成功,事情就变得有些不同了
if ($pid == -1) {
    die('fork failed');
} else if ($pid == 0) {
    $mypid = getmypid(); // 用getmypid()函数获取当前进程的PID
    $a=2;  var_dump($b);
    echo 'I am child process. My PID is ' . $mypid . ' and my fathers PID is ' . $parentPid . PHP_EOL;
} else {
    var_dump($a); $b=3;
    echo 'Oh my god! I am a father now! My childs PID is ' . $pid . ' and mine is ' . $parentPid . PHP_EOL;
}

执行结果

多进程

运行结果证明一件事、就是父进程、子进程,是独立的、

在网上看到的fork的解释:


fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,
这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是“fork时”父进程的完整拷贝,指令指针也完全相同,但只有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。

可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。
至于那一个最先运行,可能与操作系统有关,而且这个问题在实际应用中并不重要,如果需要父子进程协同,可以通过原语的办法解决。



fork前父进程的东西子进程可以继承,而在fork后子进程没有任何和父进程的继承关系了。在子进程里创建的东西是子进程的,在父进程创建的东西是父进程的。可以完全看成两个进程。


fork()函数复制了当前进程的PCB,并向父进程返回了派生子进程的pid。而且根据上面”corand”兄的提示,父子进程并行,打印语句的 先后完全看系统的调度算法。打印的内容控制则靠pid变量来控制。因为我们知道fork()向父进程返回了派生子进程的pid,是个正整数;而派生子进程 的pid变量并没有被改变。这一区别使得我们看到了他们的不同输出。

  1. 派生子进程的进程,即父进程,其pid不变;
  2. 对子进程来说,fork返回给它0,但它的pid绝对不会是0;之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid
  3. fork之后夫子进程除非采用了同步手段,否则不能确定谁先运行,也不能确定谁先结束。认为子进程结束后父进程才从fork返回的,这是不对的,fork不是这样的,vfork才这样。

php多进程代码示例

<?php

/**
 * PHP Linux Cli 模式下利用 pcntl_fork实现多进程处理
 *
 */

// 进程数
$processes = 5;

// 所有任务就是是为了输出0到9999中的所有数字
// 这些数字将被分成5个块,代表5个进程,
// 当输出的时候我们就可以看到所有进程的执行顺序
$tasks = range ( 0, 9999 );

$blocks = array ();

// 将任务按进程分块
foreach ( $tasks as $i ) {
    
    $blocks [($i % $processes)] [] = $i;
}

foreach ( $blocks as $blockNum => $block ) {

    // 通过pcntl得到一个子进程的PID
    $pid = pcntl_fork ();

    if ($pid == - 1) {
        // 错误处理:创建子进程失败时返回-1.
        die ( 'could not fork' );
    } else if ($pid) {
        // 父进程逻辑
        // 等待子进程中断,防止子进程成为僵尸进程。
        // WNOHANG为非阻塞进程,具体请查阅pcntl_wait PHP官方文档
        pcntl_wait ( $status, WNOHANG );
    } else {
        // 子进程逻辑
        foreach ( $block as $i ) {

            echo "I'm block {$blockNum},I'm  printing:{$i}\n";
            sleep ( 1 );
        }

        // 为避免僵尸进程,当子进程结束后,手动杀死进程
        if (function_exists ( "posix_kill" )) {
            posix_kill ( getmypid (), SIGTERM );
        } else {
            system ( 'kill -9' . getmypid () );
        }
        exit ();
    }
}

线程的扩展安装和代码示例

PHP 5.3 以上版本,使用pthreads PHP扩展,可以使PHP支持多线程。

多线程在处理重复性的循环任务,能够大大缩短程序执行时间。

PHP扩展下载:https://github.com/krakjoe/pthreads
PHP手册文档:http://php.net/manual/zh/book.pthreads.php

1、扩展的编译安装(Linux),编辑参数 –enable-maintainer-zts 是必选项:

cd /Data/tgz/php-5.5.1
./configure --prefix=/Data/apps/php --with-config-file-path=/Data/apps/php/etc --with-mysql=/Data/apps/mysql --with-mysqli=/Data/apps/mysql/bin/mysql_config --with-iconv-dir --with-freetype-dir=/Data/apps/libs --with-jpeg-dir=/Data/apps/libs --with-png-dir=/Data/apps/libs --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-fpm --enable-mbstring --with-mcrypt=/Data/apps/libs --with-gd --enable-gd-native-ttf --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --enable-opcache --with-pdo-mysql --enable-maintainer-zts
make clean
make
make install        

unzip pthreads-master.zip
cd pthreads-master
/Data/apps/php/bin/phpize
./configure --with-php-config=/Data/apps/php/bin/php-config
make
make install

vi /Data/apps/php/etc/php.ini

extension = "pthreads.so"

2、给出一段PHP多线程、与For循环,Thread代码示例:

<?php
class Request extends Thread {
    public $url;
    public $data;
    public function __construct($url) {
        $this->url = $url;
    }
    public function run() {
        // 线程处理一个耗时5秒的任务
        for($i=0;$i<5;$i++) {
            echo '线程: '.date('H:i:s')."\n";
            sleep(1);
        }
        $response = file_get_contents($this->url);
        if ($response) {
            $this->data = array($response);
        }
        echo "线程: 任务完成\n";
    }
}
$request = new Request('hello.html');
// 运行线程:start()方法会触发run()运行
if ($request->start()) {
    // 主进程处理一个耗时10秒的任务,此时线程已经工作
    for($i=0;$i<10;$i++) {
        echo '进程: '.date('H:i:s')."\n";
        sleep(1);
    }
    // 同步线程并输出线程返回的数据
    $request->join();
    echo '线程返回数据: '.$request->data[0];
}
/*
如果顺序执行,合计时间将是15秒,借助线程,则只需10秒.
生成文件: echo 'Hello' > hello.html
运行计时: time php req.php 
查看线程: ps -efL|head -n1 && ps -efL|grep php
*/

线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡

关于线程大牛是怎么说的?

PHP不内置多线程编程支持,是为让PHP容器(PHP-FPM/Apache)运行更加稳健,不懂求解释?


韩天峰
PHP/C程序员,PECL开发组成员

多线程太复杂了,要考虑到数据同步问题、锁的粒度问题,稍有不慎就会出现死锁。某个线程crash会导致整个进程退出。多线程编程开发成本太高,容易翻车。多进程编程简单又稳定,一个工作进程挂了,再重启一个就好了,完全不影响服务。

多线程的优势是可以共享本地堆内存,这个特性在web程序里没有任何价值,两个不同请求需要交互吗?如果真有直接用swoole、node.js这样的异步框架好了,没必要多线程。大名鼎鼎的Nginx就是多进程。

其实不只是PHP,Python、Ruby也没有真正的多线程。社区开源项目开发团队的力量本来就很有限,所以没必要耗在投入大产出小的地方。想玩多线程可以用hhvm,这种商业公司的产品不计成本,又体现技术复杂度最适合上多线程。

Last modification:January 7, 2020
如果觉得我的文章对你有用,请随意赞赏