你有没有遇到过这样的情况:办公室里三台电脑同时往一台打印机发文档,结果第一页是A的合同、第二页跳成B的表格、第三页又冒出C的发票?纸张堆了一地,内容全乱套。这不是打印机坏了,很可能是软件层的线程同步没管好。
多任务≠乱打架
现代打印管理软件(比如CUPS或Windows Print Spooler)通常用多个线程处理不同用户的请求:一个线程监听网络端口收数据,一个线程解析PCL指令,一个线程和物理打印机通信。这些线程如果各自为政、不打招呼,就容易把不同用户的打印作业混在一起写进缓冲区——就像几个人同时往一张纸上写字,谁也不让谁。
锁住关键资源,才能稳住队列
最常见的同步手段是互斥锁(mutex)。比如,当线程A开始往打印队列插入新任务时,它会先“锁住”队列头指针;这时线程B想插任务,发现被锁了,就乖乖排队等待。等A把任务写完、释放锁,B才接着操作。这样每份作业都完整落进队列,顺序不会穿插。
代码里大概长这样:
pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER;
void add_job_to_spooler(PrintJob* job) {
pthread_mutex_lock(&queue_lock);
spooler_queue.push(job);
pthread_mutex_unlock(&queue_lock);
}信号量控制并发数,防卡死
有些老式打印机一次只能处理一个作业,但系统可能允许最多5个线程同时准备数据。这时光靠互斥锁不够——得限制“正在和打印机通信”的线程数量。信号量(semaphore)就派上用场:初始化为1,每次有线程要发指令就P操作(减1),用完再V操作(加1)。哪怕10个线程抢着发,也只有一人能进,其他人自动挂起等待。
条件变量让空闲线程不瞎忙
打印队列空了,但监听线程还在疯狂轮询“有没有新任务?有没有?”——这叫忙等待,浪费CPU。换成条件变量后,线程直接休眠,等新任务一来,系统立刻唤醒它。像快递站的分拣员,没包裹时靠墙打盹,扫描枪一响马上睁眼开工。
实际中,CUPS的job.c里就大量使用pthread_cond_wait配合互斥锁,确保spooler线程只在真正有活时才启动解析流程。
别小看这些机制,它们天天在保你的打印不翻车
下次看到“正在打印…”稳稳走完十几页,背后可能是十几个线程靠锁、信号量、条件变量默契配合的结果。它们不露脸,但少了任何一个,你桌上的A4纸就可能变成抽象派拼贴画。