《APUE》之进程篇

条件变量(Condition Variable)

CV有两个问题值得讨论:

  1. 为什么有了mutex,仍需要cond
  2. 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
}

这个实现存在问题:

  1. 在Thread1做完“do work after notification”前,Thread2将不能进行“do work precedes notification”工作,因为mutex未释放,Thread2 block在lock(mutex)处。这种情况,将“do work after notification”和“do work precedes notification”放在同一个线程显然更合适。
  2. 如果Thread2不能抢占mutex,Thread1将会re-lock并继续执行,但此时它并没有收到notification,这与语义不符。虽然我们可以利用操作系统一些机制,比如在Thread1中sleep来控制Thread1和Thread2 lock的先后顺序,但这是一个糟糕的设计。

上述mutex实现方案的重大缺陷,就是cond存在的意义。

refer: https://stackoverflow.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的实现逻辑为:

  1. 将线程放入cond等待队列
  2. unlock mutex
  3. 等待唤醒信号唤醒
  4. lock mutex并返回

refer: https://stackoverflow.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都是半双工的。

原文地址:https://www.cnblogs.com/gm-201705/p/12897934.html