Linux(多进程与多线程)

目录

1、进程与线程概念

1.1 进程

1.2 线程

1.3 进程与线程区别

2、多进程

2.1多进程概念

2.2 进程相关API

2.3 多进程编程

3、多线程

3.1 多线程概念

3.2 多线程相关API

3.3 多线程编程


1、进程与线程概念

1.1 进程

在计算机科学中,进程是正在执行中的程序的实例。一个进程包括程序的代码、数据、执行上下文和操作系统分配的资源。进程是操作系统中的最小执行单位,操作系统通过管理和调度进程来实现多任务处理。

以下是关于进程的一些关键概念:

  1. 程序 vs. 进程

    • 程序(Program):是一个静态的代码文件,包含了计算机指令。程序本身并没有在计算机上执行,而只是一组指令的集合。
    • 进程(Process):是程序在计算机上实际运行的实例。进程拥有自己的内存空间、寄存器、执行状态等。
  2. 进程特点

    • 独立性:每个进程都是独立运行的实体,不受其他进程的影响。
    • 并发性:多个进程可以同时执行,操作系统通过时间分片等机制实现并发。
    • 动态性:进程可以创建、运行和结束,具有生命周期。
  3. 进程状态

    • 就绪(Ready):进程已准备好执行,等待分配处理器资源。
    • 运行(Running):进程正在执行指令。
    • 阻塞(Blocked):进程等待某个事件(如输入输出完成)发生。
    • 终止(Terminated):进程执行完毕或被终止。
  4. 进程间通信: 进程可能需要相互通信和协作。常用的进程间通信方式包括管道、信号、共享内存、消息队列和套接字等。

  5. 进程调度: 操作系统负责管理和调度进程的执行。调度算法决定了哪个进程将获得处理器资源,并控制进程之间的切换。

  6. 进程控制块(PCB): PCB 是操作系统用于管理和描述进程的数据结构,包含了进程的状态、程序计数器、寄存器值、内存指针等信息。

进程是操作系统的基础概念,操作系统通过对进程的管理和调度,实现了计算机的多任务处理和资源共享。

1.2 线程

线程(Thread)是操作系统中最小的调度单位,它是进程的一个执行流程,是在进程内部进行调度和执行的基本单元。线程在同一进程内共享该进程的内存空间和资源,但每个线程拥有自己的栈空间和寄存器状态。

以下是关于线程的一些关键概念:

  1. 线程特点

    • 线程是在进程内部创建和管理的。一个进程可以包含多个线程,这些线程共享进程的资源。
    • 线程之间的切换开销较小,因为它们共享同一进程的内存空间和上下文。
    • 线程的创建和销毁通常比进程更快,因为线程共享进程的资源,不需要为每个线程都创建独立的资源。
    • 线程可以用于实现并发执行,提高系统的响应性和资源利用率。
  2. 线程与进程的关系

    • 一个进程可以包含多个线程,这些线程共享进程的代码、数据和资源。
    • 多个线程在同一进程内运行,它们之间可以进行通信和同步。
    • 多个进程则是相互独立的,拥有各自独立的内存空间和资源。
  3. 线程调度: 操作系统负责管理和调度线程的执行。不同的线程调度算法决定了哪个线程将获得处理器资源。

  4. 线程同步: 在多线程编程中,线程之间可能会互相干扰或冲突。线程同步机制,如互斥锁、信号量和条件变量,用于协调线程之间的操作,以确保数据一致性和正确性。

  5. 用户级线程和内核级线程

    • 用户级线程:线程的创建、调度和管理完全由用户程序控制,操作系统对线程无感知。
    • 内核级线程:线程的创建、调度和管理由操作系统控制,操作系统直接管理线程。

线程是操作系统中的基本调度单位,适用于需要并发执行的任务。通过多线程编程,可以充分利用多核处理器的能力,提高程序的性能和响应性。但同时,多线程编程也需要考虑到线程安全和同步的问题。

1.3 进程与线程区别

  1. 资源分配: 进程有独立的资源(内存、文件句柄等),而线程共享所属进程的资源。
  2. 切换开销: 进程之间的切换开销较大,线程之间切换开销较小。
  3. 隔离性: 进程之间相互隔离,一个进程的错误不会影响其他进程;线程共享内存,一个线程的错误可能会影响其他线程。
  4. 通信与同步: 进程通信较复杂,线程之间共享内存,通信和同步更方便但也更容易出错。

通常来说,多线程编程更轻量,可以更好地利用多核处理器,但需要更仔细地处理并发访问共享资源的问题。多进程编程相对来说更安全,但开销较大。选择使用进程还是线程取决于你的应用需求和对多任务处理的优先级。

2、多进程

2.1多进程概念

多个独立的进程在不同的地址空间中运行,相对安全但开销较大。

多进程编程是一种并发编程的方式,它利用操作系统的多进程能力来实现多个任务的并行执行。在多进程编程中,每个任务被封装为一个独立的进程,它们在不同的内存空间中运行,相互之间相对独立。这种方法可以有效利用多核处理器,提高程序的性能和响应性。

2.2 进程相关API

  1. fork() 函数:

    • 作用:创建一个新的子进程,子进程是父进程的副本,执行相同的程序代码。
    • 返回值:在父进程中,返回子进程的 PID;在子进程中,返回 0;如果失败,返回 -1。
    • 头文件:<unistd.h>
  2. exec 函数族(如 execl, execv, execle, execve 等):

    • 作用:用于在当前进程中加载并执行一个新的程序。
    • 使用不同的函数名和参数,支持不同的参数传递方式。
    • 返回值:只在出错时返回 -1,成功执行后不会返回。
  3. wait()waitpid() 函数

    • 作用:等待子进程结束,以获取子进程的退出状态。
    • wait() 阻塞调用进程,直到任意子进程退出。
    • waitpid() 允许指定要等待的子进程的 PID,可以非阻塞等待。
    • 返回值:退出的子进程 PID 或 -1(出错时)。
    • 头文件:<sys/wait.h>
  4. exit() 函数

    • 作用:终止调用进程,并返回一个状态码给父进程。
    • 参数:传递给父进程的状态码。
    • 没有返回值,直接终止进程。
  5. getpid() 函数:

    • 作用:获取当前进程的 PID(进程标识符)。
    • 返回值:当前进程的 PID。
    • 头文件:<unistd.h>
  6. getppid() 函数:

    • 作用:获取当前进程的父进程的 PID。
    • 返回值:父进程的 PID。
    • 头文件:<unistd.h>
  7. kill() 函数:

    • 作用:向指定进程发送信号。
    • 参数:目标进程的 PID 和信号编号。
    • 返回值:成功返回 0,失败返回 -1。
  8. getuid()getgid() 函数:

    • 作用:获取当前进程的用户 ID 和组 ID。
    • 返回值:用户 ID 和组 ID。
    • 头文件:<unistd.h>
  9. setuid()setgid() 函数:

    • 作用:设置当前进程的用户 ID 和组 ID。
    • 参数:要设置的用户 ID 和组 ID。
    • 返回值:成功返回 0,失败返回 -1。
  10. sleep() 函数:

    • 作用:让当前进程休眠一段指定的时间。
    • 参数:休眠时间(秒)。
    • 返回值:休眠完毕后返回剩余休眠时间(0 表示完整休眠,-1 表示休眠被中断)。

2.3 多进程编程

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h> // 包含用于等待子进程的头文件

int main() {
    // 创建第一个子进程
    // 在父进程中,fork() 函数返回子进程的 PID,而在子进程中,fork() 返回 0。
    pid_t child1_pid = fork();

    if (child1_pid == -1) {
        // fork() 失败时返回 -1,输出错误信息并退出程序
        perror("Error creating child process");
        return 1;
    }

    if (child1_pid == 0) {
        // 子进程 1 逻辑
        printf("Child 1: My PID is %d\n", getpid());
        // 子进程 1 执行完毕
        return 0;
    }

    // 创建第二个子进程
    pid_t child2_pid = fork();

    if (child2_pid == -1) {
        // fork() 失败时返回 -1,输出错误信息并退出程序
        perror("Error creating child process");
        return 1;
    }

    if (child2_pid == 0) {
        // 子进程 2 逻辑
        printf("Child 2: My PID is %d\n", getpid());
        // 子进程 2 执行完毕
        return 0;
    }

    // 父进程逻辑
    printf("Parent: My PID is %d\n", getpid());
    printf("Parent: Child 1 PID is %d, Child 2 PID is %d\n", child1_pid, child2_pid);

    // 等待两个子进程执行完毕
    wait(NULL); // 等待第一个子进程
    wait(NULL); // 等待第二个子进程

    return 0;
}

运行结果

3、多线程

3.1 多线程概念

同一进程内的多个线程共享同一地址空间,更轻量但需要注意同步和共享数据。多线程编程可以提高程序的并发性和效率。然而,多线程编程需要注意处理并发访问共享资源的问题,如线程安全和竞态条件。

多线程编程是在同一个进程内创建和管理多个线程,使得这些线程可以并发执行不同的任务。每个线程共享进程的内存空间和资源,但每个线程有自己的栈空间和执行上下文。

3.2 多线程相关API

  1. pthread_create() 函数:

    • 作用:创建一个新线程。
    • 参数:新线程的引用、线程属性、线程函数和参数。
    • 返回值:成功创建线程时返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  2. pthread_join() 函数:

    • 作用:等待一个线程终止,并获取它的退出状态。
    • 参数:要等待的线程的引用、线程退出状态的指针。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  3. pthread_exit() 函数:

    • 作用:终止当前线程,可指定线程退出状态。
    • 参数:线程退出状态。
    • 没有返回值,直接终止线程。
    • 头文件:<pthread.h>
  4. pthread_self() 函数:

    • 作用:获取当前线程的线程 ID。
    • 返回值:当前线程的线程 ID。
    • 头文件:<pthread.h>
  5. pthread_mutex_init() 函数:

    • 作用:初始化互斥锁。
    • 参数:互斥锁引用和互斥锁属性(可为 NULL)。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  6. pthread_mutex_lock()pthread_mutex_unlock() 函数:

    • 作用:锁定和解锁互斥锁。
    • 参数:互斥锁引用。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  7. pthread_mutex_destroy() 函数:

    • 作用:销毁互斥锁。
    • 参数:互斥锁引用。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  8. pthread_cond_init() 函数:

    • 作用:初始化条件变量。
    • 参数:条件变量引用和条件变量属性(可为 NULL)。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  9. pthread_cond_wait()pthread_cond_signal() 函数:

    • 作用:等待和唤醒条件变量。
    • 参数:条件变量引用和互斥锁引用。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>
  10. pthread_cond_destroy() 函数:

    • 作用:销毁条件变量。
    • 参数:条件变量引用。
    • 返回值:成功返回 0,否则返回错误码。
    • 头文件:<pthread.h>

3.3 多线程编程

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NUM_THREADS 4 //创建线程数量

// 共享资源
int shared_counter = 0;
pthread_mutex_t mutex; // 互斥锁

// 增加计数器的函数,多个线程会同时调用这个函数
void* increment_counter(void* arg) {
    for (int i = 0; i < 100000; ++i) {
        pthread_mutex_lock(&mutex); // 加锁,开始临界区
        shared_counter++; // 对共享资源进行操作
        pthread_mutex_unlock(&mutex); // 解锁,结束临界区
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS]; // 存储线程的数组
    
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
    
    // 创建多个线程
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_create(&threads[i], NULL, increment_counter, NULL);
    }
    
    // 等待所有线程执行完毕
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&mutex); // 销毁互斥锁

    printf("Final counter value: %d\n", shared_counter);
    
    return 0;
}

运行结果(为啥输出了三次?

原因:将测试多线程和多进程的两个源文件编译后在一个main.c 文件中进行了调用,在子函数中进行创建的新进程,在子函数被调用运行结束后还会继续执行父进程中之后的函数,所以测试多线程的代码被调用了三次

猜你喜欢

转载自blog.csdn.net/qq_57594025/article/details/132525587