条件变量(Condition Variable)
CV有两个问题值得讨论:
- 为什么有了mutex,仍需要cond
- cond为什么一定要配合mutex使用
为什么有了mutex,仍需要cond
mutex与cond的适用场景并不同,mutex是控制shared resource在任一时刻只能由一个线程访问。
而cond实现了一种通知机制,当某个条件满足,则唤醒等待在这个条件的线程继续执行。如果使用mutex模拟cond,代码将会是这样:
Thread1:
while(1) {
lock(mutex); // Blocks waiting for notification from Thread2
... // do work after notification is received
unlock(mutex); // Tells Thread2 we are done
}
Thread2:
while(1) {
... // do the work that precedes notification
unlock(mutex); // unblocks Thread1
lock(mutex); // lock the mutex so Thread1 will block again
}
这个实现存在问题:
- 在Thread1做完“do work after notification”前,Thread2将不能进行“do work precedes notification”工作,因为mutex未释放,Thread2 block在lock(mutex)处。这种情况,将“do work after notification”和“do work precedes notification”放在同一个线程显然更合适。
- 如果Thread2不能抢占mutex,Thread1将会re-lock并继续执行,但此时它并没有收到notification,这与语义不符。虽然我们可以利用操作系统一些机制,比如在Thread1中sleep来控制Thread1和Thread2 lock的先后顺序,但这是一个糟糕的设计。
上述mutex实现方案的重大缺陷,就是cond存在的意义。
refer: https://*.com/questions/12551341/when-is-a-condition-variable-needed-isnt-a-mutex-enough
cond为什么一定要配合mutex使用
如果在改变条件和发送信号前不加锁,将可能导致唤醒信号丢失,考虑下面情况:
Thread1:
pthread_mutex_lock(&mutex);
while (condition == FALSE)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
Thread2:(不加锁的错误实现)
condition = TRUE;
pthread_cond_signal(&cond);
假定condition初始值为FALSE,Thread1和Thread2两个线程代码交替执行,则可能发生下面情况:
Thread1 Thread2
pthread_mutex_lock(&mutex);
while (condition == FALSE)
condition = TRUE;
pthread_cond_signal(&cond);
pthread_cond_wait(&cond, &mutex);
此时condition为TRUE,但由于pthread_cond_wait未执行,也就是Thread1并没有在cond的等待队列中,将导致Thread2的pthread_cond_signal发出的唤醒信号丢失,Thread1将一直block在pthread_cond_wait上面。
而如果在Thread2中加锁:
pthread_mutex_lock(&mutex);
condition = TRUE;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
这样唤醒信号将永远不会丢失。其中pthread_cond_wait的实现逻辑为:
- 将线程放入cond等待队列
- unlock mutex
- 等待唤醒信号唤醒
- lock mutex并返回
refer: https://*.com/questions/12551341/when-is-a-condition-variable-needed-isnt-a-mutex-enough
进程间通信IPC(interprocess communication)
Linux进程间通信分为几类:
1。管道(pipe)和FIFO(命名管道)
2。XSI IPC:消息队列,信号量(只是一种进程同步原语,配合共享内存等IPC使用),共享内存
3。网络套接字(用于跨机器通信)
4。UNIX域套接字(与网络socket类似,使用同一套API,但只能单机进程间通信。优点是免去了网络套接字协议处理部分,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文,因此效率更高)
这里说一下XSI IPC与其它几类IPC的比较。
内核中,每个XSI IPC结构,都使用一个非负数的标识符加以引用。例如,为了对一个消息队列发送或取消息,只需要知道其队列标识符。与文件描述符不同,IPC标识符不是小的整数。当一个IPC结构被创建(下面讨论的都专指 3种XSI IPC:消息队列、信号量和共享内存),以后又被删除时,与这个结构相关的标识符连续加1,直至达到一个整数型的最大正值,然后又回转到0.
标识符是IPC对象的内部名。为了使IPC对象可以被多个进程使用,需要提供一个外部名方案。为此使用了键(key),每个IPC对象都与一个键相关联,于是键就用作该对象的外部名。
XSI IPC还为每个IPC结构设置了一个ipc_perm结构。该结构规定了权限和所有者。
XSI IPC的主要问题是:
1。IPC结构是在系统范围内起作用的,没有访问计数。例如,如果进程创建了一个消息队列,凌晨在该队列放个几则消息,然后终止。但是该消息队列和消息不会被删除。
2。这些IPC结构在文件系统没有名字。我们不能通过open,write,read,fcntl,close等函数来操作他们,为了支持这些功能 ,不得不增加十几条全新的系统调用。我们不能用ls命令见到IPC对象,不能使用rm命令删除他们,也不能通过chmod来修改他们的权限。于是不得不增加ipcs()和ipcrm命令。
3。因为这些IPC不使用文件描述符,所以不能对他们使用多路转接I/O函数:select和pool。
总之,Stevens认为XSI IPC的设计是不好的。
在IPC选择方面,他给了以下一些建议:
要学会使用管道和FIFO,因为在大量应用程序中级可有效地使用这两种基本技术。在新的应用程序中,要尽可能避免使用消息队列以及信号量,而应当考虑全双工管道和记录锁,他们使用起来会简单得多。共享内存有其应用场合,而mmap函数也能提供同样的功能。
值得注意的是,Linux的全双工管道使用UNIX域套接字实现(socketpair) 。默认管道和FIFO都是半双工的。