Linux进程、线程——保姆级助理解

目录

1、进程(Process)

1.1 进程基本概念:

1.2 进程分类

1.3 进程的特征

1.4 进程和程序的区别

1.5 进程的状态

1.6 进程的创建——Fork()函数

1.6.1 简介

1.6.2 使用

1.7 进程终止

2、线程(Thread)

1.1 线程基本概念:

1.2 线程的一些相关知识

1.3 多线程的缺点

1.4 创建线程

1.4.1 创建线程 pthread_create

1.4.2 结束线程 pthread_exit

1.4.3 线程等待 pthread_join

1.4.4 线程分离 pthread_detach

1.4.5 助理解线程等待函数和线程分离函数

通俗理解

3、进程和线程的区别

3.1 区别

3.2 举例 

3.3 总结


        学习Linux的同学都知道,进程、线程这两个东西,有些同学理解能力强的看书上的概念就可以理解是什么,或者有些初学者,怎么看都看不明白;别担心,下面我将分别讲解进程、线程的概念再举相应的生活中的例子助理解,废话不多说,开始吧。

1、进程(Process)

有些童鞋觉得进程离我们很远,其实不然,我们可以随时看到,最直观的是打开我们电脑任务管理器。如下图:

1.1 进程基本概念:

对于进程的概念,我讲出三种概念,那种便于理解就理解那种,希望能够帮助需要的人。

① 描述和管理程序的"运行过程"称为进程。在Windows中可以打开任务管理器查看进程

② 进程是程序在计算机上的一次执行活动。一个运行的程序,可能有多个进程。进程在操作系统中执行特定的任务。进程是正在执行的一个程序的实例,通常是由程序,数据集合和进程控制块三部分组成。

③进程是一个程序的执行实例,具有独立的内存空间和资源。操作系统为每个进程分配独立的资源,如内存、文件句柄等。进程之间是相互独立的,一个进程的崩溃不会影响其他进程。

为了便于理解,举一个生活中的例子,自行想象一下。

独立的房子:想象每个进程是一栋独立的房子,每栋房子有自己的院子、厨房、卧室等(内存和资源)。房子之间是独立的,一个房子的火灾不会直接影响到另一个房子。

1.2 进程分类

进程一般分为交互进程、批处理进程、守护进程。

①交互进程:是由shell启动的进程,它既可以在前台运行,也可以在后台运行。交互进程在执行过程中,交互进程在执行过程中,要求与用户进行交互操作。简单来说,就是用户需要给出某些参数或者信息,进程才能继续执行。

②批处理进程:与windows原来的批处理很类似,是一个进程序列。该进程负责按照顺序启动其它进程。

③守护进程:是执行特定功能或者执行系统相关任务的后台进程。守护进程只是一个特殊的进程,不是内核的组成部分。许多守护进程在系统启动时启动,直到系统关闭时才停止运行。而某些守护进程只是在需要时才会启动,比如FTP或者Apache服务等,可以在需要的时候才启动该服务。

1.3 进程的特征

1.动态性:进程是程序的一次执行过程,动态的产生,动态的消亡。

2.并发性:进程同其他进程一起向前推进。

3.异步性:每个进程按照自己各自的速度向前推进。

4.独立性:每个进程都是一个独立的实体,有自己独立的地址空间、资源和控制流。这意味着一个进程的执行不会直接影响其他进程的执行。

1.4 进程和程序的区别

1.动态与静态:进程是动态的,它是程序的一次执行过程;程序是静态的,它是一组指令的有序集合。

2.暂存与长存:进程是暂存的,它在内存上短暂驻留;程序是长存的,它在磁盘或者其他介质上长期保存。

3.程序和进程的对应关系:一个程序可能有多个进程(一个程序运行多次就会产生多个进程)。

1.5 进程的状态

1.运行状态(Running):进程已经占有CPU,并且在CPU上运行。

2.就绪状态(Ready):具有运行条件但没有CPU而暂时不能运行,处于就绪态的进程只要占有CPU便立马可以运行。

3.阻塞状态(Block):进程因为等待某项服务的完成或者信号而不得不停下来,例如调用系统调用等待执行结果、I/O操作操作、等待合作进程的信号......

实际上不同的操作系统有不同的进程状态,某些操作系统甚至具有新建态(new)和终止态(terminate):

Linux的进程状态:

1.可运行态:Linux没有就绪态,它把占有CPU的进程和处于就绪队列的进程的状态统称为可运行态。

2.阻塞态:Linux分为浅度阻塞和深度阻塞。浅度阻塞的进程可以被其他进程的信号或者时钟唤醒,反之深度阻塞的进程则不能。

3.僵尸态:进程终止运行时所处的状态,处于这个状态的进程会释放大部分资源。

4.挂起态:当调试程序时这个进程就处于挂起态。

1.6 进程的创建——Fork()函数

1.6.1 简介

fork()函数的功能是创建一个新的进程,新进程为当前进程的子进程,那么当前的进程为父进程。在一个函数中,可以通过fork()函数的返回值判断进程是在子进程中还是在父进程中。

①一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

②一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己

③fork()函数调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值,如图:
(1)在父进程中,fork()函数返回新创建子进程的进程ID
(2)在子进程中,fork()函数返回0
(3)如果出现错误,fork()函数返回一个负值;

1.6.2 使用

//示例
#include <stdio.h>
#include <unistd.h> 

int main()  
{
    pid_t pid;
    pid = fork();    //创建新进程
    if (pid  == 0) //返回子进程
    { 
        printf("child pid: %d\n", getpid());
    } else 
    {
        printf("pid: %d\n", pid);//父进程中返回子进程的pid
        printf("father pid: %d\n", getpid());
    }
}

一个父进程希望复制自己,使父子进程同时执行不同的代码段。
比如在网络服务程序中,父进程等待客户端的服务请求。当请求到达时,父进程调用fork()使子进程处理此请求;而父进程继续等待下一个请求。

②一个进程要执行一个不同的程序。这个在shell下比较常见,这种情况下,fork()之后一般立即接exec函数。

③fork()函数出错可能有两种原因:
(1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN
(2)系统内存不足,这时errno的值被设置为ENOMEM

每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

fork()函数执行完毕后,出现两个进程。

vfor()函数

(1)vfork
基本功能和fork相同
(2)区别:
vfork()函数创建新进程的主要目的是exec一个新程序。
vfork()函数并不复制父进程的地址空间,并和父进程的内存数据share一起用。
vfork()函数保证子进程先运行。
当子进程调用exit()或exec()后,父进程往下执行。

1.7 进程终止

进程终止的5种正常情况
①在main中执行return(在main函数中会结束当前进程;子函数中,会返回调用当前函数的调用位置,进程从下个C语句开始执行)
②调用_exit 或_Exit(结束当前的进程,不对缓存区刷新)
③调用exit(结束当前的进程,并且会刷新缓存区,关闭没有关闭的文件等)
④进程的最后一个线程执行了返回语句
⑤进程的最后一个线程调用了pthread_exit函数

进程的3种异常终止方式

①调用abort,产生SIGABRT信号

进程收到某些信号

最后一个线程对“取消”请求作出响应

2、线程(Thread)

1.1 线程基本概念:

对于线程的概念,也是进程一样,讲多个概念。

①线程是进程中的一个执行单元,一个进程可以包含多个线程。线程共享进程的资源,如内存和文件句柄,但每个线程有自己的程序计数器、寄存器和栈。

②线程是包含在进程内。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,线程之间资源是共享的。

③线程是CPU直接运行的实体,是CPU调度的基本单位。一个进程可以有多个执行路径,这些路径叫做线程。多个线程可以同时共享CPU,从而实现并发。

生活中的例子:

房子里的房间:想象一个进程是一栋房子,线程是房子里的房间。房子里的房间(线程)共享房子的院子、厨房等(进程的资源),但每个房间有自己的家具(程序计数器和栈)。房间之间可以并行使用资源,但一个房间的操作不会直接影响到其他房间。

1.2 线程的一些相关知识

①如果只有一个线程,任务就是顺序执行的。一个进程可以多个线程,每条线程并行执行不同的任务,引入多线程可以在执行某个任务的过程中,执行其他任务。所以在耗时多任务中,应用非常广泛。

②线程通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源。如:虚拟地址空间,文件描述符和信号处理等等

③线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。

④同一进程中的多个线程有各自的调用栈(call stack)寄存器环境(register context)本地存储(thread-local storage)


⑤在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。

单线程程序:整个进程只有一个线程,不适用多线程技术时都是单线程程序,这个线程称为主线程。

多线程程序:整个进程有至少两个线程,多个线程当中一定存在一个主线程。

1.3 多线程的缺点

多线程并不是完美的,它也会带来许多让人头疼的问题。例如程序调试起来是非常困难的,因为是多个执行流并发执行的;并发的过程难以控制,因为CPU的调度是随机的,我们不能预测线程安全问题,这也是最严重的问题,当多个线程同时访问同一份资源时,就会产生数据冲突、数据不一致等等问题。

1.4 创建线程

1.4.1 创建线程 pthread_create

创建线程是多线程编程的第一步,理解线程创建是多线程编程的关键;mian 函数运行时,系统会自动创建一个线程,称为主线程。通过 pthread_create创建的线程,称为子线程。创建线程的函数如下。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

线程创建函数包含四个变量,分别为:

1. 一个线程变量名,被创建线程的标识;thread用于存储新线程标识符的变量。

2.线程的属性指针,缺省为NULL即可;attr用于设置新线程属性的指针,通常可以传入NULL以使用默认属性。

3.被创建线程的程序代码;start_routine新线程的入口函数,是线程执行的起点。

4. 程序代码的参数;arg传递给入口函数start_routine的参数。

返回值

1.若线程创建成功,返回0。

2.若线程创建失败,返回非0的错误码。

pthread_create函数的使用示例

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

// 线程函数
void* thread_function(void* arg) 
{
    int thread_num = *((int*)arg);
    printf("Hello from thread %d\n", thread_num);
    pthread_exit(NULL);
}

int main() 
{
    pthread_t threads[5];
    int thread_args[5];
    int result_code;

    // 创建5个线程
    for (int i = 0; i < 5; ++i) 
    {
        thread_args[i] = i;
        printf("Main: creating thread %d\n", i);
        result_code = pthread_create(&threads[i], NULL, thread_function, &thread_args[i]);
        if (result_code != 0) 
        {
            printf("Error creating thread %d, error code: %d\n", i, result_code);
            exit(EXIT_FAILURE);
        }
    }

    // 等待所有线程结束
    for (int i = 0; i < 5; ++i) 
    {
        result_code = pthread_join(threads[i], NULL);
        if (result_code != 0) 
        {
            printf("Error joining thread %d, error code: %d\n", i, result_code);
            exit(EXIT_FAILURE);
        }
    }

    printf("Main: all threads completed.\n");
    return 0;
}

1.4.2 结束线程 pthread_exit

线程通过调用pthread_exit()函数终止执行,就如同进程在结束时调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。

void pthread_exit(void *retval);//retval用于存放线程结束的退出状态

1.4.3 线程等待 pthread_join

pthread_create调用成功以后,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数,这个函数实际上类似与多进程编程中的waitpid。

  • 作用:等待一个线程结束,并回收该线程的资源。
  • 特点:这个函数会阻塞调用它的线程,直到被等待的线程终止。
  • 使用场景:当你需要确认一个线程已经结束并获取它的退出状态时使用。
int pthread_join(pthread_t thread, void **retval);
/*示例:
假设 A 线程调用 pthread_join 试图去操作B线程,该函数将A线程阻塞,直到B线程退出,当B线程退出以后,A线程会收集B线程的返回码。 该函数包含两个参数:

pthread_t thread    //thread是要等待结束的线程的标识
void **thread_return     //指针thread_return指向的位置存放的是终止线程的返回状态。

*/
//调用实例:
pthread_join(thread, NULL);

1.4.4 线程分离 pthread_detach

即主线程与子线程分离,子线程结束后,资源自动回收。

在POSIX线程库(pthread)中,pthread_detach函数用于将一个线程的状态设置为分离(detachedstate)状态。一个分离状态的线程在终止后,其资源会立即被回收,而不需要其他线程调用pthread_join来等待它的结束。这样可以避免资源泄漏,同时也简化了线程管理。

返回值:pthread_detach() 在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_detach()将失败并返回相应的值。
 

  • 作用:将一个线程设置为分离状态。分离状态的线程在结束时,系统会自动回收它的资源。
  • 特点:调用这个函数不会阻塞调用它的线程。分离状态的线程结束后,资源会自动回收,不需要通过 pthread_join 手动回收。
  • 使用场景:当你不关心线程的退出状态,只希望它结束时能自动回收资源时使用。

1.4.5 助理解线程等待函数和线程分离函数

通俗理解

  • pthread_join:相当于你等你的朋友回家,等他回家了,你才能做其他事情。
  • pthread_detach:相当于你告诉朋友,不用告诉你他什么时候回家,他回家了自己开门,你继续做你的事情就好。

3、进程和线程的区别

3.1 区别

①内存空间:

进程:每个进程有自己独立的内存空间。
线程:同一进程中的线程共享内存空间。

②资源开销:

进程:创建和切换进程的开销较大。
线程:创建和切换线程的开销较小。

③通信方式:

进程:进程之间通信需要使用进程间通信机制(IPC),如管道、信号、共享内存等。
线程:线程之间直接共享内存,通信更方便,但需要注意同步和互斥。

④独立性:

进程:一个进程崩溃不会直接影响其他进程。
线程:一个线程的异常可能导致整个进程崩溃。

3.2 举例 

举一个生活中的对比助于理解
独立的公司(进程)和公司内部的部门(线程):
进程:每个公司(进程)有自己的办公室、设备和资源(内存和资源)。一个公司的倒闭(进程崩溃)不会直接影响到其他公司(进程)。
线程:每个公司 (进程) 内部可能有多个部门(线程),部门之间共享公司 (进程) 的资源(内存)。一个部门的错误操作(异常)可能会影响整个公司 (进程) 。

3.3 总结

①进程是操作系统分配资源和运行程序的基本单位,具有独立的内存空间和资源。

②线程是进程中的执行单元,共享进程的资源,但有独立的执行路径。

③进程和线程的主要区别在于资源的独立性和通信方式,进程之间更加独立,但通信复杂;线程之间通信方便,但需要注意同步和互斥。

④通过这些对比和生活中的类比,可以更好地理解进程和线程的概念及其应用。

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值