Linux软件编程(七)线程间同步与进程间通信
一、线程间同步机制
定义:让多个线程在执行某个任务时,具有先后顺序的执行。
异步定义:多线程、多进程本质是异步执行,CPU 调度是随机的。
1. 信号量(Semaphore)
用于实现线程间同步。
操作步骤:
定义信号量对象
初始化信号量
PV 操作:P(申请信号量);V(释放信号量)
销毁信号量
相关函数接口:
#include <semaphore.h>
sem_t sem; // 定义对象
int sem_init(sem_t *sem, int pshared, unsigned int value); // 初始化信号量
int sem_wait(sem_t *sem); // P操作:申请信号量
int sem_post(sem_t *sem); // V操作:释放信号量
int sem_destroy(sem_t *sem); // 销毁信号量
参数说明:
pshared = 0
:线程间共享pshared != 0
:进程间共享value
:信号量初始值
2. 死锁及解决方法
死锁:在多线程环境中,每个线程持有资源并请求其他线程持有的资源,导致无限等待。
产生死锁的四个必要条件:
互斥条件:资源每次只能被一个线程使用。
请求与保持条件:线程请求新资源时不释放已持有的资源。
不剥夺条件:资源无法被强行剥夺。
循环等待条件:多个线程形成环状等待资源。
解决方法:
锁一定要成对出现
线程加锁顺序一致
破坏环路等待条件
使用非阻塞锁,如果锁被占用,释放已持有锁再尝试
二、进程间通信(IPC)
原因:进程间空间独立,无法直接通信,需要 IPC 机制实现通信。
1. 同一主机进程间通信
古老方式:
无名管道(父子进程)
有名管道(任意进程)
信号(通知机制)
IPC 对象通信(System V):
共享内存(效率最高)
消息队列
信号量集
2. 不同主机进程间通信
Socket 通信(网络通信)
3. 管道通信
无名管道
#include <unistd.h>
int pipe(int pipefd[2]);
0 为读端,1 为写端
默认大小 64KB
特性:
写阻塞:管道满时阻塞
读阻塞:管道空时阻塞
读返回值为 0:写端关闭且无数据
管道破裂:读端关闭,写端写入出错
有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
创建管道文件,读写端可任意进程使用
其他操作:
open()
、read()
、write()
、close()
、remove()
三、代码示例
1. 多线程模拟大棚数据采集
tasks.h
#ifndef __TASK_H__
#define __TASK_H__#include<pthread.h>typedef void *(*PFUN_t)(void *);typedef struct tasks
{pthread_t tid; PFUN_t ptask;
} Task_t;extern int create_thread_tasks(Task_t *tasks, int len);
extern void destroy_thread_task(Task_t *tasks, int len);#endif
task.c
#include <stdio.h>
#include "tasks.h"int create_thread_tasks(Task_t *tasks, int len) {for (int i = 0; i < len; i++) {int ret = pthread_create(&(tasks[i].tid), NULL, tasks[i].ptask, NULL);if (ret != 0) {printf("pthread_create error!");return -1;}}return 0;
}void destroy_thread_task(Task_t *tasks, int len) {for (int i = 0; i < len; i++)pthread_join(tasks[i].tid, NULL);
}
main.c(线程同步示例)
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>sem_t sem_w, sem_e, sem_r;void *task1(void *arg) {while (1) {sem_wait(&sem_w);printf("A\n");sem_post(&sem_e);sleep(1);}
}void *task2(void *arg) {while (1) {sem_wait(&sem_e);printf("B\n");sem_post(&sem_r);sleep(1);}
}void *task3(void *arg) {while (1) {sem_wait(&sem_r);printf("C\n");sem_post(&sem_w);sleep(1);}
}int main() {pthread_t tid[3];sem_init(&sem_w, 0, 1);sem_init(&sem_e, 0, 0);sem_init(&sem_r, 0, 0);pthread_create(&tid[0], NULL, task1, NULL);pthread_create(&tid[1], NULL, task2, NULL);pthread_create(&tid[2], NULL, task3, NULL);for (int i = 0; i < 3; i++)pthread_join(tid[i], NULL);sem_destroy(&sem_w);sem_destroy(&sem_e);sem_destroy(&sem_r);return 0;
}
2. 无名管道通信示例
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>int main() {int pipefd[2];pipe(pipefd);pid_t pid = fork();if(pid > 0) { // 父进程close(pipefd[0]);char buff[1024] = {0};fgets(buff, sizeof(buff), stdin);write(pipefd[1], buff, strlen(buff));close(pipefd[1]);wait(NULL);} else if(pid == 0) { // 子进程close(pipefd[1]);char buff[1024] = {0};read(pipefd[0], buff, sizeof(buff));printf("%s\n", buff);close(pipefd[0]);}return 0;
}
3. 有名管道通信示例
写端
int fd = open("./myfifo", O_WRONLY);
char buff[1024] = {0};
fgets(buff, sizeof(buff), stdin);
write(fd, buff, strlen(buff));
close(fd);
读端
int fd = open("./myfifo", O_RDONLY);
char buff[1024] = {0};
read(fd, buff, sizeof(buff));
printf("%s\n", buff);
close(fd);
四、Tips
代码设计原则:
高内聚:增强复用性
低耦合:模块间关联度低
开闭原则:对扩展开放,对修改关闭
互斥锁初始化:可以使用系统宏
PTHREAD_MUTEX_INITIALIZER
内存初始化:
void *memset(void *s, int c, size_t n);
功能:将内存区域填充指定数据
通信方式分类:
单工:广播
半双工:对讲机
全双工:打电话