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);

    }

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

  • 相关阅读:
    用gdb调试python多线程代码-记一次死锁的发现
    使用docker部署standalone cinder
    Linux上open-iscsi 的安装,配置和使用
    Windows上Ruby开发环境的配置
    在Pypi上发布自己的Python包
    docker X509 证书错误的终极解决办法
    oslo_config中的DuplicateOptError坑
    删除emacs临时文件
    xfce4快捷键设置
    设置emacs启动窗口的两种方法
  • 原文地址:https://www.cnblogs.com/likeyiyy/p/3670213.html
Copyright © 2011-2022 走看看