4. 进程与线程管理:进程创建与终止、线程同步与调度
好,咱们今天聊聊QNX里最核心的东西——进程和线程。说实话,我刚开始接触QNX那会儿,也被它这套机制绕得有点晕。但后来发现,只要你理解了它的设计哲学,其实特别清晰。
4.1 进程创建:spawn vs fork
在QNX里创建进程,主要有两条路:spawn() 和 fork()。很多人问我,到底该用哪个?
先说说 fork()。它会把当前进程完整复制一份。子进程从fork返回的地方继续跑。嗯,这里有个坑——我早期做项目时,用fork创建了一个大进程,结果内存直接爆了。为什么?因为fork会复制整个地址空间,包括那些锁、文件描述符,全给你复制一遍。
再说 spawn()。这是QNX推荐的方式。它直接加载一个新程序,不复制父进程的内存。说白了,就是干净利落地启动一个新任务。
// spawn示例:启动一个子进程
#include <spawn.h>
#include <stdio.h>
int main() {
pid_t pid;
char *argv[] = {"/usr/bin/myapp", "-v", NULL};
// 我个人习惯用spawn,干净
int ret = spawn("/usr/bin/myapp", 0, NULL, NULL, argv, NULL);
if (ret == -1) {
perror("spawn failed");
return 1;
}
printf("子进程PID: %d\n", ret);
return 0;
}
4.2 线程创建与管理:pthread
线程这块,QNX用的是标准的POSIX线程——pthread。如果你在Linux上写过线程,那基本无缝切换。
创建线程很简单,但管理起来有讲究。我见过不少新手,线程一多就乱套了。
#include <pthread.h>
#include <stdio.h>
void* worker_thread(void* arg) {
int id = *(int*)arg;
printf("线程 %d 开始工作\n", id);
// 干活...
return NULL;
}
int main() {
pthread_t threads[4];
int ids[4];
for (int i = 0; i < 4; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, worker_thread, &ids[i]);
}
// 等待所有线程结束
for (int i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
这里有个细节:pthread_create 的第四个参数是void*,你可以传任何东西。但我建议你传一个结构体,而不是单个整数。为什么?因为传整数时,如果线程启动慢了,主线程可能已经把那个整数改了——我曾经被这个坑过。
4.3 线程同步:互斥锁、条件变量、信号量
多线程最头疼的就是同步。QNX提供了三种主要工具:互斥锁、条件变量、信号量。
互斥锁(Mutex)
互斥锁是最基础的。说白了,就是一把锁,谁拿到谁干活。
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* safe_worker(void* arg) {
pthread_mutex_lock(&lock);
// 临界区代码
printf("线程安全操作\n");
pthread_mutex_unlock(&lock);
return NULL;
}
条件变量
条件变量用来等待某个条件成立。比如生产者-消费者模式,消费者等数据来了再处理。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int data_ready = 0;
void* producer(void* arg) {
// 生产数据
pthread_mutex_lock(&mutex);
data_ready = 1;
pthread_cond_signal(&cond); // 通知消费者
pthread_mutex_unlock(&mutex);
return NULL;
}
void* consumer(void* arg) {
pthread_mutex_lock(&mutex);
while (!data_ready) {
pthread_cond_wait(&cond, &mutex); // 等待条件
}
// 处理数据
pthread_mutex_unlock(&mutex);
return NULL;
}
注意那个while循环——这是必须的。为什么?因为pthread_cond_wait可能被虚假唤醒。嗯,这是POSIX标准规定的,不是QNX的bug。
信号量
信号量适合控制资源数量。比如限制同时访问某个设备的线程数。
#include <semaphore.h>
sem_t sem;
void* limited_worker(void* arg) {
sem_wait(&sem); // 获取信号量
// 访问有限资源
printf("访问资源\n");
sem_post(&sem); // 释放信号量
return NULL;
}
int main() {
sem_init(&sem, 0, 3); // 最多3个线程同时访问
// 创建线程...
}
4.4 线程优先级与调度策略
QNX是实时系统,优先级和调度策略是它的灵魂。你想想看,如果所有线程优先级一样,那跟普通Linux有什么区别?
QNX支持三种调度策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| SCHED_FIFO | 先进先出,直到主动让出CPU | 实时任务,需要确定性的响应时间 |
| SCHED_RR | 时间片轮转,同优先级轮流执行 | 多个同等重要的实时任务 |
| SCHED_OTHER | 普通分时调度 | 非实时任务,后台处理 |
#include <pthread.h>
#include <sched.h>
void set_realtime_priority(pthread_t thread, int priority) {
struct sched_param param;
param.sched_priority = priority;
// 设置为FIFO实时调度
int ret = pthread_setschedparam(thread, SCHED_FIFO, ¶m);
if (ret != 0) {
printf("设置优先级失败: %d\n", ret);
}
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, worker, NULL);
// 设置优先级为80(范围0-255,越高越优先)
set_realtime_priority(thread, 80);
pthread_join(thread, NULL);
return 0;
}
还有个容易忽略的点:优先级反转。当高优先级线程等待低优先级线程持有的锁时,就会发生。QNX提供了优先级继承协议来解决这个问题——在创建互斥锁时设置PTHREAD_PRIO_INHERIT属性。
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mutex, &attr);
嗯,这个设置我建议在所有实时线程共享的互斥锁上都加上。虽然有一点点性能开销,但能避免很多诡异的死锁问题。
好了,进程线程这块就聊到这儿。记住:在QNX里,线程是调度的基本单位,进程是资源分配的基本单位。搞清楚了这一点,后面就好办了。