[多线程学习笔记]互斥量

在学习操作系统概念的时候,我直到互斥量的概念是简单的,就是为了保护临界区代码。

让一次只有一个线程访问临界区代码。

在学习《POSIX多线程程序设计》的时候看到了不变量,临界区和谓词的概念才有所感悟。

所谓临界区代码,就是那些影响了共享数据的代码。

“由于大部分程序员习惯于思考程序功能而非程序数据,所以你会发现认识临界区比认识数据不变量更容易。不过临界区总能够对应到一个数据不变量,反之亦然。”

typedef struct my_struct_tag
{
    pthread_mutex_t mutex; /* 保护value被访问*/
    int                         value; /* 被保护的value    */
}my_struct_t;
 
我觉得这个结构体很能给我启示,他把要被保护的值和一个互斥量封装在一个结构体里。
 
因为我现在一直在数据流划分的问题,多个线程给我一滩的感觉,我无法感触到实体的感觉,我想:难道解决这个问题的方法就是封装再封装?
 
我还对多线程的队列有了想法,难道让每一个节点一个队列?
 
1.不能锁了之后,又锁,会造成自死锁
2.不要解锁一个已经解锁的互斥量。
 

使用互斥量本来就是为了实现原子操作。

 
互斥量的设计因素本来就是矛盾的:
1. 互斥量不是免费的,需要时间来加锁和解锁,所以锁的数量应该尽量少,每个锁所保护的区域应该尽量的大。
 
2. 互斥量的本质是串行执行,如果很多线程需要频繁的加锁同一个互斥量,那么线程的大部分时间就会在等待。这对性能是有害的。
 
如果互斥量保护的数据包含彼此无关的代码片段,则可以将大的互斥量分解为几个小的互斥量来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待的时间就会减少。
所以锁的数量应该足够多,每个锁保护的区域应该足够大。
 
3. 上述两方面看起来是相互矛盾的,但是:平衡正是我们的追求。
 

加锁层次:

 
固定加锁层次:
所有需要同时加锁互斥量A和互斥量B的代码,必须首先加锁互斥量A,然后加锁互斥量B.
试加锁和回退:
在锁住某个集合中的第一个互斥量后,使用pthread_mutex_trylock来加锁集合中的其他互斥量,
如果失败则将集合中所有已经加锁互斥量释放,并重int lock_set(lock_set * set)
{
    lock * lock;
   /* 头是哑节点*/
    lock = set->head;
    for(lock = lock->next;lock;lock=lock->next)
    {
        if(trylock(lock) != SUCCESS)
        {
            /* 解锁顺序最好和加锁顺序相反,原来如此。*/
            for(lock=set->tail;lock != set->head;lock=lock->prev)
            {
                unlock(lock);
            }
         
        }                                                                  
    }
}

上面这个方法更好吧。

int unlock_set(lock_set * set)
{
    lock * lock;
    lock = set->tail;
    for(lock;lock != set->head;lock=lock->prev)
    {
        unlock(lock);
    }
}
如果你使用“试锁和回退”算法,你应该总是以相反的顺序解锁互斥量,即加锁顺序 1234,解锁顺序4321
 
防止你这边解掉了123个锁,另一个线程保持这些锁之后,准备拿第四个锁的时候,发现4还在锁的状态,于是又把123给释放了。
所以这么做在于减少回退操作的可能性。
 
链锁:
链锁是层次锁的一个特殊实例,即两个锁的作用范围相互交叠。当锁住第一个互斥量后,代码进入一个区域,这个区域需要另一个互斥量。
 
当锁住另一个互斥量后,第一个互斥量就不再需要,可以释放它了。
 
这种技巧在遍历如树形结构或者链表结构时十分有用。
 
每一个节点设置一个互斥量,而不是一个互斥量锁住整个数据结构,阻止任何并行访问。
 
遍历代码可以首先锁住队列头或者树根节点,找到期望的节点,锁住它,然后释放根节点或队列互斥量。
 
(。。。。。。原来一个队列应该这样写。。。。。。。。哈哈)
 
由于链锁是层次锁的一个特殊实例,所以如果你小心使用,两者是兼容的。当平衡或修剪树形结构时,可以使用层次锁。
 
而当搜索某个节点时,可以使用链锁。
 
仅当多个线程几乎总是活跃在层次中的不同部分时才应该使用链锁。

 

互斥量的另外一个用法:

我一直认为互斥量的存在是为了防止多个线程执行同一段代码,但是现在看来,互斥量还能够防止两端线程执行不同的代码。

void * pthread1()

{

   lock(lock1);

   .......

   unlock(lock1);

}

void * pthread2()

{

   lock(lock1);

   ......./*代码段B*/

   unlock(lock1);

}

这个能用来干什么呢?一个临界区应该有一个锁,不应该让多个临界区用一个锁。

原文地址:https://www.cnblogs.com/likeyiyy/p/3670213.html