首页 > 系统相关 >线程与进程的实现

线程与进程的实现

时间:2024-06-04 23:59:37浏览次数:26  
标签:include thread 实现 线程 pthread 进程 NULL

目录

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函数用于创建一个新的线程。该函数创建新线程并将其加入可调度队列。

示例

// POSIX线程的创建(Linux/UNIX)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* thread_func(void* arg) {
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    pthread_t thread;
    if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {
        perror("pthread_create failed");
        return EXIT_FAILURE;
    }
    pthread_join(thread, NULL);  // 等待线程结束
    return 0;
}
// Windows线程的创建
#include <windows.h>
#include <stdio.h>

DWORD WINAPI thread_func(LPVOID arg) {
    printf("Hello from thread!\n");
    return 0;
}

int main() {
    HANDLE thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
    if (thread == NULL) {
        perror("CreateThread failed");
        return EXIT_FAILURE;
    }
    WaitForSingleObject(thread, INFINITE);  // 等待线程结束
    CloseHandle(thread);
    return 0;
}

2. 线程库

描述

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

实现

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

示例

// 使用C++11标准线程库
#include <thread>
#include <iostream>

void thread_func() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(thread_func);
    t.join();  // 等待线程结束
    return 0;
}

3. 进程克隆

描述

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

实现

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

示例

// 使用Linux中的clone系统调用
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int thread_func(void* arg) {
    printf("Hello from thread!\n");
    return 0;
}

int main() {
    const int stack_size = 1024 * 1024;
    void* stack = malloc(stack_size);
    if (stack == NULL) {
        perror("malloc failed");
        return EXIT_FAILURE;
    }

    int thread_pid = clone(thread_func, stack + stack_size, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, NULL);
    if (thread_pid == -1) {
        perror("clone failed");
        return EXIT_FAILURE;
    }

    sleep(1);  // 等待线程执行
    free(stack);
    return 0;
}

3.2 线程的终止

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

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

实现

正常终止

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

异常终止

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

外部终止

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

示例

// POSIX正常终止
void* thread_func(void* arg) {
    printf("Thread is exiting normally.\n");
    pthread_exit(NULL);
    return NULL;
}

// Windows正常终止
DWORD WINAPI thread_func(LPVOID arg) {
    printf("Thread is exiting normally.\n");
    ExitThread(0);
    return 0;
}

// POSIX外部终止
int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, thread_func, NULL);
    pthread_cancel(thread);  // 请求终止线程
    pthread_join(thread, NULL);
    return 0;
}

// Windows外部终止
int main() {
    HANDLE thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
    TerminateThread(thread, 0);  // 强制终止线程
    CloseHandle(thread);
    return 0;
}

4. 进程的创建与终止

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

4.1 进程的创建

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

1. 系统调用

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

实现

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

示例

// Unix/Linux系统调用示例
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        return EXIT_FAILURE;
    } else if (pid == 0) {
        // Child process
        printf("Hello from child process!\n");
        execlp("/bin/ls", "ls", NULL);  // 执行ls命令
        perror("execlp failed");
    } else {
        // Parent process
        wait(NULL);  // 等待子进程结束
        printf("Hello from parent process!\n");
    }
    return 0;
}
// Windows系统调用示例
#include <windows.h>
#include <stdio.h>

int main() {
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    if (!CreateProcess(NULL, "C:\\Windows\\System32\\notepad.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        printf("CreateProcess failed (%d).\n", GetLastError());
        return EXIT_FAILURE;
    }

    // Wait until child process exits
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Close process and thread handles
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}

2. 进程克隆

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

实现

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

示例

// 使用Linux中的clone系统调用
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int child_func(void* arg) {
    printf("Hello from cloned process!\n");
    return 0;
}

int main() {
    const int stack_size = 1024 * 1024;
    void* stack = malloc(stack_size);
    if (stack == NULL) {
        perror("malloc failed");
        return EXIT_FAILURE;
    }

    int clone_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD;
    if (clone(child_func, stack + stack_size, clone_flags, NULL) == -1) {
        perror("clone failed");
        return EXIT_FAILURE;
    }

    sleep(1);  // 等待子进程执行
    free(stack);
    return 0;
}

3. 进程派生

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

实现

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

示例

# 使用Python创建新进程
from multiprocessing import Process
import os

def child_process():
    print(f"Hello from child process (PID: {os.getpid()})")

if __name__ == "__main__":
    print(f"Hello from parent process (PID: {os.getpid()})")
    p = Process(target=child_process)
    p.start()
    p.join()
// 使用Java创建新进程
import java.io.IOException;

public class ProcessCreation {
    public static void main(String[] args) {
        ProcessBuilder processBuilder = new ProcessBuilder("notepad.exe");
        try {
            Process process = processBuilder.start();
            process.waitFor();  // 等待子进程结束
            System.out.println("Notepad process ended.");
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.2 进程的终止

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

1. 正常终止

描述

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

实现

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

示例

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

2. 异常终止

描述

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

实现

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

示例

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

// 异常终止(Windows)
#include <windows.h>
#include <stdio.h>

int main() {
    __try {
        // 强制触发异常
        int* p = NULL;
        *p = 0;
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        printf("Exception caught, terminating process.\n");
        return GetExceptionCode();
    }
    return 0;
}

3. 强制终止

描述

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

实现

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

示例

// 强制终止(POSIX)
// 使用命令行工具 `kill`
// 假设目标进程的PID为1234
// $ kill -9 1234

// 使用系统调用
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t target_pid = 1234;  // 假设目标进程的PID为1234
    kill(target_pid, SIGKILL);  // 发送 SIGKILL 信号以强制终止
    return 0;
}
// 强制终止(Windows)
#include <windows.h>
#include <stdio.h>

int main() {
    DWORD target_pid = 1234;  // 假设目标进程的PID为1234
    HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, target_pid);
    if (hProcess == NULL) {
        printf("OpenProcess failed (%d).\n", GetLastError());
        return EXIT_FAILURE;
    }

    if (!TerminateProcess(hProcess, 0)) {
        printf("TerminateProcess failed (%d).\n", GetLastError());
        CloseHandle(hProcess);
        return EXIT_FAILURE;
    }

    CloseHandle(hProcess);
    return 0;
}

5. 进程同步与互斥

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

信号量(Semaphores)

描述

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

实现

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

示例

// POSIX信号量示例(Linux/UNIX)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

sem_t semaphore;

void* thread_func(void* arg) {
    sem_wait(&semaphore);  // 进入临界区
    printf("Thread %ld is in critical section\n", (long)arg);
    sleep(1);  // 模拟临界区操作
    printf("Thread %ld is leaving critical section\n", (long)arg);
    sem_post(&semaphore);  // 离开临界区
    return NULL;
}

int main() {
    const int num_threads = 3;
    pthread_t threads[num_threads];

    sem_init(&semaphore, 0, 1);  // 初始化信号量,初值为1

    for (long i = 0; i < num_threads; i++) {
        pthread_create(&threads[i], NULL, thread_func, (void*)i);
    }

    for (int i = 0; i < num_threads; i++) {
        pthread_join(threads[i], NULL);  // 等待所有线程完成
    }

    sem_destroy(&semaphore);
    return 0;
}

互斥锁(Mutexes)

描述

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

实现

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

示例

 

// POSIX互斥锁示例(Linux/UNIX)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex;

void* thread_func(void* arg) {
    pthread_mutex_lock(&mutex);  // 进入临界区
    printf("Thread %ld is in critical section\n", (long)arg);
    sleep(1);  // 模拟临界区操作
    printf("Thread %ld is leaving critical section\n", (long)arg);
    pthread_mutex_unlock(&mutex);  // 离开临界区
    return NULL;
}

int main() {
    const int num_threads = 3;
    pthread_t threads[num_threads];

    pthread_mutex_init(&mutex, NULL);  // 初始化互斥锁

    for (long i = 0; i < num_threads; i++) {
        pthread_create(&threads[i], NULL, thread_func, (void*)i);
    }

    for (int i = 0; i < num_threads; i++) {
        pthread_join(threads[i], NULL);  // 等待所有线程完成
    }

    pthread_mutex_destroy(&mutex);
    return 0;
}

条件变量(Condition Variables)

描述

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

实现

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

示例

 

// POSIX条件变量示例(Linux/UNIX)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond;
int ready = 0;

void* producer(void* arg) {
    pthread_mutex_lock(&mutex);
    ready = 1;
    printf("Producer: Data is ready\n");
    pthread_cond_signal(&cond);  // 发送信号
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* consumer(void* arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {
        pthread_cond_wait(&cond, &mutex);  // 等待信号
    }
    printf("Consumer: Consuming data\n");
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t prod_thread, cons_thread;
    
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&prod_thread, NULL, producer, NULL);
    pthread_create(&cons_thread, NULL, consumer, NULL);

    pthread_join(prod_thread, NULL);
    pthread_join(cons_thread, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

6. 进程通信

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

1. 管道(Pipes)

描述

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

实现

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

示例

// POSIX匿名管道示例(Linux/UNIX)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int fd[2];
    if (pipe(fd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程 - 读取消息
        close(fd[1]);  // 关闭写端
        char buffer[128];
        read(fd[0], buffer, sizeof(buffer));
        printf("Child received: %s\n", buffer);
        close(fd[0]);
    } else {
        // 父进程 - 发送消息
        close(fd[0]);  // 关闭读端
        const char *message = "Hello from parent process";
        write(fd[1], message, strlen(message) + 1);
        close(fd[1]);
        wait(NULL);
    }

    return 0;
}

2. 消息队列(Message Queues)

描述

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

实现

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

示例

 

// POSIX消息队列示例(Linux/UNIX)
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define MSGSIZE 128

struct msg_buffer {
    long msg_type;
    char msg_text[MSGSIZE];
};

int main() {
    key_t key = ftok("progfile", 65);
    int msgid = msgget(key, 0666 | IPC_CREAT);
    struct msg_buffer msg;

    if (fork() == 0) {
        // 子进程 - 发送消息
        msg.msg_type = 1;
        snprintf(msg.msg_text, MSGSIZE, "Hello from child process");
        msgsnd(msgid, &msg, sizeof(msg), 0);
        printf("Child sent: %s\n", msg.msg_text);
    } else {
        // 父进程 - 接收消息
        msgrcv(msgid, &msg, sizeof(msg), 1, 0);
        printf("Parent received: %s\n", msg.msg_text);
        msgctl(msgid, IPC_RMID, NULL);

总结

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

标签:include,thread,实现,线程,pthread,进程,NULL
From: https://blog.csdn.net/JAZJD/article/details/139456511

相关文章

  • 利用进程池给客户端传文件
    主函数#include<func.h>#include"process_pool.h"#include"server.h"#include"transferFd.h"#defineEVENTSNUM100intmain(intargc,char*argv[]){//ipportprocessNumif(argc!=4){error(1,errno,"......
  • 分析webpack编译结果, 实现__webpack_require__函数
    分析webpack编译结果,实现__webpack_require__函数本篇文章我们通过手写来分析一下Webpack打包后的代码,并研究一下Webpack是如何将多个模块合并在一起的首先控制台输入npminit-y初始化一个项目,再输入npmiwebpackwebpack-cli-D安装Webpack在src目录想创建入......
  • 基于双向长短时记忆神经网络结合多头注意力机制BiLSTM-Mutilhead-Attention实现柴油机
    %加载数据集和标签load(‘diesel_dataset.mat’);%假设数据集存储在diesel_dataset.mat文件中data=diesel_dataset.data;labels=diesel_dataset.labels;%数据预处理%这里假设你已经完成了数据的预处理,包括特征提取、归一化等步骤%划分训练集和测试集[tra......
  • AI 绘画爆火背后:扩散模型原理及实现
    节前,我们星球组织了一场算法岗技术&面试讨论会,邀请了一些互联网大厂朋友、参加社招和校招面试的同学。针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。合集:持续火爆!!!《AIGC面试宝典》已圈粉无......
  • C#使用advapi32.dll来实现注册表的增、删、改、查
    合集-OpenTools(4) 1..NET使用P/Invoke来实现注册表的增、删、改、查功能05-142..NET实现获取NTP服务器时间并同步(附带Windows系统启用NTP服务功能)05-153.工业福利!用.NET快速开发物联网扫码器设备的通用扫码功能05-174.上位机开发福利!快速掌握.NET中的Modbus通信05-22......
  • Vue前端实现接收rabbitMQ及时消息 原
    https://blog.csdn.net/dawnStart/article/details/110479833打开APPVue前端实现接收rabbitMQ及时消息原创2020-12-0214:03:11阅读量1.4wAI必读dawnStart码龄4年关注Vue前端实现实时接收rabbitMQ及时消息,看了别人写的不太详细1.rabbitMQ安装Stom插件2.Vu......
  • IO进程线程(五)库的制作、进程
    文章目录一、库(一)静态库1.概念2.制作3.使用(编译)(1)生成可执行文件(2)运行(二)动态库(共享库)1.概念2.制作3.生成可执行文件4.基于动态库的可执行文件的执行过程二、进程(一)概念1.进程2.进程和程序的区别3.进程的组成4.进程的种类(1)交互进程(2)批处理进程(3)守护进程5.......
  • 麻醉医生的深度学习之旅 P4:Pytorch实现猴痘病识别
    ......
  • C语言数据结构实现-顺序表基本操作
    顺序表,全名顺序存储结构,是线性表的一种。通过《什么是线性表》一节的学习我们知道,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外。不仅如此,顺序表对数据的物理存储结构也有要求。顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时......
  • kettle从入门到精通 第六十五课 ETL之kettle 执行动态SQL语句,轻松实现全量&增量数据同
    本次课程的逻辑是同步t1表数据到t2表,t1和t2表的表机构相同,都有id,name,createtime三个字段。 CREATETABLE`t1`(`id`bigintNOTNULLAUTO_INCREMENT,`name`varchar(10)CHARACTERSETutf8mb4COLLATEutf8mb4_general_ciDEFAULTNULL,`createtime`datetime......