前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >进程与线程的区别

进程与线程的区别

作者头像
恋喵大鲤鱼
发布2018-08-03 10:44:26
9670
发布2018-08-03 10:44:26
举报
文章被收录于专栏:C/C++基础C/C++基础

在开发工作中,尤其是对负载较大的服务端程序的开发,为充分发挥处理器多核性能,提高硬件资源利用率,增加系统吞吐量,少不了并发编程。并发编程一般通过多进程和多线程的方式实现。

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位。程序是静态实体,进程则是动态的运行实体。操作系统为了使多个程序并发执行,提高CPU利用率,故引入进程对程序进行管理。一个程序通常有多个功能模块,假设一个应用程序由两部分组成,计算部分和I/O部分,在未引入进程之前,计算部分和I/O部分,不能并发执行,更不能并行执行,即运行计算部分,需要I/O部分执行完成,反之亦然,执行I/O部分,需要计算部分执行完成。这样的运行模式是对资源的极大浪费,因为I/O部分在运行时,CPU是空闲的,在计算部分运行时,I/O设备是空闲。为了提高硬件资源的利用率和系统性能,可以使用进程来管理计算部分和I/O部分,分别称之为计算进程和I/O进程,那么此时计算进程和I/O进程可以同时运行,并行操作,极大地提高了系统性能和硬件资源利用率。在单个程序中同时运行多个进程完成不同的工作,称为多进程。

上面使用进程来管理单个程序不同功能模块,使单个程序的不同功能模块可以并行执行。使用进程来管理程序,也可以使多个程序之间并发执行。程序是指令、数据及其组织形式的描述,进程是程序能够独立运行的活动实体,由一组机器指令、数据和堆栈等组成。进程拥有三种状态,就绪状态(Ready State)、运行状态(Running)和阻塞状态状态(Blocked State)。就绪状态指进程已获得除处理器外的所需资源,等待分配处理器资源,只要分配了处理器就可执行。就绪进程可以按多个优先级来划分队列,高优先级队列中排队的进程将优先获得处理器资源,进入运行状态。运行状态指进程占用处理器资源处于执行状态,处于此状态的进程数目小于等于处理器的数目。阻塞状态指进程等待某种条件(如I/O操作或进程同步),在条件满足之前,即使把处理器资源分配给该进程,也无法运行。

线程(Thread)是进程中的一个实体,是系统中独立运行和调度的基本单位,亦被称为轻量级进程(Light Weight Process,LWP)。因进程拥有系统资源,在不同进程之间切换和调度,付出的开销较大,所以提出了比进程更小、更轻量的单位线程,作为操作系统执行和调度的基本单位。由于线程自己不拥有系统资源,只拥有在运行中必不可少的少部分资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源(比如CPU、堆栈等),所以调度起来付出的开销更小。线程也有就绪、运行和阻塞三种基本状态。在单个进程中同时运行多个线程完成不同的工作,称为多线程。 进程和线程都是程序运行时衍生的概念,容易混淆,下面说一下具体的区别。 (1)定义不同。进程是系统分配资源的独立单元,而线程是执行和调度的基本单元; (2)所属不同。进程属于程序,线程属于进程。进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程。 (3)通信机制不同。进程间不共享资源,通信需要特殊手段,比如管道、FIFO、信号等,线程间共享进程资源,直接通信。由于多个线程共享进程资源,对临界资源访问时,往往涉及到线程间的同步问题。 (4)创建方式不同。Linux中,进程的创建调用fork或者vfork,而线程的创建调用pthread_create。 (5)安全性不同。因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径,一个线程死掉,整个进程也会死掉。所以进程的安全性会高于线程。

下面演示Linux环境下,分别使用多进程和多线程方式将两部分标准输出并行化。首先看一下串行程序。

代码语言:javascript
复制
#include <stdio.h>

int main(int argc,char* argv[])
{
    //第一部分标准输出,从0输出到9
    for(int i=0;i<10;++i)
    {
        printf("part one %d\n",i);
    }

    //第二部分标准输出,从0输出到9
    for(int i=0;i<10;++i)
    {
        printf("part two %d\n",i);
    }
}

多进程方式:

代码语言:javascript
复制
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char* argv[])
{
     pid_t pid=0;

    //创建子进程,程序开始分叉,分为父子进程
    pid=fork();

    //一次fork()返回两次,父进程中返回子进程ID,子进程返回0,出错返回-1
    if(0==pid)
    {
        //子进程进行第一部分标准输出,从0输出到9
        for(int i=0;i<10;++i)
        {
            printf("in child process part one %d\n",i);
        }
        exit(0);
    }
    else if(pid>0)
    {
        //父进程中进行第二部分标准输出,从0输出到9
        for(int i=0;i<10;++i)
        {
            printf("in parent process part two %d\n",i);
        }

        int childStatus=0;
        //父进程中阻塞式等待子进程结束并回收子进程资源。若子进程已经结束,则立即返回,若子进程未结束,则阻塞等待,直到有信号来到或子进程结束。
        //成功返回子进程ID,失败返回-1
        int ret=waitpid(pid,&childStatus,0);
        if(ret==pid)
        {
            printf("wait child process %d successfully!\n",ret);
        }
    }
    else
    {
        perror("fork");
    }
}

多进程方式输出结果:

代码语言:javascript
复制
in parent process part two 0
in parent process part two 1
in parent process part two 2
in child process part one 0
in parent process part two 3
in child process part one 1
in parent process part two 4
in child process part one 2
in child process part one 3
in child process part one 4
in parent process part two 5
in parent process part two 6
in parent process part two 7
in parent process part two 8
in parent process part two 9
in child process part one 5
in child process part one 6
in child process part one 7
in child process part one 8
in child process part one 9
wait child process 3194 successfully!

从输出结果可以看出,两部分输出出现交叉的情况,表明输出实现了并行。 下面看一下多线程实现方式:

代码语言:javascript
复制
#include <pthread.h>

#include <stdio.h>

//线程函数1。函数结束,线程结束
void* threadFunc1(void* args)
{
    //线程1进行第一部分标准输出,从0输出到iterateNum
    int iterateNum=*(int*)args;
    for(int i=0;i<iterateNum;++i)
    {
        printf("in thread1 part one %d\n",i);
    }
}

//线程函数2。函数结束,线程结束
void* threadFunc2(void* args)
{
    //线程1进行第一部分标准输出,从0输出到iterateNum
    int iterateNum=*(int*)args;
    for(int i=0;i<iterateNum;++i)
    {
        printf("in thread2 part two %d\n",i);
    }
}

int main(int argc,char* argv[])
{
    int args = 10;
    pthread_t thread1ID=0,thread2ID=0;

    //创建线程1
    pthread_create(&thread1ID,NULL,threadFunc1,&args);
    //创建线程2
    pthread_create(&thread2ID,NULL,threadFunc2,&args);

    //阻塞等待线程1结束并回收资源
    int ret1=pthread_join(thread1ID,NULL);
    if(0==ret1)
    {
        printf("thread1 %zu finished\n",thread1ID);
    }
    //阻塞等待线程2结束并回收资源
    int ret2=pthread_join(thread2ID,NULL);
    if(0==ret2)
    {
        printf("thread2 %zu finished\n",thread2ID);
    }
}

输出结果:

代码语言:javascript
复制
in thread1 part one 0
in thread1 part one 1
in thread1 part one 2
in thread1 part one 3
in thread1 part one 4
in thread1 part one 5
in thread2 part two 0
in thread2 part two 1
in thread2 part two 2
in thread1 part one 6
in thread1 part one 7
in thread1 part one 8
in thread1 part one 9
in thread2 part two 3
in thread2 part two 4
in thread2 part two 5
in thread2 part two 6
in thread2 part two 7
in thread2 part two 8
in thread2 part two 9
thread1 139932212193024 finished
thread2 139932203800320 finished

同样地,从数据结果的交叉情况可以看出,两部分输出实现了并行。

上面在介绍进程与线程的区别时,多次提及并发(Concurrency)与并行(Parallelism)的概念,二者虽很相似但有着本质的区别,下面简单地介绍一下二者的概念和区别。

并行指两个或者多个事件在同一时刻发生,并发指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,即利用每个处理机来处理单个程序,这样,多个程序便可以同时执行,这样就实现了实现并行执行。 这里引用Erlang之父Joe Armstrong对并发与并行区别的形象描述。首先看一下下面这张图。

并发是两个等待队列中的人同时去竞争一台咖啡机,谁先竞争到咖啡机谁使用;而并行是每个队列拥有自己的咖啡机,两个队列之间没有竞争的关系。因此,并发意味着多个执行实体(人)需要竞争资源(咖啡机),就不可避免带来竞争和同步的问题;而并行则是不同的执行实体拥有各自的资源,相互之间互不干扰。如果是串行执行的话,一个队列使用一台咖啡机,那么哪怕最前面的人便秘了去厕所呆半天,后面的人也只能等着他回来才能去接咖啡,这效率无疑是最低的。所以,现在操作系统中会引入并发与并行的机制来提高系统效率。可以用一句话总结并行与并发的区别:并发是逻辑上的同时发生,并行是物理上的同时发生。

说到并发与并行,为提高系统效率,计算机在不同层次上使用了并行技术,按系统层次结构由低到高主要有: 位级并行(Bit-level Parallelism)。基于处理器的字长,比如64位的CPU能在同一时间内处理字长为64位的二进制数据,比32位字长的CPU多处理一倍的数据。

数据级并行(Data-level Parallelism)。单指令多数据流(SIMD)是一种实现数据级并行的技术。SIMD表示一条指令可以同时完成多个操作。以加法指令为例,单指令单数据流(SISD)型CPU一条指令只能完成一对操作数相加,SIMD一条指令可以同时完成多对操作数相加。

指令级并行(Instruction-level Parallelism)。计算机处理问题是通过指令实现的,当指令之间不存在相关时,它们在流水线中是可以重叠起来并行执行。 指令级并行基于流水线(Pipeline)技术,将一条指令所需的活动划分成不同的步骤,每个步骤交由不同的硬件处理,不同指令的相同步骤并行执行,达到指令级并行。

线程级并行(Thread-level Parallelism)是上文中通过多线程并行执行,来达到提高系统效率,硬件基础是超线程与多核处理器。超线程允许一个单核CPU同时执行多个控制流(线程),多核处理器的不同核心能够同时执行线程。举例来说,4核的Intel Core i7处理器,配合超线程技术,可以并行执行8个线程。

任务级并行(Task Parallelism)。将作业分解为可并行处理的多个任务,每个任务则被分配分布式计算系统的各个计算节点中完成。


参考文献

[1]进程和线程的区别 [2]计算机操作系统.汤晓丹 [3]并发.百度百科 [4]并发与并行的区别.百家号

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年06月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档