博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

apr介绍 进程间通信 线程间通信

Posted on 2016-01-20 18:08  bw_0927  阅读(782)  评论(0)    收藏  举报

http://blog.csdn.net/zhuky/article/details/5020728

http://www.cnblogs.com/my_life/articles/4532873.html

http://blog.csdn.net/tingya/article/category/119274/2

 

 我见过的对记录锁讲解最详细的书就是《Unix高级环境编程》,特别是关于进程、文件描述符和记录锁三者之间关系的讲解更是让人受益匪浅,有此书的朋友一定不要放过哟。这里将其中的三原则摘录到这:
关于记录锁的自动继承和释放有三条规则:
(1) 锁与进程、文件两方面有关。这有两重含意:第一重很明显,当一个进程终止时,它所建立的锁全部释放;第二重意思就不很明显,任何时候关闭一个描述符时,则该进程通过这一描述符可以存访的文件上的任何一把锁都被释放(这些锁都是该进程设置的)。
(2) 由fork产生的子程序不继承父进程所设置的锁。这意味着,若一个进程得到一把锁,然后调用fork,那么对于父进程获得的锁而言,子进程被视为另一个进程,对于从父进程处继承过来的任一描述符,子进程要调用fcntl以获得它自己的锁。这与锁的作用是相一致的。锁的作用是阻止多个进程同时写同一个文件(或同一文件区域)。如果子进程继承父进程的锁,则父、子进程就可以同时写同一个文件。
(3) 在执行exec后,新程序可以继承原执行程序的锁。

 

在经典的《Unix网络编程第1卷》Chapter 6中作者详细介绍了五种I/O模型,分别为:
 - blocking I/O
 - nonblocking I/O
 - I/O multiplexing (select and poll)
 - signal driven I/O (SIGIO)
 - asynchronous I/O (the POSIX aio_functions)
作者同时对这5种I/O模型作了很详细的对比分析,很值得一看。这里所说的I/O多路复用就是第三种模型,它既解决了Blocking I/O数据处理不及时,又解决了Non-Blocking I/O采用轮旬的CPU浪费问题,同时它与异步I/O不同的是它得到了各大平台的广泛支持。

 

 

  •  APR分析-进程同步篇

APR提供三种同步措施,分别为:
apr_thread_mutex_t - 支持单个进程内的多线程同步;
apr_proc_mutex_t - 支持多个进程间的同步;
apr_global_mutex_t  - 支持不同进程内的不同线程间同步。

 

1、同步机制
APR提供多种进程同步的机制供选择使用。在apr_proc_mutex.h中列举了究竟有哪些同步机制:
typedef enum {
    APR_LOCK_FCNTL,         /* 记录上锁 */
    APR_LOCK_FLOCK,         /* 文件上锁 */
    APR_LOCK_SYSVSEM,       /* 系统V信号量 */
    APR_LOCK_PROC_PTHREAD,  /* 利用pthread线程锁特性 */
    APR_LOCK_POSIXSEM,      /* POSIX信号量 */
    APR_LOCK_DEFAULT        /* 默认进程间锁 */
} apr_lockmech_e;
这几种锁机制,随便拿出哪一种都很复杂。APR的代码注释中强调了一点就是“只有APR_LOCK_DEFAULT”是可移植的。这样一来用户若要使用APR进程同步机制接口,就必须显式指定一种同步机制。

2、实现点滴
APR提供每种同步机制的实现,每种机制体现为一组函数接口,这些接口被封装在一个结构体类型中:
/* in apr_arch_proc_mutex.h */
struct apr_proc_mutex_unix_lock_methods_t {
    unsigned int flags;
    apr_status_t (*create)(apr_proc_mutex_t *, const char *);
    apr_status_t (*acquire)(apr_proc_mutex_t *);
    apr_status_t (*tryacquire)(apr_proc_mutex_t *);
    apr_status_t (*release)(apr_proc_mutex_t *);
    apr_status_t (*cleanup)(void *);
    apr_status_t (*child_init)(apr_proc_mutex_t **, apr_pool_t *, const char *);
    const char *name;
};

 

3、同步机制
按照枚举类型apr_lockmech_e的声明,我们知道APR为我们提供了5种同步机制,下面分别简单说说:
(1) 记录锁
记录锁是一种建议性锁,它不能防止一个进程写已由另一个进程上了读锁的文件,它主要利用fcntl系统调用来完成锁功能的,记得在以前的一篇关于APR 文件I/O的Blog中谈过记录锁,这里不再详细叙述了。

(2) 文件锁
文件锁是记录锁的一个特例,其功能由函数接口flock支持。值得说明的是它仅仅提供“写入锁”(独占锁),而不提供“读入锁”(共享锁)。

(3) System V信号量
System V信号量是一种内核维护的信号量,所以我们只需调用semget获取一个System V信号量的描述符即可。值得注意的是与POSIX的单个“计数信号量”不同的是System V信号量是一个“计数信号量集”。所以我们在注意的是在初始化时设定好信号量集的属性以及在调用semop时正确选择信号量集中的信号量。在APR的System V信号量集中只是申请了一个信号量。

(4) 利用线程互斥锁机制
APR使用pthread提供的互斥锁机制。原本pthread互斥锁是用来互斥一个进程内的各个线程的,但APR在共享内存中创建了pthread_mutex_t,这样使得不同进程的主线程实现互斥,从而达到进程间互斥的目的。截取部分代码如下:
new_mutex->pthread_interproc = (pthread_mutex_t *)mmap(
                                       (caddr_t) 0, 
                                       sizeof(pthread_mutex_t), 
                                       PROT_READ | PROT_WRITE, MAP_SHARED,
                                       fd, 0);

(5) POSIX信号量
APR使用了POSIX有名信号量机制,从下面的代码中我们可以看出这一点:
/* in proc_mutex.c */
apr_snprintf(semname, sizeof(semname), "/ApR.%lxZ%lx", sec, usec); /* APR自定义了一种POSIX信号量命名规则,在源代码中有说明 */
psem = sem_open(semname, O_CREAT, 0644, 1);

 

  • APR分析-线程同步篇

在线程同步方面,Posix标准定义了3种同步模型,分别为互斥量、条件变量和读写锁。

一、互斥量
互斥量是线程同步中最基本的同步方式。互斥量用于保护代码中的临界区,以保证在任一时刻只有一个线程或进程访问临界区。

1、互斥量的初始化
在POSIX Thread中提供两种互斥量的初始化方式,如下:
(1) 静态初始化
互斥量首先是一个变量,Pthread提供预定义的值来支持互斥量的静态初始化。举例如下:
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
既然是静态初始化,那么必然要求上面的mutex变量需要静态分配。在APR中并不支持apr_thread_mutex_t的使用预定值的静态初始化(但可以变通的利用下面的方式进行静态分配的mutex的初始化)。

(2) 动态初始化
除了上面的情况,如果mutex变量在堆上或在共享内存中分配的话,我们就需要调用一个初始化函数来动态初始化该变量了。在Pthread中的对应接口为pthread_mutex_init。APR封装了这一接口,我们可以使用下面方式在APR中初始化一个apr_thread_mutex_t变量。

二、条件变量
互斥量一般用于被设计被短时间持有的锁,一旦我们不能确定等待输入的时间时,我们可以使用条件变量来完成同步。

 

这里我们所说的条件变量让对等的线程间达成协议,即“某一线程发现某一条件满足时必须发信号给阻塞在该条件上的线程,将后者唤醒”。这样我们就有了两种角色的线程,分别为
(1) 给条件变量发送信号的线程
其流程大致为:
{
        获取条件变量关联锁;
        修改条件为真;
        调用apr_thread_cond_signal通知阻塞线程条件满足了;------ (a)
        释放变量关联锁;
}
(2) 在条件变量上等待的线程
其流程大致为:
{
        获取条件变量关联锁;
        while (条件为假) { --------------------- (c)
                调用apr_thread_cond_wait阻塞在条件变量上等待;------ (b)
        }
        修改条件;
        释放变量关联锁;
}
上面两个流程中,理解三点最关键:
a) apr_thread_cond_signal中调用的pthread_cond_signal保证至少有一个阻塞在条件变量上的线程恢复;在《Unix网络编程 Vol2》中也谈过这里存在着一个race。即在发送cond信号的同时,该发送线程仍然持有条件变量关联锁,那么那个恢复线程的apr_thread_cond_wait返回时仍然拿不到这把锁就会再次挂起。这里的这个race要看各个平台实现是如何处理的了。
b) apr_thread_cond_wait中调用的pthread_cond_wait原子的将调用线程挂起,并释放其持有的条件变量关联锁;
c) 这里之所以使用while反复测试条件,是防止“伪唤醒”的存在,即条件并未满足就被唤醒。所以无论怎样,唤醒后我都需要重新测试一下条件,保证该条件的的确确满足了。

条件变量在解决“生产者-消费者”问题中有很好的应用,在我以前的一篇blog中也说过这个问题。

三、读写锁
前面说过,互斥量把想进入临界区而又试图获取互斥量的所有线程都阻塞住了。读写锁则改进了互斥量的这种霸道行为,它区分读临界区数据和修改临界区数据两种情况。这样如果有线程持有读锁的话,这时再有线程想读临界区的数据也是可以再获取读锁的。读锁和写锁的分配规则在《Unix网络编程 Vol2》中有详细说明,这里不详述。

四、小结
三种同步方式如何选择?场合不同选择也不同。互斥量在于完全同步的临界区访问;条件变量在解决“生产者-消费者”模型问题上有独到之处;读写锁则在区分对临界区读写的时候使用。