zoukankan      html  css  js  c++  java
  • 【UNIX环境高级编程】线程同步

      当多个线程共享相同的内存时,需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题。同样,如果变量是只读的也不会有一致性问题。但是,当一个线程可以修改变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访到无效的值。

    互斥量

      可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前 线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变为可运行状态,第一变为可运行状态的线程就可以对互斥量加锁,其他线程就会看到互斥量亦然是锁着的,只能回去再次等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。

    读写锁

      读写锁(read-write lock)与互斥量类似,不过读写锁允许更高程度的并行性。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其进行加锁。读写锁有3种状态:读模式下加锁,写模式下加锁,不加锁。一次只有一个线程可以占用写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。

      当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此进行加锁的线程都会阻塞,只有所有的线程释放它们的读锁为止。虽然各种操作系统对读写锁的实现各不相同,但当读写锁处于读模式锁住的状态,而这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。这样就可以避免读模式长期占用,而等待的写模式锁请求一直得不到满足。

      读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因此一次只有一个线程可以在写模式下拥有这个锁。当读写锁在读模式下时,只要线程先获取了读模式下的锁,该锁保护的数据结构就可以被多个获得读模式锁的线程读取。

    条件变量

       条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会和的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁住之后才能计算条件。

       下面是用条件变量解决生产者-消费者问题:

    #include<stdio.h>
    #include<pthread.h>
    #define MAX 10000
    pthread_mutex_t the_mutex;
    pthread_cond_t condc, condp;
    
    int buffer = 0;
    
    void *Producer(void *ptr) {
        int i;
    
        for (int i = 1; i <= MAX; i++) {
            pthread_mutex_lock(&the_mutex);
            while (buffer != 0) pthread_cond_wait(&condp, &the_mutex);
            buffer = i;
            pthread_cond_signal(&condc);
            pthread_mutex_unlock(&the_mutex);
        }
        pthread_exit(0);
    }
    
    void *Consumer(void *ptr) {
        int i;
        for (int i =1; i < MAX; i++) {
            pthread_mutex_lock(&the_mutex);
            while (buffer == 0) pthread_cond_wait(&condc, &the_mutex);
            buffer = 0;
            pthread_cond_signal(&condp);
            pthread_mutex_unlock(&the_mutex);
        }
        pthread_exit(0);
    }
    
    int main(int argc, char **argv)
    {
        pthread_t pro, con;
        pthread_mutex_init(&the_mutex, 0);
        pthread_cond_init(&condc, 0);
        pthread_cond_init(&condc, 0);
        pthread_create(&con, 0, Consumer, 0);
        pthread_create(&pro, 0, Producer, 0);
        pthread_join(pro, 0);
        pthread_join(con, 0);
        pthread_cond_destroy(&condc);
        pthread_cond_destroy(&condp);
        pthread_mutex_destroy(&the_mutex);
    
    }

    自旋锁

      自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)状态。自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。

      自旋锁通常作为底层原语用于实现其他类型的锁。根据它们所基于的系统体系结构,可以通过使用测试并设置指令有效地实现。当然这里说的有效也还是会导致CPU资源的浪费:当线程自旋等待锁变为可用时,CPU不能做其他的事情。这也是自旋锁只能够被位置一小段时间的原因。

      自旋锁用在非抢占式内核中时是非常有用的:除了提供互斥机制以外,它们会阻塞中断,这样中断处理程序就不会让系统陷入死锁状态,因为它需要获取已被加锁的自旋锁(把中断想成另一种抢占)。在这种类型的内核中,中断处理程序不能休眠,因为它们能用的同步原语只能是自旋锁。

    屏障

      屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。我们已经看到一种屏障,pthread_join函数是一种屏障,允许一个线程等待,直到另一个线程退出。

      但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程处理完工作,而线程不需要退出。所有线程到达屏障后可以直接工作。

    互斥锁和自旋锁的区别:

      自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。

      互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。

      两种锁适用于不同场景:

      如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。

      如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。

      如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自 身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价 很高。

      如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。

    参考资料:

      1. 《UNIX环境高级编程》

      2. 《现代操作系统》

      3. http://blog.csdn.net/swordmanwk/article/details/6819457

  • 相关阅读:
    不高级不能发帖的WPS论坛
    打不开盖子的酸奶
    无意中发现的一个好设计:不浸水的香皂盒
    几件小事
    解决ios微信页面回退不刷新
    require.js
    前端遇到的坑
    gulp详细入门教程
    js 获取当前日期
    模仿微信朋友圈 图片浏览 h5 html5 js
  • 原文地址:https://www.cnblogs.com/vincently/p/4749359.html
Copyright © 2011-2022 走看看