C语言并发编程实战:多进程与多线程的精确选择与实践
发布时间: 2024-10-02 00:19:38 阅读量: 15 订阅数: 20
![C语言并发编程实战:多进程与多线程的精确选择与实践](https://img-blog.csdnimg.cn/f2b2b220a4e447aa99d4f42e2fed9bae.png)
# 1. C语言并发编程概述
## 1.1 并发编程简介
并发编程在当今的软件开发领域扮演着至关重要的角色,尤其在高性能计算、服务器端处理、实时系统以及多任务环境中。C语言作为一门经典且功能强大的编程语言,提供了丰富的并发编程支持,包括多进程、多线程等机制,这使得它成为系统编程和高性能应用开发的首选。
## 1.2 C语言并发的必要性
由于C语言的高效执行和接近硬件的操作能力,它在并发编程中尤其重要。并发可以提高程序的响应性,优化资源的利用,并减少对单个CPU核心的依赖,使得应用程序能够充分利用现代多核处理器的强大性能。
## 1.3 并发编程的基本原则
在C语言中进行并发编程,需要掌握一些基本原则,包括进程与线程的概念、同步与通信机制、死锁的避免和资源管理。理解这些概念对设计和实现高效、稳定的并发程序至关重要。
本章概述了C语言并发编程的基础知识,并为后续章节的深入讨论奠定了基础。在接下来的章节中,我们将详细探讨多进程和多线程编程的具体实践,以及如何在实际项目中选择和应用不同的并发模型。
# 2. C语言中的多进程编程
### 2.1 多进程的基本概念和理论
#### 2.1.1 进程的创建和结束
在Linux操作系统中,每个执行的程序都被称为一个进程,每个进程都分配一个唯一的进程标识符(PID)。创建进程的常用方式是通过fork()系统调用,该调用会创建一个新的子进程,它是当前进程的一个复制品。
```c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
pid = fork(); // 创建子进程
if (pid == -1) {
// fork失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程执行部分
printf("This is the child process, PID: %d, PPID: %d\n", getpid(), getppid());
} else {
// 父进程执行部分
printf("This is the parent process, PID: %d, Child PID: %d\n", getpid(), pid);
}
return 0;
}
```
在上述代码中,`fork()` 调用后,系统返回两次。一次是在父进程中,返回子进程的PID;另一次是在子进程中,返回0。如果fork失败,则在父进程中返回-1。子进程和父进程拥有相同的代码段,但数据段、堆和栈则是独立的,子进程获得父进程数据段、堆和栈的副本。
进程结束通常通过调用`_exit()`或`exit()`函数来实现,父进程通过`wait()`或`waitpid()`系统调用等待子进程结束并收集其状态信息。
#### 2.1.2 进程间通信(IPC)
进程间通信(IPC)允许独立运行的进程之间互相交换数据或信号。Linux提供了多种IPC机制,包括管道、消息队列、共享内存、信号量等。
- **管道(Pipe)**:是最早也是最简单的IPC方法,它允许一个进程和另一个进程进行单向通信。管道在文件系统中没有名字,因此称为匿名管道。
- **消息队列(Message Queue)**:允许一个或多个进程向它写入消息,一个或多个进程读取其中信息。
- **共享内存(Shared Memory)**:允许多个进程共享一个给定的存储区,这是最快的一种IPC方式。
- **信号量(Semaphore)**:用于进程同步,一个进程可以向信号量发送信号来增加其值,也可以等待接收信号从而减少其值。
### 2.2 Linux下的多进程编程实践
#### 2.2.1 fork()和exec()系列函数
`fork()`函数的作用是创建一个与调用进程几乎完全相同的子进程。`exec()`系列函数则用于在当前进程中替换为新的程序,而不会创建新的进程。
```c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
execlp("ls", "ls", NULL); // 替换当前进程为ls
} else {
// 父进程
wait(NULL); // 等待子进程结束
}
return 0;
}
```
#### 2.2.2 进程同步机制:信号量、互斥锁
进程同步机制用于控制多个进程访问共享资源时的顺序。信号量是一种广泛使用的同步机制,可以用于进程或线程间的同步。
```c
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
int main() {
sem_t sem;
sem_init(&sem, 0, 1); // 初始化信号量
sem_wait(&sem); // P操作
// 访问共享资源
sem_post(&sem); // V操作
sem_destroy(&sem); // 销毁信号量
return 0;
}
```
互斥锁(mutex)是另一种同步机制,用于防止多个线程或进程同时访问同一资源。
#### 2.2.3 管道和消息队列的使用
管道是Linux中用于进程间通信的一种方式,而消息队列是另一种更复杂的IPC机制。
```c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
int pipefd[2];
pid_t cpid;
char buf;
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]); // 关闭读端
_exit(EXIT_SUCCESS);
} else { // 父进程
close(pipefd[0]); // 关闭读端
char *msg = "Hello, child!\n";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]); // 关闭写端
}
return 0;
}
```
### 2.3 多进程应用案例分析
#### 2.3.1 服务器程序的并发模型
在设计服务器程序时,多进程是一种常见的并发模型。服务器监听端口上的连接请求,并为每个新连接创建一个子进程,子进程负责处理该连接的数据交互。
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(const char *msg) {
perror(msg);
exit(1);
}
int main(int argc, char *argv[]) {
int sockfd, newsockfd;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建socket
if (sockfd < 0)
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(8888);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
error("ERROR on binding");
listen(sockfd, 5); // 监听连接请求
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); // 接受连接请求
if (newsockfd < 0)
error("ERROR on accept");
while (1) {
bzero(buffer, 256);
n = read(newsockfd, buffer, 255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s\n", buffer);
n = write(newsockfd, "I got your message", 18);
if (n < 0) error("ERROR writing to socket");
}
close(newsockfd);
close(sockfd);
return 0;
}
```
在该案例中,服务器程序通过`socket()`创建监听套接字,然后调用`listen()`方法等待客户端连接。一旦客户端连接请求到达,服务器就接受连接,并创建一个子进程来服务该连接。
#### 2.3.2 多进程结构的网络爬虫设计
网络爬虫程序需要高效地遍历网站,并从每个页面提取信息。在高并发的情况下,多进程可以显著提高爬虫的效率。
```c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
void crawler() {
// 网络爬虫逻辑
printf("Crawler process done\n");
}
int main() {
pid_t pid;
while (1)
```
0
0