linux多线程和锁
第二部分 互斥锁
第三部分 条件变量
第四部分 读写锁
第五部分 自旋锁
第六部分 线程壁垒
第七部分 记录锁
第一部分 多线程
Linux 线程的创建:
int pthread_create ( pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
tid:返回的线程id
attr:创建线程的属性可以为NULL,也可以在运行时再改变
func:线程运行的callback函数
arg:传给callback函数的参数
主要属性简要说明:
PTHREAD_CREATE_DETACH :表示同步脱离,且在退出时自行释放所占用的资源, 此时线程不能用pthread_join()来同步,这个属性可以在线程运行时调用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE(默认属性)状态。
int pthread_detach (pthread_t tid);
说明:线程默认终止的时候资源不释放需要主进程调用pthread_join函数才能释放(类似于僵死进程和waitpid函数), pthread_detach 使得线程终止即释放资源,主进程不需要调用pthread_join函数
线程退出情形:
1) 正常return退出
2) 被其它线程调用pthread_cancel取消
3) 调用pthread_exit 退出,其中参数为函数退出时返回的数据
int pthread_join (pthread_t thread, void **rval_ptr);
阻塞直到指定线程终止, 并返回线程的返回值。
注意pthread_join函数操作的线程必须不是DETACHED线程。
rval_ptr返回值设置:
1) 如果线程正常return,则设置为return的值
2) 如果线程被取消则设置为PTHREAD_CANCELED
3) 如果线程调用pthread_exit退出则为pthread_exit 的参数
退出函数设置:
当线程退出时,可以设置一组退出函数类似于进程的atexit
void pthread_cleanup_push (void (*rtn)(void *), void *arg);
void pthread_cleanup_pop (int execute);
退出函数执行条件:
1) 线程调用pthread_exit 退出
2) 线程被其它线程取消
3) 调用pthread_cleanup_pop 并且参数非0
注意:函数正常return 不调用退出函数
线程的取消:
int pthread_cancel (pthread_t tid);发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止 .
int pthread_setcancelstate (int state, int *oldstate)
设置本线程对cancel信号的反应.
state有两种值:
1)PTHREAD_CANCEL_ENABLE(缺省)收到信号后设为CANCLED状态2)PTHREAD_CANCEL_DISABLE,忽略CANCEL信号继续运行
old_state如果不为NULL则存入原来的cancel状态以便恢复。
int pthread_setcanceltype (int type, int *oldtype)
设置本线程取消动作的执行时机,仅当cancel状态为Enable时有效
type由两种取值:
1) PTHREAD_CANCEL_DEFFERED表示收到信号后继续运行至下一个取消点再退出
2) PTHREAD_CANCEL_ASYCHRONOUS表示立即执行取消动作(退出);
oldtype如果不为NULL则存入原来的取消动作类型值。
void pthread_testcancel (void)
检查本线程是否处于canceld状态,如果是,则执行取消动作,否则直接返回。
此函数用来设置取消点
线程私有数据(Thread-specific Data,或TSD)
int pthread_key_create (pthread_key_t *keyptr, void (*destructor) (void *value));
该函数从TSD池中分配一项,将其值赋给keyptr供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。
int pthread_key_delete (pthread_key_t key)这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数,而只是将TSD释放以供下一次调用pthread_key_create()使用。
TSD的读写:
int pthread_setspecific (pthread_key_t key, const void *pointer)
写入,将pointer的值(不是所指的内容)与key相关联
void * pthread_getspecific (pthread_key_t key)
读出函数,将与key相关联的数据读出来。
说明:不同线程对同一个key的访问互不冲突。
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once (pthread_once_t *initflag, void (*initfn)(void));
多个线程调用,保证仅执行一次的函数,其中initflag不可以是局部变量。
获得线程本身id
pthread_t pthread_self (void);
线程id 比较int pthread_equal (pthread_t tid1, pthread_t tid2); 相等返回非0
线程IO操作:
如果多个线程并发访问同一个文件的时候可以使用pread或pwrite它们保证了原子操作
其它:
pthread_kill_other_threads_np() 强行杀死所有线程,它没有通过pthread_cancel()来终止线程,而是直接向管理线程发“进程退出”信号,使所有其他线程都结束运行,而不经过cancel动作,当然也不会执行退出回调函数 ,一般用在exec执行前来结束所有正在运行的线程。
线程属性操作(略)
第二部分 互斥锁
Mutex基本操作函数:
初始化方法:
1) 如:static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER 使用默认属性
2) 调用pthread_mutex_init 方法,可以同时设定属性
销毁方法:
int pthread_mutex_destroy (pthread_mutex_t *mutex);
销毁一个互斥锁即释放它所占用的资源,并且要求锁当前是非锁定状态。由于在Linux中,互斥锁并不占用任何资源,它除了检查锁状态以外(锁定状态则返回EBUSY)没有其它动作。
互斥锁基本操作:
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_trylock (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);
Mutex基本属性操作:
int pthread_mutexattr_init (pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy (pthread_mutexattr_t *attr);
int pthread_mutexattr_getpshared (const pthread_mutexattr_t * restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared (pthread_mutexattr_t *attr, int pshared);
说明:
1) Mutex默认只能用于多线程间互斥操作即属性为PTHREAD_PROCESS_PRIVATE ,如果用于多进程操作必须修改其属性为PTHREAD_PROCESS_SHARED
2) pthread_mutexattr_init 和 pthread_mutexattr_destroy 函数用来初始化或析构pthread_mutexattr_t 结构 但由另外两个函数来设置PTHREAD_PROCESS_SHARED 属性
3) 注意有些平台不支持多进程间互斥锁.
其它属性简要介绍:
1) PTHREAD_MUTEX_TIMED_NP: 普通锁(缺省值)。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
2) PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争
3) PTHREAD_MUTEX_ERRORCHECK_NP: 检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁
4) PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,仅等待解锁后重新竞争。
使用互斥锁注意事项:
1) Linux 中实现的POSIX线程锁都不是取消点,因此,线程被取消后不会因为收到取消信号而离开加锁等待状态。
2) 如果线程在加锁后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。
3) 这个锁机制同时不是异步信号安全的,所以不应该在信号处理函数中使用互斥锁,否则容易造成死锁。
第三部分 条件变量
条件变量主要的目的是等待直到条件成立。类似于某些系统的Event。
它主要包括两个动作:
1) 一个线程等待“条件变量的条件成立”而挂起;
2) 另一个线程使“条件成立”。
注意:为了防止竞争条件变量的使用总是和一个互斥锁结合在一起
条件变量基本操作函数:初始化方法:
1) 如:static pthread_cond_t cond=PTHREAD_COND_INITIALIZER默认属性
2) 调用pthread_cond_init 方法,可以同时设定属性
销毁方法:
int pthread_cond_destroy (pthread_cond_t *cond);
只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。
等待操作:
int pthread_cond_wait (pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex)
int pthread_cond_timedwait (pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
产生信号操作:
int pthread_cond_signal (pthread_cond_t *cond);激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个
int pthread_cond_broadcast (pthread_cond_t *cond);激活所有等待线程
属性基本操作(类似于互斥锁)
int pthread_condattr_init (pthread_condattr_t *attr);
int pthread_condattr_destroy (pthread_condattr_t *attr);
int pthread_condattr_getpshared (const pthread_condattr_t * restrict attr, int *restrict pshared);
int pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared);
使用条件变量注意事项:
1) pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点, 在该处等待的线程将立即重新运行 2) 被取消后它首先获的互斥锁,然后执行取消动作。即互斥锁是保持锁定状态的,因而需要定义退出回调函数来为其解锁。
使用举例:
{
pthread_mutex_lock (&mutex));
pthread_cond_wait (&cond,& mutex);
pthread_mutex_unlock (&mutex);
}
第四部分 读写锁
读写锁基本特点:
1)如果没有线程持有写锁, 那么获得读锁就会成功
2)如果没有线程持有读锁和写锁,那么获得写锁才会成功
它常被应用于频繁读而很少修改得情况。
读写锁初始化或销毁:
static pthread_rwlock_t rwLock = PTHREAD_RWLOCK_INITIALIZER
int pthread_rwlock_init (pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);
获得锁或释放锁操作
int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);
属性设置
int pthread_rwlockattr_init (pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy (pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *attr, int pshared);
第五部分 自旋锁
自旋锁基本特点:
1) 在用户空间实现, 不需要做内核切换
2) 忙-等待,也就是说在获得不了锁的情况下仍然占用CPU资源
3) 要求关键区域执行时间必须非常得短小,并且不允许睡眠, 如果执行时间超过了其它线程等待的时间片(即时间片用完,发生了线程切换), 则用自旋锁就失去了意义,并空耗了CPU时间, 这种情况不防称为失败的等待
4) 如果失败的等待非常多会严重影响系统的性能。此时就应该选用其它锁
基本操作函数:
1) 初始化方法: __pshared大于0表示用在多进程间
int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared)
2)销毁方法:
int pthread_spin_destory (pthread_spinlock_t * __lock)
3) 锁操作方法:
int pthread_spin_lock (pthread_spinlock_t * __lock)
int pthread_spin_trylock (pthread_spinlock_t * __lock)
int pthread_spin_unlock (pthread_spinlock_t * __lock)
第六部分 线程壁垒
壁垒(barrier)基本说明:
主要用来等待某些数量的线程在壁垒处集合。在设定的数目达到之后,解锁这些线程,让它们继续运行。
而pthread_join() 方法是等待线程结束
壁垒(barrier)基本操作函数:
初始化: count表示必须调用pthread_barrier_wait线程的个数
int pthread_barrier_init (pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count);
销毁函数:
int pthread_barrier_destroy (pthread_barrier_t *barrier)
同步点函数:
int pthread_barrier_wait (pthread_barrier_t *barrier);
第七部分 记录锁
记录锁主要用于不同进程间锁定文件,它可以锁定整个文件或部分字节数据
注意:记录锁是面向进程的,因此不能用于线程间。
主要操作函数如下:
int fcntl (int filedes, int cmd, ... /* struct flock *flockptr */ );
其中:
struct flock
{
short l_type; /* F_RDLCK(共享读), F_WRLCK(排斥写), F_UNLCK(解锁) */
off_t l_start; /* 相对于l_whence的偏移(字节单位) */
short l_whence; /* SEEK_SET, SEEK_CUR, or SEEK_END */
off_t l_len; /* 锁定长度,0表示锁定到文件结束 */
pid_t l_pid; /* 用于 F_GETLK */
};
cmd:
F_GETLK: 获得锁状态
F_SETLK: 非阻塞获得锁失败返回EAGAIN
F_SETLKW: 阻塞获得锁, 可被信号唤醒。
记录锁用法说明:
1) 当进程终止或关闭描述符的时候,锁自动释放。
a. fd1 = open (pathname, ...); b. fd1 = open (pathname, ...);
read_lock(fd1, ...); read_lock(fd1, ...);
fd2 = dup(fd1); fd2 = open (pathname, ...)
close(fd2); close(fd2);
2) 当fork子进程的时候,锁不被继承。
3) 当执行exec的时候锁不释放。设置了close-on-exec的除外
4) 有Advisory和Mandatory锁
系统中默认是Advisory锁, 当一个进程获得锁进行读写的时候,并不能阻止其它进行不去获得锁直接进行读写, 为了保证操作的正确性, 所有操作文件的进程必须按照规则, 首先获得锁 然后才能进行读写。
Mandatory 锁保证了当一个进程获得锁进行读写得时候, 其它进行直接读写文件会失败。