POSIX Thread中提供了非常强大的线程互斥机制, 如 pthread_mutex_XXXX / pthread_cond_XXXX 以及 semaphore(sem_wait/sem_post)等。在同一进程内的多线程编程非常方便灵活。但对于跨进程的线程互斥问题就变得麻烦了。
其实主流的Linux在内核中也实现了System V的IPC, 在跨进程的线程互斥方面实现起来更加简单。当然,同一进程容器内的多线程也可以使用。
semget的man page: http://linux.die.net/man/2/semget
如果是在同一进程或者亲缘进程内使用
#include
这样就创建了一个无名信号量集合,其中semget的第2个参数表示这个信号量集合中有1个信号量。
而对于非亲缘关系的多进程内线程使用的信号量,则一般这样创建
#include#include key_t ipckey = ftok("/tmp/foo", 42); int semid = semget( ipckey, 1, IPC_CREAT | 0666);
ftok函数用于创建System V 的 IPC key, 其中第一个参数必须是存在的文件路径且具备可访问权限。
第2个参数(proj_id)必须是大于0的数,且这个数只有低8位起作用。
ftok通过计算指定文件的索引节点的index和第2个参数proj_id,来创建一个唯一的IPC key.
例如, /tmp/foo对应的文件索引号是0x100000, 那么proj_id等于42(0x2A), 那么得到的key就是 0x2A100000
也就是说,对于不同的进程,只要ftok的2个参数相同,则得到的IPC key的值也会是相同的。
而对于同一个IPC key, semget保证在不同的进程中能够或得到相同的信号量集合.
而这个信号量集合是一个内核数据结构,不会因为进程的退出而自动销毁,所以需要程序显式地进行销毁。
semctl的man page: http://linux.die.net/man/2/semctl
semctl( semid, 0, IPC_RMID, 0)
信号量的使用主要是通过这2个函数, man page(http://linux.die.net/man/2/semop)
int semop(int semid, struct sembuf *sops, unsigned nsops); int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);
首先了解一下sembuf 的结构
struct sembuf { unsigned short int sem_num; /* semaphore number */ short int sem_op; /* semaphore operation */ short int sem_flg; /* operation flag */ };
对于sem_op,分为下列几种情况
struct sembuf sem[2]; // 第一个操作 sem[0].sem_num = 0; // 操作信号量集合中的第一个信号量 sem[0].sem_op = 0; // 保证此信号量的值为0,否则阻塞 sem[0].sem_flg = SEM_UNDO; // 如果操作过程中进程退出则回滚 // 第二个操作 sem[1].sem_num = 0; // 操作信号量集合中的第一个信号量 sem[1].sem_op = 1; // 信号量值增1,阻塞其它线程进入 sem[1].sem_flg = SEM_UNDO; // 如果操作过程中进程退出则回滚 // 执行上述操作 if( semop( semid, sem, 2) == 0 ){ // To DO: 受保护的临界区代码 // ..... // 执行完成,信号量值减一,释放此锁,方便其它线程进入执行 sem[0].sem_op = -1; semop( semid, sem, 1); }
这是一种很常见的情况,在生产者-消费者线程池模型中, 一组生产者线程从网络或者文件中读取到数据包并追加到工作任务队列;另一组消费者线程从工作任务队列中获取工作任务后执行。
在这种模式下,这些消费者线程需要根据工作任务队列中是否有需要处理的工作任务来及时地启动或者等待执行。
首先对于生产者线程, 当将数据包追加到工作任务队列后,可以将可用资源数增一,此操作永远都不会阻塞
sem[0].sem_num = 0; // 指定操作的是该信号量集合中的第一个 sem[0].sem_op = 1; // 增1 sem[0].sem_flg = 0; semop( semid, sem, 1);
其次对于消费者线程
struct sembuf sem[1]; sem[0].sem_num = 0; // 操作信号量集合中的第一个信号量 sem[0].sem_op = -1; // 如果队列中有多余一个的工作任务,则理解返回,否则阻塞等待。 sem[0].sem_flg = SEM_UNDO; // 如果操作过程中进程退出则回滚 // 执行上述操作 if( semop( semid, sem, 1) == 0 ){ // TO DO: 锁定队列,获取并移除队列中的第一个工作任务进行处理 }