Linux下的并发编程:多进程与多线程编程

重要概念

进程(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;
}

在这个示例中:

  1. 我们首先为每个子进程创建了一个管道,以便它们可以将计算结果发送回父进程。
  2. 对于每个子进程,我们计算了一部分范围内的数字和,并将结果写入管道的写端。
  3. 在父进程中,我们关闭了管道的写端,从每个管道的读端读取子进程的计算结果,并将这些结果相加以得到最终的累加和。
  4. 父进程等待所有子进程完成,确保所有资源得到妥善处理。

单进程和多进程对比

单进程

多进程

用多线程实现累加和计算

使用多线程实现累加和计算,我们可以将要累加的数值范围分割给多个线程,每个线程计算自己那部分的和,最后将所有线程的结果累加起来得到最终结果。这里,我们将使用 POSIX 线程库(pthread)来实现这个多线程累加和计算。

以下是一个简单的实现方案:

  1. 定义一个结构体来传递给每个线程,这个结构体包含了线程需要知道的信息,比如计算的起始和结束值。
  2. 创建多个线程,每个线程负责计算一部分数值的和。
  3. 等待所有线程完成,然后汇总每个线程计算的结果。
  4. 输出最终的累加和

示例代码

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;
}

单线程和多线程结果对比

单线程

多线程

相关推荐
groundhappy11 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕12 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫13 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生14 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1115 小时前
linux基础IO
linux·运维·服务器
BelongPanda17 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯17 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983417 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器
yunwei3717 小时前
eBPF 教程:检查 exec 后真正安装的可执行镜像
linux·开源
笑锝没心没肺18 小时前
yum update 报错:GPG key retrieval failed: [Errno 14] HTTPS Error 404 - Not Found
linux·运维·服务器