POSIX线程-互斥量

为什么需要互斥量

线程最大的好处是它们可以通过全局变量来共享信息。但这个好处也带来了麻烦:有可能很多线程同时修改某一个全局变量,导致该全局变量出现错误。我们必须制定某些规则,使线程对该全局变量的修改不会导致错误。这个规则就是--同步。

POSIX线程使用互斥量来进行同步,“经验表明,正确使用互斥量比使用通用信号灯之类的其他同步模型要容易,还能很容易地使用互斥量与条件变量建立任何同步模型”—POSIX多线程程序设计-p39

当某个线程想修改某个共享资源时,它需要锁住一个互斥量;当其他线程需要访问该资源时,它也要试图锁住同一个互斥量。同步是自愿的,参与者必须协同工作。如果某一个线程不遵守设计好的规则,数据将被破坏。

术语

原子(atomic):不能被任何操作中断的操作。

临界区(critical section):一段可以存取共享资源的代码。该段代码不能被另一个想存取该共享资源的线程打断。简单的说,临界区是会影响到共享资源的代码段。

mutex的初始化

    静态初始化

pthread_mutex_t mtx = PTHERAD_MUTEX_INITIALIZER

动态初始化

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

注意点:

1.       不能拷贝互斥量变量,因为使用拷贝的互斥量是不确定的。可以拷贝指向互斥量的指针,这样就可以使多个函数或线程共享互斥量来实现同步。

2.       必须保证每个互斥量在使用前被初始化,而且只被初始化一次。

3.       如果需要初始化一个非缺省属性的互斥量,必须使用动态初始化。

4.       pthread_muteattr_t的讨论

mutex的销毁

       int pthread_mutex_destroy(pthread_mutex_t *mutex);

       注意点:

1.       一个被销毁的互斥量,可以被pthread_mutex_init重新初始化。

2.       在释放互斥量占有的空间之前,最后先将互斥量解锁和释放。

mutex的操作

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

另外两个函数

int pthread_mutex_trylock(pthread_mutex_t *mutex)

int pthread_mutex_timelock(pthread_mutex_t *mutex, const struct timespec *abstime)

 

注意:

互斥量不是免费的,它需要时间来加锁和解锁。所以,互斥量应该尽量少,够用即可。互斥量的本质是串行执行。如果很多线程频繁的加锁同一个互斥量,则线程的大部分时间是在等待,这对性能有损害。如果互斥量保护的数据包含彼此不相关的片段,则可以将大的互斥量分解为几个小的互斥量来提高性能。

如何避免死锁

       当需要多个互斥量时,有一个潜在的危险就是死锁的出现。

最简单的方法是把多个mutex定义为有层次的结构。比如,所有的线程都必须用某种相同的顺序来获得mutex链锁是层次锁的一个特例,即两个锁的作用范围互相交叠。当锁住第一个互斥量后,代码进入一个区域,该区域需要另一个互斥量。当锁住另一个互斥量后,第一个互斥量就不再需要了。这种技巧在遍历如树型结构或链表结构时十分有用。每个节点设置一个互斥量,而不是用一个互斥量锁住整个数据结构,阻止任何并行访问。遍历代码可以首先锁住队列头或根节点,找到期望的节点,锁住它,然后释放根节点或队列互斥头。这种技巧仅当多个线程总是活跃在层次中的不同部分时才应该使用链锁。--POSIX多线程编程-p59

       另一种方法是“try and back off”,也就是调用pthread_mutex_trylock()。当该函数返回EBUSY时,就释放掉原来保持的所有mutex,然后再次尝试。这种方法很没有效率。

你可能感兴趣的:(posix)