有关互斥量

创建和销毁互斥量

pthread_mutex_t_mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

大部分时间你可能在"文件范围"内,即函数体外,声明互斥量为外部或静态存储类型。如果有其他文件使用互斥量,即将其声明为外部类型;如果仅在本文件中使用,则将其声明为静态类型。应该使用宏PTHREAD_MUTEX_INITIALIZER来声明具有默认属性的静态互斥量。

通常不能静态地初始化一个互斥量,如使用malloc动态分配一个包含互斥量的数据结构时。这时,应该使用pthread_mutex_init调用来动态地初始化互斥量。也可以动态地初始化静态声明的互斥量,但必须保证每个互斥量在使用前被初始化,而且只被初始化一次。可以在创建任何线程之前初始化它,如通过调用pthread_once。若需要初始化一个非缺省属性的互斥量,必须使用动态初始化。
当不再需要一个通过pthread_mutex_init调用动态初始化的互斥量时,应该调用pthread_mutex_destroy来释放它。不需要释放一个使用PTHREAD_MUTEX_INITIALIZER宏静态初始化的互斥量。

加锁和解锁互斥量
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_trylock (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);

当调用线程已经锁住互斥量之后,就不能再加锁该互斥量。试图这样做的结果可能是返回错误(EDEADLK), 或者可能陷入"自死锁",使不幸的线程永远等待下去。不能解锁一个已经解锁的互斥量,也不能解锁由其他线程锁住的互斥量。锁住的互斥量是属于加锁线程的。

sched_yield效果:将处理器交给另一个等待运行的线程,但是如果没有就绪的线程,就立即返回。

当需要保护两个共享变量时,有两种基本策略:可以为每个变量指派一个“小”互斥量,或为两个变量指派一个“大”的互斥量。哪一种方法更好取决于很多因素。以下是主要的设计因素:
1 互斥量不是免费的,需要时间加锁和解锁。互斥量应该尽量少,够用即可,每个互斥量保护的区域则尽量大。
2 互斥量的本质是串行执行。若很多线程需要频繁地加锁在同一个互斥量,则线程的大部分时间就会在等待,这对性能是在害的。如果互斥量保护的数据包含彼此无关的片段,则可以将大的互斥量分解为几个小的互斥量来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待的时间就会减少。所以,互斥量应该足够多(到有意义的地步),每个互斥量保护的区域则应尽量的少。

使用多个互斥量:同时使用多个互斥量会导致复杂度的增加。最坏的情况就是死锁的发生,即两个线程分别锁住一个互斥量而等待对方的互斥量。更多的是像优先级倒置这样的微妙问题(当你将互斥量和优先级调用组合使用时)。

加锁层次:若可以在独立的数据上使用两个分离的互斥量,那么就应该这样做。这样,通过减少线程必须等待其他线程完成数据操作(甚至是该线程不需要的数据)的时间。若数据独立,则某个特定函数就不太可能经常需要同时加锁两个互斥量。
当数据不是完全独立的时候,情况就复杂了。如果你的程序有一个不变量,影响着由两个互斥量保护的数据,即使该不变量被改变或引用,你迟早需要编写同时锁住两个互斥量的代码,来确保不变量的完整。

两个锁的死锁:
有互斥量A B 若一个线程锁住互斥量A后,加锁互斥量B;同时,另一个线程锁住B而等待A,则代码就产生了一个经典的死锁现象。

针对死锁有解决方法:
固定加锁层次:所有需要同时加锁互斥量A和互斥量B的代码,必须首先加锁互斥量A,然后加锁互斥量B,即固定加锁顺序。
试加锁和回退:
在锁住某个集合中的第一个互斥量后,使用pthread_mutex_trylock来加锁集合中的其他互斥量,如果失败则将集合中所有已加锁互斥量释放,并重新加锁。

链锁:是层次锁的一个特殊实例,即两个锁的作用范围互相交叠。当锁住第一个互斥量后,代码进入一个区域,该区域需要另一个互斥量。当锁住另一个互斥量后,第一个互斥量就不再需要,可以释放它了。这种技巧在遍历如树型结构或链表结构时十分有用。每一个节点设置一个互斥量,而不是用一个互斥量锁住整个数据结构,阻止任何并行访问。如:遍历代码可以首先锁住队列头或树根节点,找到期望的节点,锁住它,然后释放根节点或队列头互斥量。

你可能感兴趣的:(多线程,数据结构,存储)