文章目录
- 前言
- 一、进程基础概念
-
- [1. 什么是进程](#1. 什么是进程)
- [2. 进程的特点](#2. 进程的特点)
- [3. 获取进程ID](#3. 获取进程ID)
- 二、线程基础概念
-
- [1. 什么是线程](#1. 什么是线程)
- [2. 线程的特点](#2. 线程的特点)
- [3. 线程创建](#3. 线程创建)
- [三、进程 vs 线程](#三、进程 vs 线程)
- 四、C语言中创建进程(fork)
-
- [1. fork函数介绍](#1. fork函数介绍)
- [2. 示例代码](#2. 示例代码)
- [3. 执行特点](#3. 执行特点)
- 五、进程地址空间验证
- 六、C语言中创建线程(pthread)
-
- [1. 常用函数](#1. 常用函数)
- [2. 示例代码](#2. 示例代码)
- 七、线程共享数据问题
- 八、互斥锁解决线程安全
- 九、进程通信(IPC)简介
- 总结
前言
在 C 语言开发中,并发编程是一个绕不开的重要话题,而"进程"和"线程"则是实现并发的两种核心方式。它们属于操作系统层面的概念,但在实际开发中,C 语言通常通过系统调用和线程库来对其进行操作。
一、进程基础概念
1. 什么是进程
进程(Process)是程序的一次执行过程,是操作系统进行资源分配的基本单位。
简单理解:
- 程序 = 静态代码
- 进程 = 运行中的程序
当你执行一个程序时,操作系统会为其分配内存、打开文件、建立运行环境,这个运行中的实例就是进程。
2. 进程的特点
- 拥有独立的地址空间(堆、栈、全局变量)
- 资源独立(文件描述符、内存等)
- 进程之间默认相互隔离
- 创建和切换开销较大
- 安全性高
3. 获取进程ID
c
#include <stdio.h>
#include <unistd.h>
int main() {
printf("PID = %d\n", getpid());
return 0;
}
二、线程基础概念
1. 什么是线程
线程(Thread)是进程中的一个执行单元,是 CPU 调度的基本单位。
一个进程可以包含多个线程,所有线程共享进程资源。
2. 线程的特点
- 共享进程地址空间
- 资源共享(堆、全局变量)
- 创建和切换开销小
- 通信方便
- 容易产生数据竞争
3. 线程创建
c
#include <stdio.h>
#include <pthread.h>
void* func(void* arg) {
printf("Thread running\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, func, NULL);
pthread_join(tid, NULL);
return 0;
}
编译:
bash
gcc test.c -o test -pthread
三、进程 vs 线程
| 对比项 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立 | 共享 |
| 地址空间 | 独立 | 共享 |
| 创建开销 | 大 | 小 |
| 切换开销 | 大 | 小 |
| 通信方式 | IPC | 共享变量 |
| 安全性 | 高 | 低 |
四、C语言中创建进程(fork)
1. fork函数介绍
fork() 用于创建子进程,是 Linux/Unix 系统中最常用的进程创建方式。
返回值:
- 子进程中返回 0
- 父进程中返回 子进程PID
- 出错返回 -1
2. 示例代码
c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child: PID=%d\n", getpid());
} else {
printf("Parent: PID=%d, Child PID=%d\n", getpid(), pid);
}
return 0;
}
3. 执行特点
- fork之后会执行两次(父子进程)
- 执行顺序不确定(由操作系统调度)
五、进程地址空间验证
c
#include <stdio.h>
#include <unistd.h>
int main() {
int x = 10;
pid_t pid = fork();
if (pid == 0) {
x = 20;
printf("Child x=%d\n", x);
} else {
x = 30;
printf("Parent x=%d\n", x);
}
}
说明:
进程之间变量互不影响 → 地址空间独立
六、C语言中创建线程(pthread)
1. 常用函数
pthread_create:创建线程pthread_join:等待线程结束pthread_self:获取线程ID
2. 示例代码
c
#include <stdio.h>
#include <pthread.h>
void* run(void* arg) {
printf("Thread ID: %lu\n", pthread_self());
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, run, NULL);
pthread_create(&t2, NULL, run, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
七、线程共享数据问题
c
#include <stdio.h>
#include <pthread.h>
int count = 0;
void* add(void* arg) {
for (int i = 0; i < 100000; i++) {
count++;
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, add, NULL);
pthread_create(&t2, NULL, add, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("count = %d\n", count);
}
问题:
结果不等于 200000 → 发生竞态条件
八、互斥锁解决线程安全
c
#include <stdio.h>
#include <pthread.h>
int count = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* add(void* arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&lock);
count++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, add, NULL);
pthread_create(&t2, NULL, add, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("count = %d\n", count);
return 0;
}
九、进程通信(IPC)简介
由于进程之间内存隔离,需要使用 IPC 进行通信。
常见方式:
- 管道(pipe)
- 命名管道(FIFO)
- 共享内存
- 消息队列
- 信号
- Socket
示例:管道通信
c
#include <stdio.h>
#include <unistd.h>
int main() {
int fd[2];
pipe(fd);
if (fork() == 0) {
close(fd[0]);
write(fd[1], "hello", 6);
} else {
char buf[20];
close(fd[1]);
read(fd[0], buf, sizeof(buf));
printf("recv: %s\n", buf);
}
}
总结
本文系统介绍了 C 语言中的进程和线程:
- 进程:独立、隔离、安全,但开销大
- 线程:轻量、高效,但需要同步控制
- fork:创建进程核心函数
- pthread:线程开发核心库
- IPC:进程通信基础
在实际开发中:
- 高隔离 → 多进程
- 高并发 → 多线程