现在的位置: 首页 > 综合 > 正文

linux多线程和锁

2013年12月04日 ⁄ 综合 ⁄ 共 8146字 ⁄ 字号 评论关闭

第一部分 多线程 

第二部分 互斥锁 

第三部分 条件变量 

第四部分 读写锁 

第五部分 自旋锁 

第六部分 线程壁垒 

第七部分 记录锁

 

  

第一部分 多线程

  

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 锁保证了当一个进程获得锁进行读写得时候, 其它进行直接读写文件会失败。

抱歉!评论已关闭.