Linux多线程服务端编程(muduo):线程同步的四个原则

按重要性排列:

(1)首要原则是尽量最低限度地共享对象,减少需要同步的场合。一个对象能不暴露给别的线程就不要暴露;如果要暴露,优先考虑immutable对象;实在不行才暴露可以修改的对象,并用同步措施来充分保护它。

(2)其次是使用高级的并发编程机构,如TaskQueue,Producer_Consumer Queue,CountDownLatch等等

(3)最后不得已必须使用底层同步原语时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量。

(4)除了使用atomic整数之外,不自己编写lock_free代码,也不要用内核级的同步原语。不凭空猜测“哪种做法性能更好”,比如spin lock 和mutex。

其中:TaskQueue是SAE为开发者提供的分布式任务队列服务,用来以异步HTTP方式执行用户任务。

Producer_Consumer Queue就是常见的生产者消费者模型:(c++里面用的比较多)

一个线程可以往共享资源队列里放东西,另外一个可以从里面取东西。对于producer线程来说,它需要考虑的就是如果我往资源队列里放东西时,队列满了,那么我必须要等待consumer线程取走一部分元素,这样才能继续进行。另外,和其他线程访问同一个资源队列的时候,还要保证访问是线程安全的。同样,对于consumer线程来说,如果资源队列是空的,同样,该线程也必须要等待producer线程将产生的元素放入队列。

    根据这部分的讨论,我们问题的核心就在于怎么样使得一个线程和另外一个线程互斥的访问同一个资源呢?另外,如果一个线程发现目前资源情况不适合自己运行,又该怎么去停止自己而让其他的线程来运行呢? 这里,我们实际上有几种办法。

wait, notify

 如果我们希望一个线程在已经占有资源的锁的情况下先中止自己的执行并释放锁,那么就需要调用wait方法。这样,我们前面的第二个问题就得到了解答。如果我们在已经完成了自己的那部分操作,需要释放锁并要其他的线程来继续使用时呢?我们可以通过调用notify和notifyAll方法来通知其他因为资源被加锁之后处于阻塞状态的线程。

    通过这一部分的讨论,我们可以得出一个线程producer的大致流程如下:

    1. 如果资源队列满,则调用wait方法释放所,一直等待到资源队列有空缺。

    2. 在资源队列加入新的元素。

    3. 调用notify/notifyAll方法来唤醒其他等待的线程。

对于Consumer来说,我们也可以有一个类似的流程,只是它被阻塞的条件是资源队列为空,需要等待Producer产生新的元素。

CountDownLatch是一个同步工具类(java中常见),它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,如果你对这个感兴趣可以去网上搜博客看看。

std::atomic

这是c++11中的一个新的特性:

 std::atomic对int, char, bool等数据结构进行原子性封装,在多线程环境中,对std::atomic对象的访问不会造成竞争-冒险。利用std::atomic可实现数据结构的无锁设计。

std::atomic<int> foo(0);
std::atomic_flag lock = ATOMIC_FLAG_INIT;

Atomic
       C++11给我们带来的Atomic一系列原子操作类,它们提供的方法能保证具有原子性。这些方法是不可再分的,获取这些变量的值时,永远获得修改前的值或修改后的值,不会获得修改过程中的中间数值。

       这些类都禁用了拷贝构造函数,原因是原子读和原子写是2个独立原子操作,无法保证2个独立的操作加在一起仍然保证原子性

  1. std::atomic_flag

  std::atomic_flag是一个原子的布尔类型,可支持两种原子操作:

  • test_and_set, 如果atomic_flag对象被设置,则返回true; 如果atomic_flag对象未被设置,则设置之,返回false
  • clear. 清楚atomic_flag对象

  std::atomic_flag可用于多线程之间的同步操作,类似于linux中的信号量。使用atomic_flag可实现mutex.

猜你喜欢

转载自blog.csdn.net/ypshowm/article/details/89351674
今日推荐