zoukankan      html  css  js  c++  java
  • [多线程学习笔记]互斥量

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

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

    在学习《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);

    }

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

  • 相关阅读:
    Javascript FP-ramdajs
    微信小程序开发
    SPA for HTML5
    One Liners to Impress Your Friends
    Sass (Syntactically Awesome StyleSheets)
    iOS App Icon Template 5.0
    React Native Life Cycle and Communication
    Meteor framework
    RESTful Mongodb
    Server-sent Events
  • 原文地址:https://www.cnblogs.com/likeyiyy/p/3670213.html
Copyright © 2011-2022 走看看