线程同步与进程间通信

版权声明:本文来自行者(http://blog.csdn.net/liuyuan185442111),转载请注明出处。 https://blog.csdn.net/liuyuan185442111/article/details/85233425

线程之间天然是共享数据的,所以重点讨论线程同步;进程之间进程资源是隔离的,所以重点讨论进程间通信。当然有些资源是多进程共享的,所以进程间也需要考虑同步。

线程同步

互斥量 pthread_mutex_t
读写锁 pthread_rwlock_t
条件变量 pthread_cond_t
条件变量让线程等待特定条件的发生,条件本身是由互斥量保护的。
有两个函数可以用于通知线程条件已经满足,pthread_cond_signal将唤醒等待该条件的某个线程,pthread_cond_broadcast将唤醒等待该条件的所有线程(pthread_cond_signal在实现的时候可以唤醒不止一个线程)。
使用pthread_cond_wait和pthread_cond_timewait等待条件的发生。man says “These functions atomically release mutex and cause the calling thread to block on the condition variable cond.”。这两个函数会原子地释放互斥量和将调用线程阻塞到条件变量上。调用线程被唤醒后成功获得互斥量之后函数再返回。
看APUE给出的实例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
struct msg {
struct msg *m_next;
/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void *process_msg(void *p)
{
    struct msg *mp;
        for(;;) {
                pthread_mutex_lock(&qlock);
                while(workq == NULL)
                        pthread_cond_wait(&qready, &qlock);
                mp = workq;
                workq = mp->m_next;
                pthread_mutex_unlock(&qlock);
                /* now process the message mp */
                free(mp);
        }
}
void *enqueue_msg(void *p)
{
        for(;;) {
                struct msg *mp = (struct msg *)malloc(sizeof(struct msg));
                pthread_mutex_lock(&qlock);
                mp->m_next = workq;
                workq = mp;
                pthread_cond_broadcast(&qready);
                pthread_mutex_unlock(&qlock);
                sleep(1);
        }
}
int main()
{
        pthread_t t1,t2;
        int i = 0;
        pthread_create(&t1,NULL,enqueue_msg,NULL);
        for(; i<10; ++i)
        {
                pthread_t t2;
                pthread_create(&t2,NULL,process_msg,NULL);
                pthread_detach(t2);
        }
        pthread_join(t1,NULL);
        return 0;
}

现在结合实例看一下wait操作:调用wait之前肯定要先判断条件是否满足,判断条件之前得先获得互斥锁,所以wait必须得释放互斥锁;如果wait的两个操作不是原子的,考虑在释放互斥锁之后马上切换到了生产者线程,生产者获得互斥锁产生新的条件并pthread_cond_signal了,那这个signal就会丢失掉,所以两个操作必须是原子的。
process_msg的流程看起来就像是同步的一样,通过条件变量异步操作转换为了同步操作(Lua的协程是不是就是这样实现的?)。唯一不像的地方是那个while循环,这个没办法,考虑pthread_cond_broadcast唤醒多个等待线程的情况,其他线程可能已经改变了条件,所以必须重新判断。
最后看一下enqueue_msg中pthread_cond_broadcast和释放互斥锁的先后顺序。总的来说,两种都可能是正确的。如果pthread_cond_broadcast在先,broadcast以后,等待线程会被调度运行,它们都会被阻塞在互斥锁上,等待生产者线程释放互斥锁后,一个等待线程会获得互斥锁满足条件然后使条件失效再释放互斥锁,然后其他等待线程获得互斥锁不满足条件继续调用wait等待。如果释放互斥锁在先,消费者可以在生产者调用broadcast之前被调度,获取互斥锁,然后使条件失效,最后释放互斥锁;接着,当调用broadcast时,条件不再为真,等待线程从wait返回之后再次进入wait等待,这种情况也使得从wait返回之后必须重新检查条件。

参考
APUE 11.6节和习题11.4
深入解析条件变量(condition variables)

进程间通信

主要工具:信号、管道、fifo、消息队列、信号量、共享存储、mmap、记录锁
(信号量和记录锁主要用于进程间同步)(未计入socket和UNIX域套接字)

总结
System V和POSIX都有信号量和消息队列,优先考虑使用POSIX。
共享内存用System V的,因为POSIX的实现尚未完善。
尽可能避免使用消息队列和信号量,而应当考虑管道和记录锁。(from APUE)

在相关进程间共享内存可以考虑使用mmap匿名映射;在无关进程间共享内存,除了XSI共享存储,也可以使用mmap将统一文件映射至它们的地址空间。(APUE 15.9节)

虽然信号量的意图在于进程间的同步,互斥量和条件变量的意图在于线程间同步,但信号量也可用于线程间同步,互斥量和条件变量也可通过共享内存区进行进程间同步。但应该根据具体应用考虑到效率和易用性进行具体的选择。(三个多线程同步问题及其实现中第一个例子即是信号量用于线程间同步)

TELL_WAIT除了最好使用sigaction函数替代signal函数中用信号的实现,也可以用管道实现(APUE 15.2节 管道),那么这样想:一个进程需要某个资源但不可用,于是阻塞住,另一个占有该资源的进程在释放资源的时候告诉第一个进程可以解除阻塞使用资源了,只要是一个进程可以通知另一个进程解除阻塞的工具都可以用来实现TELL_WAIT(个人推断,尚未实践)。那消息队列、二元信号量也都可以拿来实现TELL_WAIT。

参考
Unix/Linux的System V、BSD、Posix概念
Linux进程同步之POSIX信号量

猜你喜欢

转载自blog.csdn.net/liuyuan185442111/article/details/85233425