http://www.cnblogs.com/my_life/articles/4538299.html
http://www.dutor.net/index.php/2012/06/using-pthread-mutex-in-multiple-processes/
http://segmentfault.com/a/1190000000630435
http://blog.csdn.net/luansxx/article/details/7736618
这里的“不相干”,定义为:
- 这几个进程没有父子关系,也没有 Server/Client 关系
- 这一片共享内存一开始不存在,第一个要访问它的进程负责新建
- 也没有额外的 daemon 进程能管理这事情
看上去这是一个很简单的问题,实际上不简单。有两大问题:
进程在持有互斥锁的时候异常退出
如果用传统 IPC 的 semget 那套接口,是没法解决的。实测发现,down 了以后进程退出,信号量的数值依然保持不变。
用 pthread (2013年的)新特性可以解决。在创建 pthread mutex 的时候,指定为 ROBUST 模式。
pthread_mutexattr_t ma;
pthread_mutexattr_init(&ma);
pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&c->lock, &ma);
注意,pthread 是可以用于多进程的。指定 PTHREAD_PROCESS_SHARED 即可。
关于 ROBUST,官方解释在:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutexattr_setrobust.html
需要注意的地方是:
如果持有 mutex 的线程退出,另外一个线程在 pthread_mutex_lock 的时候会返回 EOWNERDEAD。这时候你需要调用 pthread_mutex_consistent 函数来清除这种状态,否则后果自负。
写成代码就是这样子:
int r = pthread_mutex_lock(lock);
if (r == EOWNERDEAD)
pthread_mutex_consistent(lock);
所以要使用这个新特新的话,需要比较新的 GCC ,要 2013 年以后的版本。
好了第一个问题解决了。我们可以在初始化共享内存的时候,新建一个这样的 pthread mutex。但是问题又来了:
怎样用原子操作新建并初始化这一片共享内存?
这个问题看上去简单至极,不过如果用这样子的代码:
void *p = get_shared_mem();
if (p == NULL)
p = create_shared_mem_and_init_mutex();
lock_shared_mem(p);
....
是不严谨的。如果共享内存初始化成全 0,那可能碰巧还可以。但我们的 mutex 也是放到共享内存里面的,是需要 init 的。
想象一下四个进程同时执行这段代码,很可能某两个进程发现共享内存不存在,然后同时新建并初始化信号量。某一个 lock 了 mutex,然后另外一个又 init mutex,就乱了。
可见,在 init mutex 之前,我们就已经需要 mutex 了。问题是,哪来这样的 mutex?前面已经说了传统 IPC 没法解决第一个问题,所以也不能用它。
其实,Linux 的文件系统本身就有这样的功能。
首先 shm_open 那一系列的函数是和文件系统关联上的。
~ ll /dev/shm/
其实 /dev/shm 是一个 mount 了的文件系统。这里面放的就是一堆通过 shm_open 新建的共享内存。都是以文件的形式展现出来。可以 rm,rename,link 各种文件操作。
其实 link 函数,也就是硬链接。是完成“原子操作”的关键所在。
搞过汇编的可能知道 CMPXCHG 这类(两个数比较,符合条件则交换)指令,是原子操作内存的最底层指令,最底层的信号量是通过它实现的。
而 link 系统调用,类似的,是系统调用级,原子操作文件的最底层指令。处于 link 操作中的进程即便被 kill 掉,在内核中也会完成最后一次这次系统调用,对文件不会有影响,不存在 “link 了一半” 这种状态,它是“原子”的。
伪代码如下:
shm_open("ourshm_tmp", ...);
// ... 初始化 ourshm_tmp 副本 ...
if (link("/dev/shm/ourshm_tmp", "/dev/shm/ourshm") == 0) {
// 我成功创建了这片共享内存
} else {
// 别人已经创建了
}
shm_unlink("ourshm_tmp");
首先新建初始化一份副本。然后用 link 函数。
最后无论如何都要 unlink 掉副本。
开源项目 kbz-event
这两种方法,貌似在各类经典书籍中都没提及,因为是 2013 年新出的,也是因为 Unix 鼓励用管道进行这类通信的原因。
在同类开源项目中。D-Bus 用的是另外的 daemon 进程去管理 socket。Android 的 IPC 则用了另外的内核模块(netlink 接口)来完成。
总之,都是用了额外的接口。
因此我开发了不需要额外 daemon 的轻量级 IPC 通信框架 kbz-event。
欢迎各种围观!
=========================================================
pthread的mutex通常用在多线程的同步当中,至于多进程的同步,一直以为只能使用记录锁和信号量,而这两种机制都需要内核的支持,属于“重量级”部件。也曾经在多进程同步中使用pthread mutex,但前提有两个:mutex能为多个进程所见,使mutex对象驻留在共享内存中;mutex本身不额外使用进程本地的内存,如堆内存。第一个前提容易满足,对于第二个,GCC的pthread实现也满足。但我一直没有注意到的是,pthread mutex标准本身对多进程提供了“可选”支持,只要实现支持、初始化mutex时pthread_mutexattr_t的pshared成员置为PTHREAD_PROCESS_SHARED即可。
pshared默认为PTHREAD_PROCESS_PRIVATE,即仅支持单进程。如果mutex驻留于共享内存,但pshared为PTHREAD_PROCESS_PRIVATE,此时多进程操作该mutex的行为是未定义的。这两天在写一个多进程可访问的库时就出现了这种“未定义”的行为,花费了一个多工作日才找到原因。
多进程使用mutex需要的基本操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <pthread.h> #include <sys/mman.h> #include <sys/types.h> pthread_mutex_t *mtx = NULL; int main() { //~ reside mutex in shm mtx = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); if (mtx == MAP_FAILED) { perror("mmap"); exit(1); } pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); //~necessary, or weird EINVAL error occurs when operating on the mutex pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(mtx, &attr); //~ here the fork pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } else if (pid > 0) { //~ parent //~ lock lock lock } else { //~ child //~ lock lock lock } return 0; } |
Linux环境下,可能需要码农们有更强的主动性。很多基础构件和第三方程序的描述文档和相关讯息不会“主动”找到你,尤其是那些不那么常用的特性,更不要说那些没有详细文档的了,很多时候需要你去扒拉他们的实现代码。但这也是开源魅力所在了,对于一个欲追求卓越的码农来说,代码就是莫大的恩赐了。Read the Fucking Manual, or Read the Fucking Code, beyond the code, nothing left.
========================================================
NAME
pthread_mutexattr_getrobust, pthread_mutexattr_setrobust - get and set the mutex robust attribute
SYNOPSIS
#include <pthread.h>
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict
attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
int robust);
DESCRIPTION
The pthread_mutexattr_getrobust() and pthread_mutexattr_setrobust() functions, respectively, shall get and set the mutex robust attribute. This attribute is set in the robust parameter. Valid values for robust include:
- PTHREAD_MUTEX_STALLED
- No special actions are taken if the owner of the mutex is terminated while holding the mutex lock. This can lead to deadlocks if no other thread can unlock the mutex.
This is the default value.- PTHREAD_MUTEX_ROBUST
- If the process containing the owning thread of a robust mutex terminates while holding the mutex lock, the next thread that acquires the mutex shall be notified about the termination by the return value [EOWNERDEAD] from the locking function. If the owning thread of a robust mutex terminates while holding the mutex lock, the next thread that acquires the mutex may be notified about the termination by the return value [EOWNERDEAD]. The notified thread can then attempt to mark the state protected by the mutex as consistent again by a call to pthread_mutex_consistent(). After a subsequent successful call to pthread_mutex_unlock(), the mutex lock shall be released and can be used normally by other threads. If the mutex is unlocked without a call to pthread_mutex_consistent(), it shall be in a permanently unusable state and all attempts to lock the mutex shall fail with the error [ENOTRECOVERABLE]. The only permissible operation on such a mutex is pthread_mutex_destroy().
The behavior is undefined if the value specified by the attr argument to pthread_mutexattr_getrobust() or pthread_mutexattr_setrobust() does not refer to an initialized mutex attributes object.
RETURN VALUE
Upon successful completion, the pthread_mutexattr_getrobust() function shall return zero and store the value of the robust attribute of attr into the object referenced by the robust parameter. Otherwise, an error value shall be returned to indicate the error. If successful, the pthread_mutexattr_setrobust() function shall return zero; otherwise, an error number shall be returned to indicate the error.
ERRORS
The pthread_mutexattr_setrobust() function shall fail if:
- [EINVAL]
- The value of robust is invalid.
These functions shall not return an error code of [EINTR].
EXAMPLES
None.
APPLICATION USAGE
The actions required to make the state protected by the mutex consistent again are solely dependent on the application. If it is not possible to make the state of a mutex consistent, robust mutexes can be used to notify this situation by calling pthread_mutex_unlock() without a prior call to pthread_mutex_consistent().
If the state is declared inconsistent by calling pthread_mutex_unlock() without a prior call to pthread_mutex_consistent(), a possible approach could be to destroy the mutex and then reinitialize it. However, it should be noted that this is possible only in certain situations where the state protected by the mutex has to be reinitialized and coordination achieved with other threads blocked on the mutex, because otherwise a call to a locking function with a reference to a mutex object invalidated by a call to pthread_mutex_destroy() results in undefined behavior.
RATIONALE
If an implementation detects that the value specified by the attr argument to pthread_mutexattr_getrobust() or pthread_mutexattr_setrobust() does not refer to an initialized mutex attributes object, it is recommended that the function should fail and report an [EINVAL] error.
FUTURE DIRECTIONS
None.
SEE ALSO
pthread_mutex_consistent, pthread_mutex_destroy, pthread_mutex_lock
XBD <pthread.h>
CHANGE HISTORY
First released in Issue 7.
对于robust互斥锁,当持有它的线程没解锁就退出以后,别的线程再去调用pthread_mutex_lock,函数会回一个EOWNERDEAD错误,线程检测到这这个错误后可以调用pthread_mutex_consistent使robust互斥锁恢复一致性,紧接着就可以调用phtread_mutex_unlock解锁了(尽管这个锁并不是当前这个线程加持的)。解锁完毕就可以重新调用pthread_mutex_lock了。
采用这种用方案的时候,首先要声明一个全局可见的mutex属性变量:
pthread_mutexattr_t mutexattr;然后初始化并设置属性值:
pthread_mutexattr_init(&mutexattr); pthread_mutexattr_setrobust(&mutexaddtr, PTHREAD_MUTEX_ROBUST);有了mutex属性,接下来就是在初始化mutex的地方作修改了,通常我们对mutex的初始化都是pthread_mutex_init(&mutex, NULL),现在改成:
pthread_mutex_init(&mutex, &mutexattr);现在准备工作已经完毕,开始干正事了。对于通知线程:
while(EOWNERDEAD==pthread_mutex_lock(&mutex)) {
pthread_mutex_consistent(&mutex);
pthread_mutex_unlock(&mutex);
}
global_count++;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
对于等待线程:
while(EOWNERDEAD==pthread_mutex_lock(&mutex)) { pthread_mutex_consistent(&mutex); pthread_mutex_unlock(&mutex); } while(global_count<=0) { pthread_cond_wait(&cond, &mutex); } global_count--; pthread_mutex_unlock(&mutex);