目录
1. 用户级线程(User-Level Threads,ULTs)
2. 内核级线程(Kernel-Level Threads,KLTs)
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_create
。pthread_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_create
、pthread_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 线程的终止
线程的终止可以有多种方式:
- 正常终止:线程函数完成后返回,线程正常结束。
- 异常终止:线程函数中发生未捕获的异常或调用非终止库函数导致异常退出。
- 外部终止:另一个线程调用线程库提供的终止函数,强制终止目标线程。
实现
正常终止
- 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系统中,进程可以通过接收和处理特定的信号(如
SIGSEGV
、SIGFPE
等)来进行异常终止。
示例
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_init
、sem_wait
、sem_post
等用于创建和操作信号量。 - Windows: 使用
CreateSemaphore
、WaitForSingleObject
、ReleaseSemaphore
等函数操作信号量。
示例
// 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_init
、pthread_mutex_lock
、pthread_mutex_unlock
等函数操作互斥锁。 - Windows: 使用
CreateMutex
、WaitForSingleObject
、ReleaseMutex
等函数操作互斥锁。
示例
// 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_init
、pthread_cond_wait
、pthread_cond_signal
等函数操作条件变量。 - Windows: 使用
ConditionVariable
、SleepConditionVariableCS
、WakeConditionVariable
等函数操作条件变量。
示例
// 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标准: 使用
msgget
、msgsnd
、msgrcv
等系统调用操作消息队列。 - 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