同步
(1). 场景
同一进程内各个线程间针对该进程内非线程私有数据的访问.各个进程间针对共享内存的访问.
(2). 同步的背景和目标
操作系统的每个进程,或多线程下进程内每个线程有一个自己的执行序列.现代操作系统对同时运行的多个进程,线程的指令按并发方式调度.即可能在多核处理器上同时执行多个线程,进程指令.可能在单核处理器上交叉执行不同线程,进程的指令,而非串行的执行一个进程,线程全部指令序列后再执行下一个指令序列.
基于上述背景,产生了同步需求.同步的目标,首要的是,保证并发执行下的一致性(达到并发交替调度执行和串行调度下执行结果一致),然后要保证的是,在一致性可以保证的情况下,提供尽可能大的并发支持(不要长时间锁定大段指令序列).
互斥锁,条件变量
(1). api
// 互斥锁
int pthread_mutex_lock(pthread_mutex_t*);
int pthread_mutex_trylock(pthread_mutex_t*);
int pthread_mutex_unlock(pthread_mutex_t*);
// 条件变量
// 需要等待时:释放互斥锁,进入睡眠.
// 被唤醒继续时:获取互斥锁,继续运行.
int pthread_cond_wait(pthread_cond_t* cptr, pthread_mutex_t* mptr);
int pthread_cond_timedwait(pthread_cond_t *cptr, pthread_mutex_t *mptr,
// 指定函数必须返回时的系统时间,如此时还未收到信号,返回ETIMEDOUT.自TUC时间1970.1.1子时,流逝的秒数和纳秒数
const struct timespec* abstime);
// 唤醒此条件变量上一个等待者
int pthread_cond_signal(pthread_cond_t* cptr);
// 唤醒此条件变量上所有等待者
int pthread_cond_broadcast(pthread_cond_t *cptr);
属性
(1). 互斥锁初始化
a. 常量初始化(获得默认属性)
如:pthread_mutex_t x = PTHREAD_MUTEX_INITIALIZER;
b. 堆上分配形式的初始化
如:pthread_mutex_init(...)
(2). 条件变量初始化
a. 常量初始化(获得默认属性)
如:pthread_cond_t x = PTHREAD_COND_INITIALIZER;
b. 堆上分配形式的初始化
如:pthread_cond_init(...)
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
int pthread_condattr_init(pthread_condattr_t* attr);
int pthread_condattr_destroy(pthread_condattr_t* attr);
// 在<unistd.h>中定义了_POSIX_PTHREAD_PROCESS_SHARED时才支持,PTHREAD_PROCESS_PRIVATE/PTHREAD_PROCESS_SHARED
int pthread_mutexattr_getpshared(const pthread_mutexattr_t* attr, int* valptr);
int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr, int value);
int pthread_condattr_getpshared(const pthread_condattr_t* attr, int* valptr);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int value);
int pthread_mutex_init(pthread_mutex_t *mptr, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mptr);
int pthread_cond_init(pthread_cond_t *cptr, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cptr);
读写锁
(1). api
int pthread_rwlock_rdlock(pthread_rwlock_t* rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t* rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t* rwptr);
// 不阻塞,无法获取时,返回EBUSY
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwptr);
属性
(1). 初始化
a. 常量初始化(获得默认属性)
如:pthread_rwlock_t x = PTHREAD_RWLOCK_INITIALIZER;
b. 堆上分配形式的初始化
如:pthread_rwlock_init(...)
int pthread_rwlockattr_init(pthread_rwlockattr_t* attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t* attr);
// 在<unistd.h>中定义了_POSIX_PTHREAD_PROCESS_SHARED时才支持,PTHREAD_PROCESS_PRIVATE/PTHREAD_PROCESS_SHARED,
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int* valptr);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t* attr, int value);
int pthread_rwlock_init(pthread_rwlock_t* rwptr, const pthread_rwlockattr_t* attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwptr);
线程取消
同一进程内线程A
可用pthread_cancel
取消线程B
.(线程A,B
属于同一进程)
int pthread_cancel(pthread_t tid);
void pthread_cleanup_push(void (*function)(void*), void* arg);
void pthread_cleanup_pop(int execute);
处理程序在以下情况下被调用:
(1). 线程被同进程另一线程取消
(2). 线程自己调pthread_exit
/从线程函数返回
若线程在pthread_cond_wait
阻塞期间被进程内另一个线程所取消,调pthread_cancel
的线程发出取消请求后即从调用返回
被取消线程,将执行:获取pthread_cond_wait
关联的互斥量,然后终止.
故对使用pthread_cond_wait
的函数常见的处理模式为
pthread_cleanup_push(清理函数, 参数)
pthread_cond_wait(...);
pthread_cleanup_pop(0);
这样在阻塞于pthread_cond_wait
期间,若被取消,则将执行清理函数.清理函数中,可以执行pthread_cond_wait
所关联互斥锁的释放及相关工作.若pthread_cond_wait
执行下去,则清理函数无存在必要(不需要执行锁的释放),用pthread_cleanup_pop
将其删除.
记录上锁
被锁住的文件通过其描述符访问.
用于不同进程间对文件的锁定.记录上锁是一种在共享内存出现前就存在的用于进程间对文件访问进行同步的机制.
int fcntl(int fd,
// F_GETLK/F_SETLK/F_SETLKW,cmd为F_GETLK,参数3-flock*是一个值-结果参数
int cmd, ...);
struct flock {
// F_RDLCK/F_WRLCK/F_UNLCK
short l_type;
// SEEK_SET/SEEK_CUR/SEEK_END
short l_whence;
// start offset
off_t l_start;
off_t l_len;
// 返回持有此锁的进程的ID
pid_t l_pid;
};
描述符关闭时,描述符对应所关联的记录锁会被删除.使用文件记录锁时,要直接用read/write
与文件交互.
信号量
(1). 进程可在某个信号量上执行的三种操作
sem_open,sem_close,sem_unlink
sem_t* sem_open(const char* name, int oflag, ...);
int sem_close(sem_t* sem);
int sem_unlink(const char* name);
int sem_wait(sem_t*);
int sem_trywait(sem_t*);
int sem_post(sem_t*);
// 通过参数2返回参数1所指信号量当前值,如信号量当前已上锁,则返回值为0,或为负数(其绝对值是等待该信号量解锁的线程数)
int sem_getvalue(sem_t*, int*);
int sem_init(sem_t*, int shared, unsigned int value);
int sem_destroy(sem_t*);
(1). 互斥锁&条件变量对比信号量:
a. 互斥锁的一对加锁,解锁必须有同一线程完成.信号量则不必.
b. 条件变量通知pthread_cond_signal
发出时,若无因等待对应条件变量而阻塞的线程.则无响应.信号量的资源释放.即使释放时没进程进程/线程阻塞等待此资源.释放操作也会影响到后来资源申请.
互斥锁/信号量进行同步时,一个不可避免的现象是死锁(在未采取死锁避免协议约束下)所以使用时最好引入超时机制.有名信号量具有随内核的持续性.意味着,进程创建/打开某信号量,改变值,后续进程退出.进程所做的更新写入内核信号量对象空间,后续对其他打开此信号量的进程是可见的.
(2). 进程间共享
进程间共享内存信号量/互斥锁/条件变量/读写锁时,同步对象需驻留在由所有希望共享它的进程所共享的内存区中,且对象需以
PTHREAD_PROCESS_SHARED
属性初始化/sem_init
参数2
为1
.有名信号量,不同进程通过同一名字打开的是同一信号量对象.
(3). 限制
一般系统对同时打开的信号量对象数,每个信号量对象最大值有限制