• 线程与进程的实现


    目录

    1. 线程的实现方式

    1. 用户级线程(User-Level Threads,ULTs)

    2. 内核级线程(Kernel-Level Threads,KLTs)

    2. 线程的具体实现

    2.1 用户级线程的实现

    2.2 内核级线程的实现

    3. 线程的创建与终止

    4. 进程的创建与终止

    5. 进程同步与互斥

    6. 进程通信

    总结


    1. 线程的实现方式

            线程作为操作系统中重要的执行单元,可以通过不同的方式实现,主要有用户级线程和内核级线程两种实现方式。每种方式有其优缺点,适合不同的应用场景和需求。

    1. 用户级线程(User-Level Threads,ULTs)

    概述
            用户级线程由用户程序自行管理,而操作系统内核对此完全不了解。线程库在用户空间实现线程的创建、调度、切换和终止等功能。

    特点

    • 线程管理:用户级线程的管理完全由用户程序通过线程库实现,内核不参与线程的调度。
    • 线程切换:线程切换在用户空间进行,不涉及内核态切换,因此切换速度快。
    • 多线程支持:一个进程可以有多个用户级线程,线程库管理这些线程的执行。

    优点

    • 切换速度快:由于线程切换不需要内核的介入,仅在用户空间进行,因此上下文切换速度相对较快,性能较高。
    • 实现简单:用户级线程的实现和调度机制较简单,不需要复杂的内核机制支持。
    • 灵活性高:用户程序可以灵活地自定义线程调度算法,调整线程优先级等,满足特定应用的需求。

    缺点

    • 缺乏内核支持:由于内核不了解用户级线程的存在,同一进程中的所有线程共享一个进程上下文,进程阻塞会导致该进程的所有线程阻塞,缺乏并发性。例如,当一个用户级线程执行I/O操作而阻塞时,整个进程都会阻塞。
    • 同步和互斥问题:用户级线程需要自行维护线程间的同步和互斥机制,如果实现不当,容易引发安全性问题。

    应用场景
    适用于轻量级的多线程任务、实时性要求较高的应用以及对线程管理拥有较高控制需求的场景。

    2. 内核级线程(Kernel-Level Threads,KLTs)

    概述
            内核级线程由操作系统内核来管理,内核负责线程的创建、调度、切换和终止,并维护线程间的同步和互斥。

    特点

    • 线程管理:内核对线程的管理和调度,在内核态实现。
    • 线程切换:线程切换需要在内核态进行,上下文切换相对较慢。
    • 多线程支持:内核能够感知每个线程的存在,并且每个线程都拥有独立的内核线程上下文。

    优点

    • 并发性强:内核级线程具有操作系统级的调度能力,能够在多核处理器上实现真正的并行执行。即使一个线程阻塞,内核也可以调度其他线程运行,实现更高的并发性。
    • 系统支持的同步机制:内核提供原子操作和同步原语(如信号量、互斥锁等),提高线程间同步和互斥的安全性和可靠性。
    • 资源独立性:内核维护各个线程的资源和状态,使得线程调度更加高效和均衡。

    缺点

    • 切换速度慢:由于线程切换需要进入内核态,进行上下文切换的开销较大,可能会影响性能。
    • 实现复杂:内核级线程的实现和管理较为复杂,需要内核提供支持,增加了操作系统内核的复杂性。
    • 资源消耗大:每个线程独立的系统上下文增加了内核的开销,比如线程控制块(TCB)和线程栈等。

    应用场景
            适用于计算密集型任务、I/O密集型任务、需要高并发性的应用和对线程安全性要求较高的场景。

    混合实现(Hybrid Threads)

            一些现代操作系统采用混合实现方式,结合了用户级线程和内核级线程的优点。例如,使用轻量级进程(Lightweight Process,LWP)和线程库的组合。LWP由内核管理,多个用户级线程可以映射到一个或多个LWP上,实现高效的线程调度和同步管理。

    2. 线程的具体实现

    2.1 用户级线程的实现

            用户级线程的实现主要有两种策略:协作式线程和抢占式线程。每种策略都有其独特的机制、优缺点和适用的场景。

    1. 协作式线程(Cooperative Threads)

    概述
            协作式线程的切换由线程自身控制,即线程的调度是由线程自愿让出CPU时间来实现。当前线程主动决定何时切换到其他线程,线程之间通过显式的调用来进行切换。

    实现机制

    • 显式切换:线程在代码执行的某个特定点显式地调用线程库的切换函数,将执行权让给其他线程。
    • 主动让出:每个线程在合适的时候主动让出CPU,比如在完成一个子任务或遇到I/O操作时。

    优点

    • 简单高效:由于切换由线程自身控制,避免了操作系统的调度开销,线程切换速度快,性能高。
    • 无竞争:没有调度竞争,避免了由于系统强制抢占导致的资源竞争和争用,减少了上下文切换的开销。
    • 代码可读性:显式的切换点使得代码的流控制更加清晰,方便调试和分析。

    缺点

    • 不公平性:线程调度依赖于线程自身的运行状态,如果某个线程没有主动让出CPU,其他线程将无法获得执行机会,可能导致“饥饿”问题。
    • 编程复杂性:需要开发者在代码中明确插入让出点,增加了编程的复杂性,且容易导致切换点设置不当的问题。
    • 低响应性:由于没有优先级概念,协作式线程的响应速度较慢,特别是在处理实时任务时显得力不从心。

    应用场景
            适用于对实时性要求不高、线程任务独立且调用顺序明确的场景。例如,基于事件驱动的编程模型以及独立的任务处理等。

    2. 抢占式线程(Preemptive Threads)

    概述
            抢占式线程的切换由系统调度程序控制,即调度程序会定期检查各个线程的状态,并在需要时强制进行线程切换。调度程序可以强制暂停当前线程,将CPU时间分配给其他有更高优先级或准备就绪的线程。

    实现机制

    • 计时器中断:通过使用计时器中断,调度程序可以定期打断当前线程,检查其他线程的状态,进行上下文切换。
    • 优先级调度:调度程序按照一定的调度算法(如时间片轮转、优先级调度)来选择并切换到新的线程。
    • 强制抢占:当发现有更高优先级的线程处于就绪状态时,调度程序会强制抢占当前线程的执行权。

    优点

    • 公平性:调度程序可以公平地分配CPU时间给各个线程,避免了任何一个线程独占CPU资源,防止“饥饿”问题。
    • 实时性:可以按照优先级调度,为高优先级线程提供更快速的响应,适用于实时应用。
    • 自动化调度:线程切换由系统自动管理,简化了开发者的工作,不需要显式地在代码中插入切换点。

    缺点

    • 性能开销:由于需要维护线程状态和进行调度判断,抢占式线程的上下文切换开销较大,性能相对较低。
    • 复杂性:调度程序的设计和实现较为复杂,需要处理多线程间的同步和互斥,管理线程的优先级和状态等。
    • 资源竞争:多个线程间的资源竞争可能导致锁竞争、死锁等问题,增加了系统设计的复杂性。

    应用场景
            适用于需要高并发、实时性要求较高和多任务协作的场景。例如,交互性强的桌面应用程序、实时数据处理系统、多用户服务器等。

    2.2 内核级线程的实现

            内核级线程是由操作系统内核管理的线程,内核负责线程的创建、调度、切换和终止。内核级线程的实现主要采用以下两种方式:进程内线程和轻量级进程(Lightweight Process, LWP)。每种方式有其独特的实现机制和应用场景。

    1. 进程内线程(Intra-Process Threads)

    概述
            进程内线程是一种内核级线程实现方式,在这种方式下,每个进程可以包含多个线程,这些线程共享同一个地址空间和资源,但能够独立调度和执行。

    实现机制

    • 共享地址空间:进程内的所有线程共享相同的地址空间,这意味着所有线程可以访问相同的内存区域和全局变量。
    • 资源共享:线程共享进程的资源,如打开的文件描述符、信号处理器和其它内核对象。
    • 独立栈和寄存器:每个线程拥有独立的栈和寄存器集合,以维护线程的私有数据和执行状态。
    • 内核调度:操作系统内核调度和管理对这些线程的执行,确保线程之间的公平竞争和资源利用。

    优点

    • 高效的并发性:利用共享地址空间的特点,线程间通信和数据共享更为高效,无需进行复杂的进程间通信(IPC)。
    • 资源节省:由于线程共享进程资源(如文件描述符和地址空间),系统开销相对较小,可以支持大量并发线程。
    • 实时响应:内核调度可以及时响应高优先级线程的需求,提高系统的实时性。

    缺点

    • 同步问题:由于线程共享地址空间和资源,需要进行适当的同步和锁机制,避免竞态条件和数据同步问题。
    • 稳定性风险:一个线程的崩溃或非法操作可能影响整个进程,导致进程中的所有线程无法正常工作。

    应用场景
    适用于多线程并发任务,如多重文件处理、计算密集型任务、需要共享大量数据的应用等。

    2. 轻量级进程(Lightweight Process, LWP)

    概述
            轻量级进程是一种特殊的内核级线程实现方式,LWP拥有自己的堆栈和寄存器集合,但与其他LWP共享相同的地址空间和资源。LWP通常用于实现高级线程模型,如系统线程库(如Solaris线程库)。

    实现机制

    • 独立栈和寄存器:每个LWP拥有自己独立的栈和寄存器集合,以维护LWP的执行状态和私有数据。
    • 共享地址空间:所有LWP共享相同的地址空间和资源,与同一进程中的其他LWP共同使用进程的地址空间。
    • 内核管理和调度:LWP由操作系统内核管理和调度,内核负责在多个LWP之间切换和分配CPU时间。
    • 用户级线程支持:LWP通常用于支持用户级线程库,使用户级线程可以映射到LWP上,实现高效的线程管理和调度。

    优点

    • 独立性强:每个LWP有自己的执行上下文,不同LWP之间不会互相干扰,提高了系统的稳定性。
    • 灵活性高:LWP可以灵活地映射用户级线程,使得高层次的线程模型(如用户级线程和轻量级进程的结合)更加灵活高效。
    • 真实并行性:在多核处理器上,LWP可以实现真正的并行执行,充分利用多核硬件资源。

    缺点

    • 复杂性增加:LWP的管理和调度需要操作系统内核的支持,增加了系统的复杂性和资源开销。
    • 同步和互斥:与进程内线程类似,LWP之间的资源共享需要进行适当的同步和锁机制,避免竞态和数据同步问题。

    应用场景
    适用于需要高并发和高实时性要求的应用程序,如高性能服务器、数据库管理系统、网络服务等。

    3. 线程的创建与终止

    3.1 线程的创建

            线程的创建是多线程编程中至关重要的一步。现代操作系统提供了多种方式来创建线程,以下是几种常见的方法:

    1. 系统调用

    描述

    • 用户程序可以通过操作系统提供的系统调用来创建新线程。系统调用是操作系统内核提供的一种接口,允许用户程序与内核进行交互。

    实现

    • POSIX(Portable Operating System Interface): 在POSIX兼容系统(如Linux、UNIX)中,创建线程的常用系统调用是pthread_createpthread_create函数创建一个新的线程,并调用指定的函数开始执行。
    • Windows: 在Windows操作系统中,CreateThread函数用于创建一个新的线程。该函数创建新线程并将其加入可调度队列。

    示例

    1. // POSIX线程的创建(Linux/UNIX)
    2. #include
    3. #include
    4. #include
    5. void* thread_func(void* arg) {
    6. printf("Hello from thread!\n");
    7. return NULL;
    8. }
    9. int main() {
    10. pthread_t thread;
    11. if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {
    12. perror("pthread_create failed");
    13. return EXIT_FAILURE;
    14. }
    15. pthread_join(thread, NULL); // 等待线程结束
    16. return 0;
    17. }
    1. // Windows线程的创建
    2. #include
    3. #include
    4. DWORD WINAPI thread_func(LPVOID arg) {
    5. printf("Hello from thread!\n");
    6. return 0;
    7. }
    8. int main() {
    9. HANDLE thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
    10. if (thread == NULL) {
    11. perror("CreateThread failed");
    12. return EXIT_FAILURE;
    13. }
    14. WaitForSingleObject(thread, INFINITE); // 等待线程结束
    15. CloseHandle(thread);
    16. return 0;
    17. }

    2. 线程库

    描述

    • 许多操作系统都提供了线程库,线程库封装了系统调用,提供了更加简洁和友好的接口,方便用户程序创建和管理线程。

    实现

    • POSIX线程库(Pthreads): 提供了一组标准化的线程操作函数,如pthread_createpthread_join等。
    • C++11标准线程库: 提供了std::thread类方便C++程序员进行多线程编程。

    示例

    1. // 使用C++11标准线程库
    2. #include
    3. #include
    4. void thread_func() {
    5. std::cout << "Hello from thread!" << std::endl;
    6. }
    7. int main() {
    8. std::thread t(thread_func);
    9. t.join(); // 等待线程结束
    10. return 0;
    11. }

    3. 进程克隆

    描述

    • 在某些操作系统中,一个进程可以通过克隆自己来创建新线程。新线程与原进程共享相同的地址空间和资源,实现方式类似于进程间的分叉(fork)。

    实现

    • Linux: 在Linux系统中,可以使用clone系统调用来创建新线程。clone允许更加灵活的共享资源控制,可以指定新线程继承特定的资源和环境。

    示例

    1. // 使用Linux中的clone系统调用
    2. #define _GNU_SOURCE
    3. #include
    4. #include
    5. #include
    6. #include
    7. int thread_func(void* arg) {
    8. printf("Hello from thread!\n");
    9. return 0;
    10. }
    11. int main() {
    12. const int stack_size = 1024 * 1024;
    13. void* stack = malloc(stack_size);
    14. if (stack == NULL) {
    15. perror("malloc failed");
    16. return EXIT_FAILURE;
    17. }
    18. int thread_pid = clone(thread_func, stack + stack_size, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, NULL);
    19. if (thread_pid == -1) {
    20. perror("clone failed");
    21. return EXIT_FAILURE;
    22. }
    23. sleep(1); // 等待线程执行
    24. free(stack);
    25. return 0;
    26. }

    3.2 线程的终止

    线程的终止可以有多种方式:

    1. 正常终止:线程函数完成后返回,线程正常结束。
    2. 异常终止:线程函数中发生未捕获的异常或调用非终止库函数导致异常退出。
    3. 外部终止:另一个线程调用线程库提供的终止函数,强制终止目标线程。

    实现

    正常终止

    • POSIX: 使用pthread_exit函数让线程正常终止,退出时可以返回一个值。
    • Windows: 使用ExitThread函数让线程正常终止。

    异常终止

    • 发生未捕获的异常或调用非终止信号处理函数。

    外部终止

    • POSIX: 使用pthread_cancel函数请求终止指定的线程。
    • Windows: 使用TerminateThread函数强制终止目标线程。

    示例

    1. // POSIX正常终止
    2. void* thread_func(void* arg) {
    3. printf("Thread is exiting normally.\n");
    4. pthread_exit(NULL);
    5. return NULL;
    6. }
    7. // Windows正常终止
    8. DWORD WINAPI thread_func(LPVOID arg) {
    9. printf("Thread is exiting normally.\n");
    10. ExitThread(0);
    11. return 0;
    12. }
    13. // POSIX外部终止
    14. int main() {
    15. pthread_t thread;
    16. pthread_create(&thread, NULL, thread_func, NULL);
    17. pthread_cancel(thread); // 请求终止线程
    18. pthread_join(thread, NULL);
    19. return 0;
    20. }
    21. // Windows外部终止
    22. int main() {
    23. HANDLE thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
    24. TerminateThread(thread, 0); // 强制终止线程
    25. CloseHandle(thread);
    26. return 0;
    27. }

    4. 进程的创建与终止

            进程是操作系统中一个独立的执行单元,通过为其分配专门的资源(如内存、CPU时间片等)实现其独立运行。进程的创建是操作系统中一个关键的操作,可以通过以下几种方式实现。

    4.1 进程的创建

            进程的创建主要有系统调用、进程克隆和进程派生三种方式。每种方式有其独特的实现机制和应用场景。

    1. 系统调用

    描述
    用户程序可以通过操作系统提供的系统调用来创建新进程。这些系统调用由操作系统内核提供,允许用户程序请求内核服务,以创建并管理进程。

    实现

    • Unix/Linux: 在Unix和Linux系统中,fork系统调用用于创建一个新进程。新进程是调用进程的副本,但具有独立的地址空间和资源。exec系统调用可用于在新创建的进程中运行不同的程序。
    • Windows: 在Windows操作系统中,CreateProcess系统调用用于创建新进程。该函数创建一个新的进程和一个新的线程,并初始化新进程的地址空间。

    示例

    1. // Unix/Linux系统调用示例
    2. #include
    3. #include
    4. #include
    5. int main() {
    6. pid_t pid = fork();
    7. if (pid == -1) {
    8. perror("fork failed");
    9. return EXIT_FAILURE;
    10. } else if (pid == 0) {
    11. // Child process
    12. printf("Hello from child process!\n");
    13. execlp("/bin/ls", "ls", NULL); // 执行ls命令
    14. perror("execlp failed");
    15. } else {
    16. // Parent process
    17. wait(NULL); // 等待子进程结束
    18. printf("Hello from parent process!\n");
    19. }
    20. return 0;
    21. }
    1. // Windows系统调用示例
    2. #include
    3. #include
    4. int main() {
    5. STARTUPINFO si = { sizeof(si) };
    6. PROCESS_INFORMATION pi;
    7. if (!CreateProcess(NULL, "C:\\Windows\\System32\\notepad.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
    8. printf("CreateProcess failed (%d).\n", GetLastError());
    9. return EXIT_FAILURE;
    10. }
    11. // Wait until child process exits
    12. WaitForSingleObject(pi.hProcess, INFINITE);
    13. // Close process and thread handles
    14. CloseHandle(pi.hProcess);
    15. CloseHandle(pi.hThread);
    16. return 0;
    17. }

    2. 进程克隆

    描述
            进程克隆是一种特殊的系统调用,通过克隆现有进程来创建新进程。新进程与原进程共享特定的资源,如内存、文件描述符等。

    实现

    • Linux: 在Linux系统中,clone系统调用用于创建新进程。clone系统调用允许调用进程决定新进程与父进程共享哪些资源(例如,内存空间、文件描述符等),提供了更细粒度的控制。

    示例

    1. // 使用Linux中的clone系统调用
    2. #define _GNU_SOURCE
    3. #include
    4. #include
    5. #include
    6. #include
    7. int child_func(void* arg) {
    8. printf("Hello from cloned process!\n");
    9. return 0;
    10. }
    11. int main() {
    12. const int stack_size = 1024 * 1024;
    13. void* stack = malloc(stack_size);
    14. if (stack == NULL) {
    15. perror("malloc failed");
    16. return EXIT_FAILURE;
    17. }
    18. int clone_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD;
    19. if (clone(child_func, stack + stack_size, clone_flags, NULL) == -1) {
    20. perror("clone failed");
    21. return EXIT_FAILURE;
    22. }
    23. sleep(1); // 等待子进程执行
    24. free(stack);
    25. return 0;
    26. }

    3. 进程派生

    描述
            进程派生是一种通过父进程派生出子进程来创建新进程的方式。一些高层次的编程语言或框架提供了派生进程的机制,使得进程创建和管理更加方便。

    实现

    • Python: Python的multiprocessing模块提供了创建子进程的简单接口。通过Process类,可以派生新的子进程并执行特定的函数。
    • Java: Java的ProcessBuilder类提供了启动新进程的接口,通过调用系统命令或执行程序文件来创建新进程。

    示例

    1. # 使用Python创建新进程
    2. from multiprocessing import Process
    3. import os
    4. def child_process():
    5. print(f"Hello from child process (PID: {os.getpid()})")
    6. if __name__ == "__main__":
    7. print(f"Hello from parent process (PID: {os.getpid()})")
    8. p = Process(target=child_process)
    9. p.start()
    10. p.join()
    1. // 使用Java创建新进程
    2. import java.io.IOException;
    3. public class ProcessCreation {
    4. public static void main(String[] args) {
    5. ProcessBuilder processBuilder = new ProcessBuilder("notepad.exe");
    6. try {
    7. Process process = processBuilder.start();
    8. process.waitFor(); // 等待子进程结束
    9. System.out.println("Notepad process ended.");
    10. } catch (IOException | InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. }

    4.2 进程的终止

    ·进程的终止是进程生命周期的一个重要阶段,当一个进程完成其任务或遇到错误时,它将结束其执行。进程终止的方式主要包括:

    1. 正常终止

    描述

    • 进程正常完成其运行后会自动终止。通常通过返回一个状态码来指示是否成功完成。

    实现

    • 返回状态码:在C/C++中,通常使用return语句从main函数返回一个整数状态码。
    • 系统调用:使用exit系统调用显式地终止进程。

    示例

    1. // 正常终止(POSIX/Windows)
    2. int main() {
    3. printf("Process is exiting normally.\n");
    4. return 0; // 返回状态码0表示成功
    5. // exit(0); // 显式使用系统调用来终止进程
    6. }

    2. 异常终止

    描述

    • 进程由于遇到错误或其他意外情况而终止。这种情况下,进程可能返回一个非零的状态码或者触发异常处理机制。

    实现

    • 异常处理:在进程内部捕获异常或错误,通过返回相应的状态码来终止进程。
    • 信号处理:在POSIX系统中,进程可以通过接收和处理特定的信号(如SIGSEGVSIGFPE等)来进行异常终止。

    示例

    1. 2. 异常终止
    2. 描述
    3. 进程由于遇到错误或其他意外情况而终止。这种情况下,进程可能返回一个非零的状态码或者触发异常处理机制。
    4. 实现
    5. 异常处理:在进程内部捕获异常或错误,通过返回相应的状态码来终止进程。
    6. 信号处理:在POSIX系统中,进程可以通过接收和处理特定的信号(如SIGSEGV、SIGFPE等)来进行异常终止。
    7. 示例

    1. // 异常终止(Windows)
    2. #include
    3. #include
    4. int main() {
    5. __try {
    6. // 强制触发异常
    7. int* p = NULL;
    8. *p = 0;
    9. }
    10. __except(EXCEPTION_EXECUTE_HANDLER) {
    11. printf("Exception caught, terminating process.\n");
    12. return GetExceptionCode();
    13. }
    14. return 0;
    15. }

    3. 强制终止

    描述

    • 用户或系统管理员可以通过命令或管理工具强制终止一个进程。强制终止操作系统中的进程通常用于停止不可控或挂起的进程。

    实现

    • POSIX标准(如Linux和Unix): 使用kill命令或者kill系统调用向目标进程发送终止信号。
    • Windows: 使用任务管理器终止进程,或者使用TerminateProcess函数强制终止进程。

    示例

    1. // 强制终止(POSIX)
    2. // 使用命令行工具 `kill`
    3. // 假设目标进程的PID为1234
    4. // $ kill -9 1234
    5. // 使用系统调用
    6. #include
    7. #include
    8. #include
    9. int main() {
    10. pid_t target_pid = 1234; // 假设目标进程的PID为1234
    11. kill(target_pid, SIGKILL); // 发送 SIGKILL 信号以强制终止
    12. return 0;
    13. }
    1. // 强制终止(Windows)
    2. #include
    3. #include
    4. int main() {
    5. DWORD target_pid = 1234; // 假设目标进程的PID为1234
    6. HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, target_pid);
    7. if (hProcess == NULL) {
    8. printf("OpenProcess failed (%d).\n", GetLastError());
    9. return EXIT_FAILURE;
    10. }
    11. if (!TerminateProcess(hProcess, 0)) {
    12. printf("TerminateProcess failed (%d).\n", GetLastError());
    13. CloseHandle(hProcess);
    14. return EXIT_FAILURE;
    15. }
    16. CloseHandle(hProcess);
    17. return 0;
    18. }

    5. 进程同步与互斥

            进程同步是指多个进程在访问共享资源时进行协调,以保证数据的正确性和一致性。进程互斥是指保证在同一时刻只有一个进程访问共享资源。进程同步与互斥通常通过以下几种机制实现:

    信号量(Semaphores)

    描述

    • 信号量是一种广泛用于进程同步的机制,通过维护一个计数器来控制进程对资源的访问。信号量可以是计数信号量或二值信号量(类似于互斥锁)。

    实现

    • POSIX标准: 一些系统调用和库函数如sem_initsem_waitsem_post等用于创建和操作信号量。
    • Windows: 使用CreateSemaphoreWaitForSingleObjectReleaseSemaphore等函数操作信号量。

    示例

    1. // POSIX信号量示例(Linux/UNIX)
    2. #include
    3. #include
    4. #include
    5. #include
    6. sem_t semaphore;
    7. void* thread_func(void* arg) {
    8. sem_wait(&semaphore); // 进入临界区
    9. printf("Thread %ld is in critical section\n", (long)arg);
    10. sleep(1); // 模拟临界区操作
    11. printf("Thread %ld is leaving critical section\n", (long)arg);
    12. sem_post(&semaphore); // 离开临界区
    13. return NULL;
    14. }
    15. int main() {
    16. const int num_threads = 3;
    17. pthread_t threads[num_threads];
    18. sem_init(&semaphore, 0, 1); // 初始化信号量,初值为1
    19. for (long i = 0; i < num_threads; i++) {
    20. pthread_create(&threads[i], NULL, thread_func, (void*)i);
    21. }
    22. for (int i = 0; i < num_threads; i++) {
    23. pthread_join(threads[i], NULL); // 等待所有线程完成
    24. }
    25. sem_destroy(&semaphore);
    26. return 0;
    27. }

    互斥锁(Mutexes)

    描述

    • 互斥锁是一种提供互斥访问的机制,保证在同一时刻只有一个进程或线程访问共享资源。

    实现

    • POSIX标准: 使用pthread_mutex_initpthread_mutex_lockpthread_mutex_unlock等函数操作互斥锁。
    • Windows: 使用CreateMutexWaitForSingleObjectReleaseMutex等函数操作互斥锁。

    示例

     

    1. // POSIX互斥锁示例(Linux/UNIX)
    2. #include
    3. #include
    4. #include
    5. pthread_mutex_t mutex;
    6. void* thread_func(void* arg) {
    7. pthread_mutex_lock(&mutex); // 进入临界区
    8. printf("Thread %ld is in critical section\n", (long)arg);
    9. sleep(1); // 模拟临界区操作
    10. printf("Thread %ld is leaving critical section\n", (long)arg);
    11. pthread_mutex_unlock(&mutex); // 离开临界区
    12. return NULL;
    13. }
    14. int main() {
    15. const int num_threads = 3;
    16. pthread_t threads[num_threads];
    17. pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
    18. for (long i = 0; i < num_threads; i++) {
    19. pthread_create(&threads[i], NULL, thread_func, (void*)i);
    20. }
    21. for (int i = 0; i < num_threads; i++) {
    22. pthread_join(threads[i], NULL); // 等待所有线程完成
    23. }
    24. pthread_mutex_destroy(&mutex);
    25. return 0;
    26. }

    条件变量(Condition Variables)

    描述

    • 条件变量是一种用于线程或进程间同步的机制,用于阻塞一个线程,直到某个特定条件为真。条件变量通常与互斥锁配合使用。

    实现

    • POSIX标准: 使用pthread_cond_initpthread_cond_waitpthread_cond_signal等函数操作条件变量。
    • Windows: 使用ConditionVariableSleepConditionVariableCSWakeConditionVariable等函数操作条件变量。

    示例

     

    1. // POSIX条件变量示例(Linux/UNIX)
    2. #include
    3. #include
    4. #include
    5. pthread_mutex_t mutex;
    6. pthread_cond_t cond;
    7. int ready = 0;
    8. void* producer(void* arg) {
    9. pthread_mutex_lock(&mutex);
    10. ready = 1;
    11. printf("Producer: Data is ready\n");
    12. pthread_cond_signal(&cond); // 发送信号
    13. pthread_mutex_unlock(&mutex);
    14. return NULL;
    15. }
    16. void* consumer(void* arg) {
    17. pthread_mutex_lock(&mutex);
    18. while (!ready) {
    19. pthread_cond_wait(&cond, &mutex); // 等待信号
    20. }
    21. printf("Consumer: Consuming data\n");
    22. pthread_mutex_unlock(&mutex);
    23. return NULL;
    24. }
    25. int main() {
    26. pthread_t prod_thread, cons_thread;
    27. pthread_mutex_init(&mutex, NULL);
    28. pthread_cond_init(&cond, NULL);
    29. pthread_create(&prod_thread, NULL, producer, NULL);
    30. pthread_create(&cons_thread, NULL, consumer, NULL);
    31. pthread_join(prod_thread, NULL);
    32. pthread_join(cons_thread, NULL);
    33. pthread_mutex_destroy(&mutex);
    34. pthread_cond_destroy(&cond);
    35. return 0;
    36. }

    6. 进程通信

            进程通信是指在不同进程之间传递信息或数据。常见的进程通信方式包括管道、消息队列、共享内存和套接字。

    1. 管道(Pipes)

    描述

    • 管道是一种单向通信方式,数据只能从一个进程流向另一个进程。管道可以分为匿名管道和命名管道。

    实现

    • POSIX标准: 使用pipe函数创建匿名管道,使用mkfifo函数创建命名管道。
    • Windows: 使用CreatePipe函数创建匿名管道,使用CreateNamedPipe函数创建命名管道。

    示例

    1. // POSIX匿名管道示例(Linux/UNIX)
    2. #include
    3. #include
    4. #include
    5. int main() {
    6. int fd[2];
    7. if (pipe(fd) == -1) {
    8. perror("pipe");
    9. exit(EXIT_FAILURE);
    10. }
    11. pid_t pid = fork();
    12. if (pid == -1) {
    13. perror("fork");
    14. exit(EXIT_FAILURE);
    15. } else if (pid == 0) {
    16. // 子进程 - 读取消息
    17. close(fd[1]); // 关闭写端
    18. char buffer[128];
    19. read(fd[0], buffer, sizeof(buffer));
    20. printf("Child received: %s\n", buffer);
    21. close(fd[0]);
    22. } else {
    23. // 父进程 - 发送消息
    24. close(fd[0]); // 关闭读端
    25. const char *message = "Hello from parent process";
    26. write(fd[1], message, strlen(message) + 1);
    27. close(fd[1]);
    28. wait(NULL);
    29. }
    30. return 0;
    31. }

    2. 消息队列(Message Queues)

    描述

    • 消息队列是一种多向通信方式,多个进程可以向消息队列发送消息,也可以从消息队列接收消息。

    实现

    • POSIX标准: 使用msggetmsgsndmsgrcv等系统调用操作消息队列。
    • Windows: 使用Windows消息队列API。

    示例

     

    1. // POSIX消息队列示例(Linux/UNIX)
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define MSGSIZE 128
    7. struct msg_buffer {
    8. long msg_type;
    9. char msg_text[MSGSIZE];
    10. };
    11. int main() {
    12. key_t key = ftok("progfile", 65);
    13. int msgid = msgget(key, 0666 | IPC_CREAT);
    14. struct msg_buffer msg;
    15. if (fork() == 0) {
    16. // 子进程 - 发送消息
    17. msg.msg_type = 1;
    18. snprintf(msg.msg_text, MSGSIZE, "Hello from child process");
    19. msgsnd(msgid, &msg, sizeof(msg), 0);
    20. printf("Child sent: %s\n", msg.msg_text);
    21. } else {
    22. // 父进程 - 接收消息
    23. msgrcv(msgid, &msg, sizeof(msg), 1, 0);
    24. printf("Parent received: %s\n", msg.msg_text);
    25. msgctl(msgid, IPC_RMID, NULL);

    总结

            进程是操作系统中运行的基本单元,它由程序代码、数据和进程控制块 (PCB) 组成。进程的实现包括线程的实现、进程的创建与终止、进程同步与互斥以及进程通信等。

  • 相关阅读:
    目标检测算法——YOLOv5/YOLOv7改进|结合涨点Trick之ASFF_Detect(自适应空间特征融合)
    Ci2454国产2.4GHz无线收发遥控SoC芯片
    牛客网剑指offer刷题练习之链表中环的入口结点
    MVCC:多版本并发控制案例分析(二)
    OpenAI 更新 ChatGPT:支持图片和语音输入【附点评】
    Python爬虫案例入门教程(纯小白向)——夜读书屋小说
    B. Good Kid
    基于SSM农产品商城系统
    vue3.0父子组件方法调用(setup语法糖)
    基于 BERT+BILSTM 实现情感分析分类(附源码)
  • 原文地址:https://blog.csdn.net/JAZJD/article/details/139456511