信号,信号量,条件变量三者辨析
1.信号(Signal)
- 概念
- 信号是一种软中断机制,用于通知进程发生了某个特定的事件。它是一种异步事件通知方式,进程在收到信号时可以采取相应的动作,如终止进程、暂停进程或者忽略信号等。信号是由操作系统内核发送给进程的。
- 举例
- 当用户在终端中按下
Ctrl + C
组合键时,内核会向当前正在运行的前台进程发送一个SIGINT
(中断信号)。如果进程没有对这个信号进行特殊处理,默认情况下会终止运行。例如,下面是一个简单的 C 程序来处理SIGINT
信号:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <stdio.h> #include <signal.h> #include <stdlib.h>
void signal_handler(int signum) { printf("Received signal %d\n", signum); }
int main() { if (signal(SIGINT, signal_handler) == SIG_ERR) { perror("signal"); return 1; } while (1) { } return 0; }
|
在这个例子中,通过signal
函数注册了SIGINT
信号的处理函数signal_handler
。当接收到SIGINT
信号时,就会执行signal_handler
函数,而不是默认的终止进程。
2.信号量(Semaphore)
- 概念
- 信号量是一种用于进程同步和互斥的机制。它本质上是一个非负整数计数器,用于控制对共享资源的访问。信号量有两个基本操作:
P
操作(也称为wait
操作)和V
操作(也称为signal
操作)。P
操作会将信号量的值减 1,如果信号量的值小于 0,则进程会被阻塞;V
操作会将信号量的值加 1,如果信号量的值小于等于 0,则会唤醒一个被阻塞的进程。
- 举例
- 假设有一个共享缓冲区,多个生产者进程向缓冲区中写入数据,多个消费者进程从缓冲区中读取数据。为了避免同时访问缓冲区导致数据不一致,我们可以使用信号量来进行同步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h>
union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; };
void P(int sem_id) { struct sembuf sem_op; sem_op.sem_num = 0; sem_op.sem_op = -1; sem_op.sem_flg = 0; if (semop(sem_id, &sem_op, 1) == -1) { perror("P operation"); exit(1); } }
void V(int sem_id) { struct sembuf sem_op; sem_op.sem_num = 0; sem_op.sem_op = 1; sem_op.sem_flg = 0; if (semop(sem_id, &sem_op, 1) == -1) { perror("V operation"); exit(1); } }
int main() { int sem_id = semget(IPC_PRIVATE, 1, 0666); if (sem_id == -1) { perror("semget"); return 1; } union semun arg; arg.val = 1; if (semctl(sem_id, 0, SETVAL, arg) == -1) { perror("semctl"); return 1; } pid_t pid = fork(); if (pid == -1) { perror("fork"); return 1; } else if (pid == 0) { P(sem_id); printf("Producer: Writing to buffer\n"); V(sem_id); } else { P(sem_id); printf("Consumer: Reading from buffer\n"); V(sem_id); } if (semctl(sem_id, 0, IPC_RMID) == -1) { perror("semctl"); return 1; } return 0; }
|
在这个例子中,通过semget
函数创建了一个信号量,通过semctl
函数初始化信号量的值为 1。生产者进程和消费者进程在访问共享资源(模拟的缓冲区)之前,都需要执行P
操作获取信号量,如果信号量的值为 0(表示资源被占用),则会被阻塞。访问完资源后,执行V
操作释放信号量,使得其他进程可以获取信号量来访问资源。
3.条件变量(Condition Variable)
- 概念
- 条件变量是用于线程同步的一种机制,它允许线程等待某个条件成立。条件变量通常和互斥锁一起使用。线程在等待条件变量时会被阻塞,当其他线程改变了条件并发出信号(
signal
或broadcast
操作)时,被阻塞的线程会被唤醒,重新检查条件是否满足。
- 举例
- 假设有一个线程池,工作线程等待任务队列中有任务时才开始工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue>
std::mutex mutex_queue; std::condition_variable cond_var; std::queue<int> task_queue;
void worker_thread() { std::unique_lock<std::mutex> lock(mutex_queue); while (true) { cond_var.wait(lock, [] { return!task_queue.empty(); }); int task = task_queue.front(); task_queue.pop(); lock.unlock(); std::cout << "Thread is working on task: " << task << std::endl; lock.lock(); } }
int main() { const int num_threads = 3; std::thread threads[num_threads]; for (int i = 0; i < num_threads; ++i) { threads[i] = std::thread(worker_thread); } for (int i = 0; i < 10; ++i) { std::unique_lock<std::mutex> lock(mutex_queue); task_queue.push(i); lock.unlock(); cond_var.notify_one(); } for (int i = 0; i < num_threads; ++i) { threads[i].join(); } return 0; }
|
在这个例子中,工作线程通过cond_var.wait
函数等待任务队列中有任务。主线程在向任务队列添加任务后,通过cond_var.notify_one
函数唤醒一个等待的工作线程来执行任务。互斥锁mutex_queue
用于保护任务队列的并发访问,保证在检查任务队列是否为空和添加 / 取出任务时的原子性。
4.相同点
- 目的相似:信号、信号量和条件变量都是用于在多进程或多线程环境下进行协调和同步的机制,以避免竞争条件和确保程序的正确执行。
- 基于操作系统支持:它们都依赖于操作系统提供的底层机制来实现功能,例如信号是由内核发送和处理,信号量和条件变量的操作也是通过系统调用或操作系统提供的库函数来实现。
- 与并发编程相关:在并发编程场景中,无论是进程还是线程之间的交互,都可能会用到这些机制来处理共享资源的访问、事件通知等问题。
5.不同点
- 功能重点
- 信号主要用于异步事件通知,进程接收到信号后可以执行预定义的动作来响应事件。信号量重点在于对共享资源的访问控制,通过计数来限制同时访问资源的进程或线程数量。条件变量主要用于线程等待某个特定条件成立,它和互斥锁配合使用,更关注于条件的等待和唤醒机制。
- 应用场景
- 信号适用于处理外部事件(如用户输入、硬件中断等)对进程的影响。信号量常用于多个进程或线程对有限的共享资源(如缓冲区、设备等)的访问控制。条件变量主要用于线程之间基于条件的同步,比如等待某个数据结构满足一定条件(如队列非空等)。
- 操作方式
- 信号是由内核发送给进程,进程可以选择忽略、默认处理或自定义处理信号。信号量有
P
和V
操作来控制计数器的值,从而控制进程或线程的阻塞和唤醒。条件变量通过wait
、signal
和broadcast
操作来让线程等待条件和唤醒等待条件的线程。