重要概念
进程(Process)
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。每个进程都有自己独立的地址空间,包括文本区域(程序代码)、数据区域(变量和进程运行中使用的动态分配的内存)和堆栈(用于存放函数参数、局部变量等)。进程间相互独立,一个进程崩溃不会直接影响到其他进程。但是进程间通信(IPC)需要特定的OS支持。
进程有几个关键特性:
- 独立性:每个进程在操作系统中相互独立,有自己的地址空间。
- 动态性:进程是动态产生、变化和消亡的。
- 并发性:多个进程可以并发执行。
线程(Thread)
线程,有时被称为轻量级进程,是进程的执行单元。一个进程中可以包含多个线程,它们共享进程的地址空间和资源,但每个线程有自己的执行序列(即线程执行的代码)、程序计数器、寄存器集合和栈。线程之间的信息共享和通信更为容易,但需要注意同步和互斥问题,以避免竞态条件。
线程的关键特性包括:
- 轻量级:线程的创建和上下文切换比进程更快、更高效。
- 共享性:线程共享所属进程的资源,如内存和文件。
- 独立性:每个线程有自己的执行路径和状态。
IPC(Inter-Process Communication)
IPC,即进程间通信,是指在不同进程之间传递数据或信号的机制。由于进程间相互独立,它们不能直接访问对方的地址空间,IPC提供了一种安全的方法来交换数据,包括管道(pipe)、信号(signal)、共享内存(shared memory)、消息队列(message queue)、信号量(semaphore)等机制。
IPC的主要目的是:
- 数据共享:不同进程之间共享信息。
- 任务协作:多个进程协同完成一项任务。
- 资源共享:不同进程共享系统资源,如打印机等。
- 进程控制:一个进程可以启动和控制另一个进程的执行。
关键函数
fork()
函数
fork()
是 Unix 和 Linux 操作系统中用于创建新进程的系统调用。调用 fork()
会创建一个新的进程,这个新进程被称为子进程,它是调用进程(父进程)的副本。子进程和父进程会继续从 fork()
调用之后的位置开始执行。fork()
在父进程中返回新创建的子进程的进程ID,在子进程中返回0。如果出错,fork()
会在父进程中返回一个负值。
fork()
创建的新进程拥有父进程数据段、堆和栈的副本,但是这两个进程的这些部分在物理内存中是独立的。父子进程只共享代码段。
用法示例:
c
pid_t pid = fork();
if (pid == -1) {
// 错误处理
} else if (pid > 0) {
// 父进程代码
} else {
// 子进程代码
}
pthread_create()
函数
pthread_create()
是 POSIX 线程库中用于创建新线程的函数。POSIX 线程,或 "pthread",是一个可移植的线程标准,定义了线程的创建、控制和终止等操作。pthread_create()
调用会创建一个新的线程并将其加入到当前进程中。新线程从指定的函数地址开始执行。
pthread_create()
函数需要4个参数:一个指向线程标识符的指针、一个指定线程属性的指针(通常设置为NULL,表示默认属性)、线程函数的起始地址以及传递给线程函数的参数。线程函数通常有一个指向 void
的指针作为参数,返回一个指向 void
的指针。
用法示例:
c
void *thread_function(void *arg) {
// 线程执行的代码
}
pthread_t thread_id;
int result;
result = pthread_create(&thread_id, NULL, thread_function, (void*) arg);
if (result != 0) {
// 错误处理
}
总结
fork()
用于创建一个与当前进程几乎完全相同的新进程。它们之间的主要区别在于PID和一些资源统计数据。pthread_create()
用于在同一个进程内创建一个新线程,这些线程共享进程的地址空间和资源,但拥有独立的执行路径和堆栈。- 在使用这些函数时,特别要注意对返回值的检查,以便正确处理错误情况。在多线程编程中还需要考虑线程之间的同步和数据一致性问题。
用多进程实现累加和计算
要使用多进程实现累加和计算,我们可以使用 fork()
来创建子进程,每个子进程负责计算数列中一部分的和。主进程(父进程)等待所有子进程完成计算,并收集它们的计算结果来得到最终的累加和。
在这个示例中,我们将通过管道(pipe)来实现父子进程间的通信,以便子进程可以将其计算结果发送回父进程。
以下是一个简单的实现:
c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/time.h>
#define NUM_PROCESSES 10 // 定义子进程数量
// 累加函数,每个子进程执行的代码
long sumRange(int start, int end) {
long sum = 0;
for (int i = start; i <= end; ++i) {
sum += i;
}
return sum;
}
int main() {
int numbersPerProcess = 1000000000 / NUM_PROCESSES;
int pipefds[2 * NUM_PROCESSES]; // 为每个子进程创建一个管道
pid_t pids[NUM_PROCESSES];
struct timeval start, end;
// 获取开始时间
gettimeofday(&start, NULL);
// 创建管道
for (int i = 0; i < NUM_PROCESSES; ++i) {
if (pipe(pipefds + i*2) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
}
// 创建子进程
for (int i = 0; i < NUM_PROCESSES; ++i) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pids[i] == 0) { // 子进程
close(pipefds[i*2]); // 关闭读端
int start = i * numbersPerProcess + 1;
int end = (i + 1) * numbersPerProcess;
long partialSum = sumRange(start, end);
write(pipefds[i*2 + 1], &partialSum, sizeof(partialSum)); // 写入计算结果
close(pipefds[i*2 + 1]); // 关闭写端
exit(EXIT_SUCCESS);
}
}
// 父进程
long totalSum = 0, readSum = 0;
// 等待子进程并读取其计算结果
for (int i = 0; i < NUM_PROCESSES; ++i) {
close(pipefds[i*2 + 1]); // 关闭写端
read(pipefds[i*2], &readSum, sizeof(readSum));
totalSum += readSum;
close(pipefds[i*2]); // 关闭读端
wait(NULL); // 等待子进程结束
}
// 获取结束时间
gettimeofday(&end, NULL);
// 计算并打印执行时间
long seconds = end.tv_sec - start.tv_sec;
long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
printf("NUM_PROCESSES = %d\n", NUM_PROCESSES);
printf("Total Sum = %ld\n", totalSum);
printf("Time taken: %ld microseconds (%.3f seconds)\n", micros, micros / 1000000.0);
return 0;
}
在这个示例中:
- 我们首先为每个子进程创建了一个管道,以便它们可以将计算结果发送回父进程。
- 对于每个子进程,我们计算了一部分范围内的数字和,并将结果写入管道的写端。
- 在父进程中,我们关闭了管道的写端,从每个管道的读端读取子进程的计算结果,并将这些结果相加以得到最终的累加和。
- 父进程等待所有子进程完成,确保所有资源得到妥善处理。
单进程和多进程对比
单进程
多进程
用多线程实现累加和计算
使用多线程实现累加和计算,我们可以将要累加的数值范围分割给多个线程,每个线程计算自己那部分的和,最后将所有线程的结果累加起来得到最终结果。这里,我们将使用 POSIX 线程库(pthread)来实现这个多线程累加和计算。
以下是一个简单的实现方案:
- 定义一个结构体来传递给每个线程,这个结构体包含了线程需要知道的信息,比如计算的起始和结束值。
- 创建多个线程,每个线程负责计算一部分数值的和。
- 等待所有线程完成,然后汇总每个线程计算的结果。
- 输出最终的累加和。
示例代码
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#define NUM_THREADS 10 // 定义线程数量
typedef struct {
int start;
int end;
long sum; // 用于存储这个线程计算的部分和
} ThreadData;
// 线程函数
void* sumRange(void* arg) {
ThreadData* data = (ThreadData*)arg;
data->sum = 0;
for (int i = data->start; i <= data->end; ++i) {
data->sum += i;
}
pthread_exit((void*) &(data->sum));
}
int main() {
pthread_t threads[NUM_THREADS];
ThreadData threadData[NUM_THREADS];
int numbersPerThread = 1000000000 / NUM_THREADS;
long totalSum = 0;
struct timeval start, end;
// 获取开始时间
gettimeofday(&start, NULL);
// 创建线程
for (int i = 0; i < NUM_THREADS; ++i) {
threadData[i].start = i * numbersPerThread + 1;
threadData[i].end = (i + 1) * numbersPerThread;
pthread_create(&threads[i], NULL, sumRange, (void*)&threadData[i]);
}
// 等待线程完成并汇总结果
for (int i = 0; i < NUM_THREADS; ++i) {
void* status;
pthread_join(threads[i], &status);
totalSum += *(long*)status;
}
// 获取结束时间
gettimeofday(&end, NULL);
// 计算并打印执行时间
long seconds = end.tv_sec - start.tv_sec;
long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
printf("NUM_THREADS = %d\n", NUM_THREADS);
printf("Total Sum = %ld\n", totalSum);
printf("Time taken: %ld microseconds (%.3f seconds)\n", micros, micros / 1000000.0);
return 0;
}