进程线程与 IPC 工控实践多线程采集、共享内存、管道数据交互一个进程干活太慢多线程协作像流水线数据怎么在进程间流转IPC就是工厂里的传送带。一、进程与线程从独门独户到合租室友进程是操作系统分配资源的基本单位每个进程有自己的内存空间、文件描述符——就像独门独户的住户互不打扰。线程是进程内的执行单元多个线程共享同一个进程的地址空间——就像合租室友共享客厅厨房但各有各的卧室栈空间。对比进程线程地址空间独立隔离共享同进程内创建开销大fork复制小轻量创建通信方式需IPC机制直接读写共享变量崩溃影响不影响其他进程可能拖垮整个进程调度OS内核调度OS内核调度(pthread)工控场景怎么选多线程采集多个传感器、并行处理数据——共享地址空间方便数据交换多进程核心采集和Web服务分离——隔离性强一个崩了另一个不受影响实际项目往往是混合架构主进程多线程采集子进程做网络/显示服务二、多线程采集架构设计一个典型的工控多线程采集架构┌─────────────────────────────────────────────┐ │ 主线程 │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │串口1│ │串口2│ │ CAN │ │ ADC │ ← 采集线程池 │ │ └─────┘ └─────┘ └─────┘ └─────┘ │ │ ↓ ↓ ↓ ↓ │ │ ┌──────────────────────────────┐ │ │ │ 数据缓冲区环形队列 │ ← mutex保护│ │ └──────────────────────────────┘ │ │ ↓ │ │ ┌──────┐ ┌──────┐ │ │ │处理线程│ │上报线程│ ← 工作线程 │ │ └──────┘ └──────┘ │ └─────────────────────────────────────────────┘核心思路采集线程只管收数据往缓冲区塞处理线程从缓冲区取数据加工上报线程把结果发出去。各线程通过互斥锁和条件变量协调。三、pthread编程基础3.1 创建与等待线程#includepthread.hvoid*thread_func(void*arg){intid*(int*)arg;printf(线程%d 开始工作\n,id);/* ... 干活 ... */returnNULL;}intmain(void){pthread_ttid;intarg1;pthread_create(tid,NULL,thread_func,arg);pthread_join(tid,NULL);/* 等待线程结束 */return0;}pthread_create创建线程传入函数指针和参数。pthread_join阻塞等待线程结束类似进程的wait。3.2 互斥锁Mutex多个线程同时写同一个缓冲区不加锁就是灾难——数据会被搅成一锅粥。pthread_mutex_tmtxPTHREAD_MUTEX_INITIALIZER;/* 加锁访问共享资源 */pthread_mutex_lock(mtx);/* ... 操作共享缓冲区 ... */pthread_mutex_unlock(mtx);锁的原则持锁时间尽量短只保护必要的数据操作别在锁里做sleep或IO。3.3 条件变量Condition Variable互斥锁只能防止并发冲突但线程间怎么通知对方条件变量就是信号灯——一个线程等信号另一个线程发信号。pthread_mutex_tmtxPTHREAD_MUTEX_INITIALIZER;pthread_cond_tcondPTHREAD_COND_INITIALIZER;/* 消费者等数据 */pthread_mutex_lock(mtx);while(buf_count0){pthread_cond_wait(cond,mtx);/* 等待自动解锁被唤醒后重新加锁 */}/* 取数据 ... */pthread_mutex_unlock(mtx);/* 生产者放数据后通知 */pthread_mutex_lock(mtx);/* 放数据到缓冲区 ... */buf_count;pthread_cond_signal(cond);/* 通知一个等待者 */pthread_mutex_unlock(mtx);为什么pthread_cond_wait要用while而非if因为可能被虚假唤醒——没有真正信号也被唤醒了所以要再检查一遍条件。四、IPC机制总览进程间通信Inter-Process Communication是多进程协作的桥梁IPC方式特点工控适用场景管道(pipe/fifo)半双工、字节流简单命令传递命名管道(mkfifo)有文件名、无关进程可用进程间命令/状态传递共享内存(shm)最快、零拷贝高速数据交换视频帧等消息队列(msg)有格式、按类型读取结构化命令分发信号(signal)异步通知、信息量少事件通知紧急停止等Socket网络或本地、最通用跨机器或本地进程通信工控最常用的三件套共享内存高速数据、命名管道命令传递、信号紧急通知。五、共享内存实战共享内存是最快的IPC方式——两个进程直接读写同一块内存不需要内核中转真正零拷贝。#includesys/shm.h#includestring.h#defineSHM_KEY0x1234#defineSHM_SIZE4096/* 进程A写入共享内存 */intwriter(void){intshmidshmget(SHM_KEY,SHM_SIZE,IPC_CREAT|0666);if(shmid0){perror(shmget);return-1;}void*addrshmat(shmid,NULL,0);/* 写入传感器数据 */sensor_data_t*data(sensor_data_t*)addr;data-temperature25.3f;data-timestamptime(NULL);shmdt(addr);return0;}/* 进程B读取共享内存 */intreader(void){intshmidshmget(SHM_KEY,SHM_SIZE,0666);if(shmid0){perror(shmget);return-1;}void*addrshmat(shmid,NULL,SHM_RDONLY);/* 只读映射 */sensor_data_t*data(sensor_data_t*)addr;printf(温度: %.1f, 时间: %ld\n,data-temperature,data-timestamp);shmdt(addr);return0;}共享内存本身没有同步机制必须配合互斥锁或信号量/* 共享内存中嵌入锁 */typedefstruct{pthread_mutex_tmtx;/* 进程间互斥锁 */intdata_ready;floattemperature;floathumidity;time_ttimestamp;}shm_buffer_t;/* 注意进程间mutex需要设置为PROCESS_SHARED属性 */pthread_mutexattr_tattr;pthread_mutexattr_init(attr);pthread_mutexattr_setpshared(attr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(shm_buf-mtx,attr);六、管道实战命名管道命令传递命名管道FIFO在文件系统中有路径名任何进程都能打开它读写。适合进程间的命令传递——一个进程发命令另一个接收执行。#includesys/stat.h#includefcntl.h#defineFIFO_PATH/tmp/ic_cmd_fifo/* 创建命名管道 */mkfifo(FIFO_PATH,0666);/* 命令发送进程 */intsend_cmd(constchar*cmd){intfdopen(FIFO_PATH,O_WRONLY);write(fd,cmd,strlen(cmd)1);close(fd);return0;}/* 命令接收进程 */intrecv_cmd(void){intfdopen(FIFO_PATH,O_RDONLY);charbuf[128];read(fd,buf,sizeof(buf));printf(收到命令: %s\n,buf);close(fd);return0;}管道的特性默认阻塞——写端打开时读端阻塞等数据读端打开时写端阻塞等读者。这天然实现了等命令再执行。七、信号实战自定义信号通知信号是进程间最轻量的通知方式适合紧急事件通知——比如紧急停止或数据更新了。#includesignal.h/* 注册信号处理函数 */voidsigusr1_handler(intsig){printf(收到SIGUSR1: 数据已更新!\n);/* 触发重新读取共享内存 */}voidsigusr2_handler(intsig){printf(收到SIGUSR2: 紧急停止!\n);/* 立即停止采集关闭设备 */}intmain(void){signal(SIGUSR1,sigusr1_handler);signal(SIGUSR2,sigusr2_handler);while(1){pause();/* 等待信号 */}}/* 另一个进程发送信号 *//* kill -SIGUSR1 pid 通知数据更新 *//* kill -SIGUSR2 pid 紧急停止 */编程发送信号kill(target_pid,SIGUSR1);/* 通知目标进程 */信号局限携带信息量极少只有信号编号不能传数据。适合做触发器数据本身走共享内存或管道。八、完整代码示例多线程串口采集共享内存转发这是一个完整的工控采集架构多线程从多个串口采集数据通过共享内存转发给另一个进程处理。#includepthread.h#includesys/shm.h#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includetime.h#defineSHM_KEY0x5678#defineMAX_PORTS3#defineBUF_SIZE64typedefstruct{pthread_mutex_tmtx;pthread_cond_tcond;intcount;struct{intport_id;floatvalue;time_tts;}items[BUF_SIZE];}shm_ring_t;shm_ring_t*shm_buf;/* 采集线程 */void*collect_thread(void*arg){intport_id*(int*)arg;while(1){floatvalread_serial_port(port_id);/* 串口读取 */time_tnowtime(NULL);pthread_mutex_lock(shm_buf-mtx);if(shm_buf-countBUF_SIZE){shm_buf-items[shm_buf-count].port_idport_id;shm_buf-items[shm_buf-count].valueval;shm_buf-items[shm_buf-count].tsnow;shm_buf-count;pthread_cond_signal(shm_buf-cond);}pthread_mutex_unlock(shm_buf-mtx);usleep(100000);/* 100ms采集间隔 */}returnNULL;}/* 数据处理线程 */void*process_thread(void*arg){while(1){pthread_mutex_lock(shm_buf-mtx);while(shm_buf-count0){pthread_cond_wait(shm_buf-cond,shm_buf-mtx);}/* 处理所有数据 */for(inti0;ishm_buf-count;i){printf(端口%d: %.2f %ld\n,shm_buf-items[i].port_id,shm_buf-items[i].value,shm_buf-items[i].ts);}shm_buf-count0;pthread_mutex_unlock(shm_buf-mtx);}returnNULL;}intmain(void){/* 创建共享内存 */intshmidshmget(SHM_KEY,sizeof(shm_ring_t),IPC_CREAT|0666);shm_bufshmat(shmid,NULL,0);memset(shm_buf,0,sizeof(shm_ring_t));/* 初始化进程间互斥锁 */pthread_mutexattr_tmattr;pthread_mutexattr_init(mattr);pthread_mutexattr_setpshared(mattr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(shm_buf-mtx,mattr);pthread_condattr_tcattr;pthread_condattr_init(cattr);pthread_condattr_setpshared(cattr,PTHREAD_PROCESS_SHARED);pthread_cond_init(shm_buf-cond,cattr);/* 启动采集线程 */pthread_ttids[MAX_PORTS];intport_ids[MAX_PORTS]{0,1,2};for(inti0;iMAX_PORTS;i){pthread_create(tids[i],NULL,collect_thread,port_ids[i]);}/* 启动处理线程 */pthread_tproc_tid;pthread_create(proc_tid,NULL,process_thread,NULL);/* 主线程等待 */for(inti0;iMAX_PORTS;i){pthread_join(tids[i],NULL);}pthread_join(proc_tid,NULL);shmdt(shm_buf);shmctl(shmid,IPC_RMID,NULL);return0;}九、工控IPC方案选择对比表场景推荐IPC原因多线程内部数据交换全局变量mutex同地址空间最简单最快高速数据流视频帧/ADC波形共享内存零拷贝最高带宽命令/指令传递命名管道有序可靠自带阻塞同步紧急事件通知信号(SIGUSR)最快响应异步触发结构化消息分发消息队列按类型读取灵活筛选跨网络通信Socket通用性强可跨机器选择原则内部用线程共享外部用IPC隔离。数据量大用共享内存命令传递用管道紧急事件用信号——三者搭配就是工控IPC的最佳组合。