概要
线程和进程是两种不同的并发执行单元,它们都可以实现多任务的执行,但是有各自的特点和区别。
- 线程是操作系统独立调度和执行的基本单位,它是进程的一个组成部分,一个进程可以包含多个线程,它们共享进程的地址空间、数据和资源。线程有自己的栈、寄存器和程序计数器,它们之间可以直接通信和协作。
- 进程是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。进程有自己独立的地址空间、数据和资源,进程之间通过通信机制(如管道、信号、消息队列等)来交换信息。
要调用线程和进程,需要使用不同的方法,具体取决于操作系统和编程语言。一般来说,有以下几种常见的方法:
- 在 Linux 系统中,可以使用 fork() 函数来创建一个新的进程,该函数会复制当前进程的地址空间、数据和资源,并返回一个进程标识符(PID)。父进程和子进程可以通过 PID 来区分自己,并继续执行不同的代码。可以使用 wait() 函数来等待子进程结束,并获取其返回值。可以使用 exec() 系列函数来在子进程中执行另一个程序,并替换其地址空间、数据和资源。
- 在 Linux 系统中,可以使用 pthread 库来创建和管理线程。pthread 库提供了一系列函数来创建、终止、同步、互斥和调度线程。可以使用 pthread_create() 函数来创建一个新的线程,并指定其要执行的函数和参数。可以使用 pthread_join() 函数来等待一个线程结束,并获取其返回值。可以使用 pthread_exit() 函数来终止当前线程,并返回一个值。
- 在 Windows 系统中,可以使用 CreateProcess() 函数来创建一个新的进程,并指定其要执行的程序、命令行参数、安全属性、优先级等。该函数会返回一个进程句柄(HANDLE),用于标识和控制该进程。可以使用 WaitForSingleObject() 函数来等待一个进程结束,并获取其退出码。可以使用 TerminateProcess() 函数来强制终止一个进程,并指定其退出码。
- 在 Windows 系统中,可以使用 CreateThread() 函数来创建一个新的线程,并指定其要执行的函数和参数。该函数会返回一个线程句柄(HANDLE),用于标识和控制该线程。可以使用 WaitForSingleObject() 函数来等待一个线程结束,并获取其退出码。可以使用 ExitThread() 函数来终止当前线程,并返回一个值。
- 在 C 语言中,可以使用 system() 函数来调用操作系统命令,并创建一个新的进程来执行该命令。该函数会阻塞当前进程,直到命令执行完毕,并返回命令的退出码。
- 在 C++ 语言中,可以使用 std::thread 类来创建和管理线程。std::thread 类提供了构造函数、析构函数、移动赋值运算符等方法来创建、销毁、转移线程对象。可以向构造函数传递一个可调用对象(如函数指针、函数对象、lambda 表达式等)和参数,来指定线程要执行的任务。可以使用 join() 方法来等待一个线程结束,并获取其返回值。可以使用 detach() 方法来分离一个线程,使其在后台运行。
进程和线程的概念
- 进程是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。
- 线程是进程中的一个执行单元,是操作系统进行调度的基本单位。
进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。 - 而线程是共享进程中的数据的,使用相同的地址空间,因此 CPU 切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
进程和线程的状态转换
- 进程有三种基本状态:就绪态、运行态和阻塞态。就绪态表示进程已经准备好运行,等待 CPU 分配时间片;运行态表示进程正在占用 CPU 执行;阻塞态表示进程因为等待某些事件而暂停执行,让出 CPU 资源。
- 线程也有类似的状态转换,但是线程的阻塞不会导致整个进程的阻塞,同样线程的就绪也不一定会立即运行。
- 进程和线程的状态转换都需要操作系统的干预,操作系统通过进程控制块(PCB)来管理和调度进程,通过原语来实现进程状态转换的功能。
进程和线程的通信方式
- 进程间通信(IPC)需要借助操作系统提供的服务,常见的方式有管道、消息队列、信号量、共享内存、套接字等。
- 线程间通信(TIC)由于共享同一进程的资源,可以直接读写数据,无需操作系统干预。但是为了避免数据不一致或冲突,需要使用互斥锁、信号量等同步机制来保证线程安全。
线程共享进程的资源
-
线程是进程的一部分,它们在同一个进程内共享同一内存空间和其他资源。
-
线程之间共享的资源包括:
- 进程代码段:这里保存的是编译后的可执行机器指令,所有线程都可以访问和执行相同的程序逻辑。
- 进程的公有数据:这里保存的是全局变量和静态变量,所有线程都可以直接读写这些数据。
- 进程打开的文件描述符:如果一个线程打开了文件,其他线程也可以访问该文件的数据。
- 信号处理器:进程中的所有线程共享信号处理程序,当进程收到信号时,所有线程都会受到影响。
- 进程的当前目录和进程用户ID与进程组ID:这些是进程的状态信息,所有线程都共享。
-
线程之间不共享的资源包括:
- 栈区:每个线程都有自己独立的栈区,用来保存函数运行时的信息,如返回值、参数、局部变量、寄存器等。虽然没有严格的隔离机制,但一般不建议一个线程修改另一个线程的栈区数据。
- 程序计数器:每个线程都有自己的程序计数器,用来记录下一条要执行的指令地址。
- 线程状态:每个线程都有自己的状态,如就绪、运行、阻塞等。
C++中的线程进程调用
C++11标准提供了头文件来支持多线程编程,可以使用std::thread类来创建和管理线程。
下面是一些C++中的线程或进程调用示例:
- 创建一个简单的线程,执行一个无参数的函数:
#include <iostream>
#include <thread>
void thread_fun() // 线程要执行的函数
{
std::cout << "Hello, thread!" << std::endl;
}
int main()
{
std::thread t(thread_fun); // 创建一个线程对象t,并传入要执行的函数
t.join(); // 等待线程t结束
return 0;
}
- 创建一个带参数的线程,执行一个有参数的函数:
#include <iostream>
#include <thread>
void thread_fun(int x) // 线程要执行的函数,有一个整型参数
{
std::cout << "The parameter is " << x << std::endl;
}
int main()
{
std::thread t(thread_fun, 42); // 创建一个线程对象t,并传入要执行的函数和参数
t.join(); // 等待线程t结束
return 0;
}
- 创建多个线程,共享同一个函数,并使用互斥锁来保护共享数据:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 创建一个互斥锁对象
int counter = 0; // 创建一个共享数据
void thread_fun() // 线程要执行的函数
{
mtx.lock(); // 上锁,保护共享数据
counter++; // 修改共享数据
std::cout << "Thread " << counter << " is running" << std::endl;
mtx.unlock(); // 解锁,释放共享数据
}
int main()
{
const int N = 10; // 定义线程数量
std::thread threads[N]; // 创建一个线程数组
for (int i = 0; i < N; i++)
{
threads[i] = std::thread(thread_fun); // 为每个线程对象分配要执行的函数
}
for (int i = 0; i < N; i++)
{
threads[i].join(); // 等待每个线程结束
}
return 0;
}
- 在Linux系统中,创建一个新的进程,并在子进程中执行另一个程序:
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork(); // 创建一个新的进程,返回进程标识符
if (pid == -1) // fork失败
{
std::cerr << "Fork error" << std::endl;
return -1;
}
else if (pid == 0) // 子进程
{
std::cout << "Child process, pid = " << getpid() << std::endl;
execl("/bin/ls", "ls", "-l", NULL); // 在子进程中执行ls命令,替换子进程的地址空间、数据和资源
std::cerr << "Exec error" << std::endl; // 如果exec成功,这一行不会被执行
return -1;
}
else // 父进程
{
std::cout << "Parent process, pid = " << getpid() << std::endl;
int status;
wait(&status); // 等待子进程结束,并获取其返回值
std::cout << "Child process exited with status = " << status << std::endl;
return 0;
}
}