1. 介绍
1.1 什么是进程
进程(Process)是一个正在执行中的程序实例。
每个进程都有自己的内存空间,包含了代码、数据和状态等信息,可以被看作是计算机所执行任务的基本单位。
在操作系统中,每个进程都有一个唯一标识符 PID(Process ID
)来区分其他进程。
进程可以通过操作系统的调度机制在 CPU
上进行执行。进程的创建、调度、通信和同步
等都是由操作系统来管理和控制的,为系统的稳定性和可靠性提供保障。
常见的进程包括浏览器进程、编辑器进程、音频播放器进程等。
1.2 什么是线程
线程(Thread)是计算机操作系统能够进行运算调度的最小单位。
它被包含在进程之中,是进程中的一条执行路径,用于完成进程中的各种任务。
相对于进程来说,线程更加轻量级,一个进程中可以同时包含多个线程,各个线程之间共享相同的进程资源,如内存空间、文件句柄等。
由于多线程可以在同一时间内完成多项任务,所以能够大大提高程序的运行效率和并发能力。
常见的线程包括界面线程、输入输出线程、计算线程等。
和进程一样,线程的创建、调度、通信和同步
都是由操作系统来管理和控制的。
1.3 进程和线程之间的关系
进程和线程是操作系统中的两个重要的概念,它们之间有着密切的关系。
具体来说,它们的关系可以总结为以下三点:
- 进程可以包含多个线程。一个进程中可以同时执行多个线程,线程之间共享进程的资源。在多任务操作系统中,一个程序可以同时创建多个线程并发执行,从而提高系统的效率和吞吐量。
- 进程和线程都可以并发执行。操作系统可以将进程和线程分配到不同的 CPU 核心或者 CPU 时间片上,让它们并发执行。进程和线程之间互相独立,一个线程的执行不会影响其他线程的执行,不同进程的执行也不会互相干扰。
- 进程和线程之间存在层次关系。进程是操作系统分配资源和管理任务的最小单位,线程是进程中任务的最小单位。在操作系统中,进程和线程有着不同的调度策略、优先级和资源限制,因此它们之间存在明显的层次关系。一个进程中的线程可以通过共享内存等方式来进行通信和同步,多个进程之间要通过进程间通信(IPC)等特定机制来完成信息的传递和同步。
2. 进程的概念
2.1 进程的定义
进程是计算机中正在执行中的一个程序实例,可以看作是操作系统进行资源分配的最小单位。
每个进程都有自己独立的内存空间、代码、数据、打开的文件、网络连接等系统资源,和进程状态(运行、等待、挂起等)等信息。
进程之间通过操作系统提供的 IPC(进程间通信)机制来进行通信和同步。操作系统对进程进行管理和调度,为系统提供运行时环境和资源保护。一个进程可以包含多个线程,线程共享进程的资源,可以说线程是进程中实际执行工作的单位。
进程的创建、销毁、调度、通信、同步等都是由操作系统来完成的。
2.2 进程的特征
进程是计算机中最重要的概念之一,具有如下几个特征:
- 动态性:进程是动态产生、动态变化的,它的产生、运行和消亡都是动态的,一个进程的状态可能会随着时间推移而改变。
- 独立性:每个进程都有自己的独立空间,进程之间互相独立,互不干扰。
- 并发性:多个进程可以在同一时间内并发运行,操作系统通过进程调度器来控制进程间的并发执行,保证各个进程之间的和谐相处。
- 不可见性: 进程的存在是对用户透明的,用户不需要知道操作系统是如何管理进程的,也不需要知道进程的具体实现细节。
- 阻塞性: 进程可能因为等待某种资源而被阻塞(例如
等待用户输入、等待 I/O 完成
等),直到资源就绪后才能继续执行。 - 可恢复性:系统中断或进程崩溃时,操作系统可以重新启动进程并恢复上次执行的状态。
- 共享性:进程可以共享某些资源,例如
内存、文件、I/O 端口
等,共享资源带来的好处是节约系统资源,提高系统效率。 - 交互性:进程可以通过操作系统提供的进程间通信(
IPC
)机制来进行数据交互、同步等操作。
2.3 进程的状态
进程是计算机中最重要的概念之一,根据进程的不同状态,可以将其分为以下几个状态:
- 创建状态:当一个程序被用户启动时,操作系统会为其创建一个新进程,此时进程处于创建状态。
- 就绪状态:创建后的进程经过初始化之后,就处于就绪状态,此时进程已经准备好了被操作系统分配 CPU 时间片来执行。
- 运行状态:当就绪状态的进程获得 CPU 时间片后,就会进入运行状态,即进程开始执行其代码,执行计算任务。
- 阻塞状态:当运行的进程需要等待某种资源(例如等待用户输入、等待 I/O 完成等)时,就会从运行状态变成阻塞状态。此时进程会停止执行,等待相应资源的获取。
- 挂起状态:操作系统可以将某个进程暂停执行并将其挂起到磁盘上,此时进程处于挂起状态。在挂起状态下,进程的数据和代码不会被操作系统读取和处理,直到操作系统重新唤醒进程后才能继续执行。
- 终止状态:当一个进程完成了它的任务或者因为某些原因被操作系统强制终止时,就会处于终止状态。在终止状态下,进程会被从系统中移除,释放内存和系统资源。
综上所述,进程可能处于以下几个状态之一:创建状态、就绪状态、运行状态、阻塞状态、挂起状态和终止状态。操作系统根据进程的状态来进行调度和管理,可以实现多进程并发执行和资源共享,提高系统的效率和吞吐量。
2.4 进程的调度
进程调度是指操作系统决定哪个进程应该获得 CPU 时间片来执行的过程。
操作系统有多种调度算法可以选用,不同的算法有着不同的优缺点,选择合适的调度算法可以提高系统的效率和响应速度。
常用的进程调度算法有:
- 先来先服务调度算法(
FCFS
):按照进程提交的时间顺序进行调度,即先提交的进程先执行,如果某个进程长时间运行,会导致后面的进程等待时间较长。 - 短作业优先调度算法(
SJF
):按照进程需要 CPU 时间片长度的大小来进行调度,即就绪队列中需要时间片最短的进程先执行。如果有多个周期短的进程,可能会导致长作业的饥饿。 - 优先级调度算法:给每个进程设定一个优先级或者根据执行的任务不同分配优先级,并按照优先级从高到低进行调度。但可能会导致低优先级的进程长时间等待。
- 时间片轮转调度算法:将 CPU 的运行时间分配成一个个的时间片,当一个时间片用完后,操作系统把进程放到就绪队列尾部,然后执行下一个进程,且每个进程得到同样的时间片,不会导致长作业的饥饿。
- 多级反馈队列调度算法:将就绪队列分成多个队列,新到达的进程放入第一个队列,如果第一个队列一段时间未执行完毕,则降级到第二个队列,以此类推。在这种调度方式下,作业在所有队列上都有机会被执行,也有一定的优先级。
不同的调度算法有不同的优缺点,适用于不同的场景和需求。操作系统的调度器需要根据系统负载、用户行为、硬件资源等情况动态地选取最适当的调度算法进行进程调度,以确保系统的高效和可靠性。
3. 线程的概念
3.1 线程的定义
线程(Thread),也称为轻量级进程(Lightweight Process),是计算机中能够运行并执行操作系统分配给它的任务的最小单位。线程是进程内部的一个独立执行单元,一个进程可以包含多个线程。不同线程之间可以共享所属进程的资源和数据,如代码、数据、堆栈、文件等,也可以通过操作系统提供的 IPC(进程间通信)机制进行通信和同步。
线程与进程的主要区别在于,进程是操作系统资源分配的基本单位,而线程是 CPU 调度的基本单位。同一个进程中的所有线程共享同一片内存空间,可以在同一个地址空间中互相访问共享数据,这使得线程之间的通讯和同步变得简单高效。线程的出现以及进程中两个或多个线程之间的通信和同步,大大提高了系统的并发执行能力和性能。
3.2 线程的优点
线程可以调用操作系统提供的系统调用,也可以像进程一样创建子线程和进行 IPC,但因为线程已经内嵌在进程之中,所以线程间的调用和通讯比进程方便,也不会占用太多的系统资源。
线程通常使用标准的编程方式进行编写,与单线程程序相比,线程程序具有更高的可复用性、更好的模块化、更高的扩展性,具有如下优点:
- 更高的并发性能:多个线程之间的并发执行可以加快程序运行速度,提高系统的性能。
- 更灵活的资源调度:线程的创建和销毁可以更加灵活,对系统资源的分配和调度有更好的控制。
- 更好的共享性:线程可以共享进程的资源,可以更方便的实现数据共享、通信和同步。
- 更好的互动性:多线程程序可以实现更为复杂的程序逻辑,可以轻松处理用户交互、事件响应等操作。
因此,线程作为计算机系统中的一个基本概念,具有很多优点和应用场景,被广泛应用于多种系统中。
3.3 线程的实现
在操作系统中,线程的实现有两种方式:用户级线程(User-Level Threads,ULTs)和内核级线程(Kernel-Level Threads,KLTs)。
- 用户级线程(ULTs):是在用户空间中实现的线程,只在用户级执行,并且不依赖于内核支持。用户级线程的实现是基于线程库(Thread Library),是一种用户程序库,在应用程序中调用线程库的函数来管理线程。线程库负责协调所有用户级线程的执行顺序,以及处理线程间的通信和同步。如 pthreads 线程库就是一种常见的用户级线程库。用户级线程的优点是:快速创建和销毁线程,线程切换开销较小,便于管理和调度。缺点是:不能利用多处理器的优势,线程的中断、I/O 等操作会阻塞相应进程的所有线程。
- 内核级线程(KLTs):是在内核空间中实现的线程,由操作系统内核来直接管理和调度。内核级线程的实现是通过系统调用来完成,如 POSIX 标准中定义的 clone() 系统调用,可创建一个新的内核线程,并将它加入到调度程序的就绪队列中。内核可以利用多处理器的优势,使多个内核级线程并发执行。内核级线程的优点是:能够利用多处理器的优势,避免线程间共享的数据出现竞争问题。缺点是:线程的切换和创建销毁等操作需要系统调用,开销较大,线程的管理和调度需要操作系统的支持。
在实际中,一些操作系统将用户级线程和内核级线程结合起来使用,这种方式被称为“混合级线程”(Hybrid Threads),其实现主要是用户级线程层和内核级线程层的交互。混合级线程兼具用户级线程和内核级线程的优点,可以根据需要在用户空间和内核空间实现不同的线程,提高线程的执行效率和灵活性。
3.4 线程的调度
线程调度是指操作系统根据一定的算法和策略选择一个就绪线程来执行的过程,线程调度的目的是最大化系统资源利用率,提高系统的并发效率和响应速度。
下面列举几种线程调度算法:
- 轮询调度算法:简单地将 CPU 的时间片均分给就绪队列中的所有线程,按照顺序依次执行,属于静态优先级调度算法。
- 优先级调度算法:为每个线程赋予优先级,并按照优先级从高到低进行线程调度。优先级可以由线程本身设置,也可以由操作系统根据线程的重要程度自动设置。
- 短作业優先調度算法(SJF): 将每个线程的执行时间预测出来,按照执行时间从短到长进行调度,预测短的线程可以更早执行,以最小化平均等待时间。
- 时间片轮转调度算法:为每个线程分配一段时间,称为时间片,每个线程在其时间片内执行一定时间后,被强行切换出来,分配给下一个线程执行,以避免某个线程长时间占用 CPU 资源。
- 多级队列反馈调度算法:将就绪队列分级,不同级别有不同的时间片,并根据优先级和历史执行时间等因素动态调整线程的优先级和时间片。
常见的线程调度算法都是动态的,将线程分配给不同的 CPU 时间片来执行,同时加入了优先级、性能评估、调度队列管理等多个因素。线程调度算法的选择要根据应用程序的需求、系统的结构和工作负载的情况来选择最优的算法。
总体来说,线程调度算法的性能直接关系到系统的并发处理和处理能力,对于大型复杂的并发系统来说更加重要。如果线程调度算法得到了良好的优化和适配,系统可以运行更快、更流畅,具有更好的响应速度和吞吐能力。
4. 进程和线程的比较
4.1 进程和线程的区别
进程和线程都是操作系统中的并发执行实体,但是它们之间有以下区别:
- 资源拥有情况:进程是独立的程序运行环境,包含独立的内存空间、代码、数据、文件句柄等,拥有自己的资源;而线程是进程中的实体,共享所属进程的资源,如进程的内存空间、文件句柄等。
- 调度:进程是操作系统资源调度的基本单位,由操作系统进行调度和管理;而线程是 CPU 调度的基本单位,由操作系统或线程库进行调度和管理。
- 通讯和同步:进程之间的通讯和同步一般通过操作系统提供的进程间通讯(IPC)机制完成;而线程之间的通讯和同步由线程库提供的同步函数和共享变量进行实现。
- 运行效率:由于进程之间资源独立,进程间切换开销较大,加上需要频繁进行 I/O 操作,运行效率通常较低;而线程之间共享内存空间和数据,切换开销相对较小,因此运行效率较高。
- 安全性:由于线程共享进程的资源,线程间的竞争和冲突可能会导致数据不一致或者资源泄漏的问题,需要通过同步机制进行协调和管理;而进程独立的资源和隔离性使得进程之间相互独立,不会出现数据竞争和冲突的问题。
综上所述,进程和线程都是并发执行下的重要实体,二者的应用场景及优缺点有所不同。在实际应用中,需要根据具体的需求和系统架构,选择合适的叙述方式进行实现。
4.2 进程和线程的联系
进程和线程都是操作系统中的并发执行实体,在使用上具有如下联系:
- 进程包含一个或多个线程,线程是进程的一部分,共享进程的内存空间和其他资源。
- 进程和线程都是由操作系统进行调度和管理,包括
分配和回收资源
等。 - 进程和线程的并发执行都可以提高系统的性能和效率,包括
并发处理、异步处理和分布式处理
等。 - 进程和线程之间可以进行通讯和同步操作,如
共享内存、消息传递、信号量、事件
等。 - 进程和线程都可以进行并发编程,编写多线程或多进程程序,可以实现更高级别的并发处理,如
多线程 Web 服务器、多线程图形界面程序
等。
虽然进程和线程都是并发执行实体,但是在使用时需要根据具体应用需求、系统性能等因素进行权衡选择。有时需要使用多进程实现并发,有时则需要使用多线程来提高效率,也有可能同时使用进程和线程来优化并发效率。
4.3 进程和线程的应用
进程和线程作为操作系统中的两种并发处理实体,在实际应用中有各自不同的应用场景,下面列举一些典型的应用:
- 进程应用:通常用于独立的应用程序之间的并发执行,如多任务处理、服务端程序等,各进程之间通过 IPC 进行通信和同步,安全性更高,可以实现更好的资源隔离和共享。
- 多线程应用:通常用于单个应用程序中的并发处理,可以利用多核 CPU 提高处理效率,适合于多任务、I/O 密集型操作等场景,可以共享进程的资源并减少开销。
- 混合应用:在实际应用中,有些场合需要同时使用进程和线程来加强并发处理,如 Web 服务器、数据库系统等,可以采用线程池的方式复用线程资源,减少创建和销毁线程时的开销,同时使用进程来实现资源隔离和安全性。还可以使用线程来加速某些瓶颈操作(如 I/O 操作),同时使用进程来处理 CPU 密集型计算,更好地利用系统资源。
- 并发编程应用:进程和线程的并发编程模型被广泛应用于网络编程、图形界面编程、科学计算、游戏开发等众多领域,具有简单、灵活、高效的特点,可以实现任务并发执行,提高系统的并发能力和性能。
总之,进程和线程的应用是非常广泛的,具有各自不同的优点和局限性,需要根据实际应用需求进行权衡选择。可以根据系统特点和性能要求,选择合适的进程形式和线程数量,以最大限度地提高系统的并发效率和性能。
5. 进程和线程的实现
5.1 进程和线程的API
进程和线程在操作系统中都具有自己的 API 接口,通过这些 API 可以对进程和线程进行创建、管理、通讯和同步等操作。下面列举了一些常用的 API 接口。
- 进程创建与管理 API:
- fork():创建子进程,复制父进程的上下文信息;
- exec():在当前进程上下文中启动指定程序,替换当前进程为新进程;
- wait():父进程等待子进程结束;
- exit():结束当前进程;
- kill():发送信号给目标进程。
- 线程创建与管理 API:
- pthread_create():创建线程;
- pthread_join():等待线程结束;
- pthread_detach():将线程退出状态设置为分离状态,自动释放资源;
- pthread_cancel():向目标线程发送终止请求。
- 进程间通讯 API:
- 管道(pipe):用于有亲缘关系的进程间通信;
- 有名管道(named pipe):用于无亲缘关系的进程间通信;
- 共享内存(shared memory):允许多个进程共享同一块内存区域;
- 消息队列(message queue):为独立进程之间提供双向数据传输的 IPC 机制;
- 套接字(socket):用 TCP/IP 协议实现网络通信。
- 线程间通讯 API:
- 互斥锁(mutex):用于保障共享数据的原子性,防止多个线程同时访问;
- 条件变量(condition variable):用于线程间同步,实现等待、唤醒等操作;
- 读写锁(reader-writer lock):实现多个线程对同一个共享资源进行读操作,降低锁的竞争;
- 屏障(barrier):实现多个线程在同一时间点上同步,需要等待其他线程都执行到同一点才能继续执行。
总之,进程和线程 API 提供了一系列接口用于操作系统的管理和调度,实现多任务处理、并发编程和 IPC 等功能,是并发编程的基础。开发人员使用这些 API 可以方便地实现进程和线程间的通讯、同步和管理。
5.2 进程和线程的创建
进程和线程的创建是操作系统中重要的内容之一。下面分别介绍进程和线程的创建方法。
1. 进程创建
在 Linux/Unix
系统中,使用 fork()
系统调用创建子进程,可以复制当前进程的整个状态,并在返回时给父进程和子进程分别返回不同的返回值。子进程的进程 ID(pid)值是父进程的 pid,但是子进程会拥有父进程的副本,其中包括代码、数据、堆栈等资源。
具体代码如下:
#include <unistd.h> #include <stdio.h> int main(void) { pid_t pid; pid = fork(); if (pid < 0) { perror("Fork failed.\n"); return -1; } else if (pid == 0) { // Child process printf("I am child process. My process ID is %d.\n", getpid()); } else { // Parent process printf("I am parent process. My process ID is %d. My child's process ID is %d.\n", getpid(), pid); } return 0; }
2. 线程创建
在 POSIX
系统中,使用 pthread_create(
) 函数来创建线程,该函数接受一个回调函数和一个参数列表作为线程的入口点。线程的创建涉及各种资源的分配和初始化,如堆栈空间、线程 ID、调度优先级等。
具体代码如下:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> void *task(void *args) { int i; for (i = 0; i < 10; i++) { printf("Thread %d: i = %d\n", *(int*)args, i); sleep(1); } return NULL; } int main(void) { pthread_t t1, t2; int a = 1, b = 2; pthread_create(&t1, NULL, task, &a); pthread_create(&t2, NULL, task, &b); pthread_join(t1, NULL); pthread_join(t2, NULL); return 0; }
在这个示例中,我们创建了两个线程,分别执行 task() 回调函数,并传入不同的参数。每个线程都循环打印出一些信息,最后进行线程同步,等待线程结束。
总之,进程和线程的创建是操作系统中的基本操作,可以利用这些创建方法实现多任务处理、并发编程等功能。根据系统的要求和性能需求,开发人员可以选择不同的创建方式来运行程序和实现复杂的并发处理。
【利用AI让知识体系化】进程和线程(二)https://developer.aliyun.com/article/1426105