一、进程与线程的概念:
在了解进程之间我们先来了解一下程序和可执行文件。当我们编写了一段程序也就是源文件(.c、.cpp等等),首先会进行预处理操作,处理预处理指令:如 #include、#define、#ifdef等。将宏定义替换为实际代码;将 #include 指令中的头文件内容插入到源代码中。以及根据条件选择地编译代码块。然后是编译阶段,通过编译器(如GCC、Clang)编译成汇编代码(.s),其次是汇编阶段,汇编器将汇编代码转换为机器代码,生成目标文件(.obj),最后是链接阶段,通过链接器将目标文件与所需要的库文件和其他目标文件链接在一起,生成一个完整的可执行文件(.exe)。可执行文件是以二进制形式存在的,因为计算机只认识0和1。计算机可以直接执行它。
进程和程序是什么关系?
程序是我们编写好的一段代码,当这段代码运行起来也就成了一个进程。简单来说就是进程是活的程序。
进程:进程是操作系统中一个程序的执行实例,具有独立的地址空间和执行环境。它包含了程序代码、数据、进程控制块(PCB)、寄存器状态、堆栈以及其他资源。进程是动态的,随着程序的执行而存在,并在运行时由操作系统管理,包括调度、创建、终止等操作。每个进程都有自己的内存空间和系统资源,可以并发执行多个进程,每个进程相互独立。
线程:线程(Thread)是进程中的一个执行单元,它是操作系统能够进行调度的最小单位。多个线程可以在同一个进程内并发执行,并共享进程的资源,如内存和文件句柄。线程的引入使得进程能够在多个任务之间进行并发处理,提升了程序的效率和响应能力。
二、进程地址空间
进程的地址空间大小取决于系统架构和配置。对于32位系统,通常是4 GB(2^32字节)。
这里的地址空间并不是物理内存,而是虚拟内存,虚拟内存通过内存管理单元(MMU)和分页机制映射到物理内存,物理内存的大小由计算机硬件决定。上图中代码区存放当前运行程序的二进制代码,位于代码段,字符常量区存储字符串字面量和字符常量,位于.rodata段(只读),初始化数据存储已初始化的全局变量和静态变量,位于.data段(可读可写),未初始化数据保存未初始化的全局变量和静态变量,位于.bss段。堆中存储动态分配的内存中的数据(通过malloc、new)。共享区主要用于存储共享库,栈用于存储局部变量,函数的参数以及返回值等。
三、进程和线程的区别
进程是操作系统分配资源的最小单位,每个进程都有自己独立的地址空间和系统资源,进程之间是相互独立的,一个进程的崩溃不会影响其他进程。一个线程崩溃可能导致整个进程崩溃。进程的创建和销毁比线程的开销大。线程是系统调度的最小单位,是进程内的实际执行单位。一个进程可以包括多个线程,线程共享所属进程的地址空间和系统资源。线程之间的切换比进程之间的切换更快,因为线程共享相同的上下文和资源。线程间通信更加方便,可以直接读写共享内存,进程间通信需要通过特定的机制(如管道、信号、消息队列等等)。
在linux环境下创建一个进程:
#include <iostream>
#include <unistd.h> // 包含 fork 的头文件
int main() {
pid_t pid = fork();
if (pid < 0) {
// fork 失败
std::cerr << "fork failed.\n";
return 1;
} else if (pid == 0) {
// 子进程
std::cout << "Hello from child process!" << std::endl;
} else {
// 父进程
std::cout << "Hello from parent process!" << std::endl;
}
return 0;
}
在linux环境下创建一个线程:
#include <iostream>
#include <pthread.h>
// 线程函数
void* ThreadFunction(void* arg) {
std::cout << "Hello from the thread!" << std::endl;
return NULL;
}
int main() {
// 线程 ID
pthread_t thread;
// 创建线程
int result = pthread_create(&thread, NULL, ThreadFunction, NULL);
if (result != 0) {
std::cerr << "pthread_create failed.\n";
return 1;
}
// 等待线程结束
pthread_join(thread, NULL);
std::cout << "Thread completed.\n";
return 0;
}
四、进程、线程常用通信方式
进程:无名管道、有名管道、信号、信号量、消息队列、共享内存、套接字socket。
线程:信号量、互斥锁、条件变量。
五、多进程多线程的由来
多进程:早期计算机系统通常运行单个程序,这限制了资源的利用效率。通过引入多进程,系统能够同时运行多个程序或任务,使得计算机资源(如 CPU 和内存)得到更高效的使用。多进程概念的出现是为了提高计算机资源的利用效率、实现并发处理、增强系统的稳定性和安全性。
多线程: 多线程允许在单个进程内创建多个线程,这些线程共享同一进程的资源(如内存和文件描述符),而不像多进程那样需要额外的资源开销。线程比进程更轻量级,创建和切换的成本较低,从而提升了资源利用率和系统性能。多线程概念的由来主要是为了更高效地利用计算机资源,并提升应用程序的性能和响应能力。
下面是一个简单的多线程例子。
#include <iostream>
#include <pthread.h>
#include <vector>
// 线程函数
void* threadFunction(void* arg) {
int threadNum = *(int*)arg;
std::cout << "Hello from thread " << threadNum << "!" << std::endl;
return NULL;
}
int main() {
const int numThreads = 5; // 线程数量
std::vector<pthread_t> threads(numThreads); // 线程 ID 数组
std::vector<int> threadArgs(numThreads); // 线程参数数组
// 创建线程
for (int i = 0; i < numThreads; ++i) {
threadArgs[i] = i + 1;
int result = pthread_create(&threads[i], NULL, threadFunction, &threadArgs[i]);
if (result != 0) {
std::cerr << "pthread_create failed with error code " << result << std::endl;
return 1;
}
}
// 等待所有线程结束
for (int i = 0; i < numThreads; ++i) {
pthread_join(threads[i], NULL);
}
std::cout << "All threads completed." << std::endl;
return 0;
}