专栏首页跟Qt君学编程翻译 | 可重入与线程安全

翻译 | 可重入与线程安全

❝Qt君今天在Qt帮助文档中看到一篇不错的文章,翻译分享给大家。❞

  在整个文档中,术语:「可重入和线程安全」用于标记类和函数,以表示它们如何在多线程应用程序中使用:

  • 「即使在调用使用共享数据时,也可以从多个线程同时调用线程安全的函数,因为对共享数据的所有引用都是序列化的」
  • 「也可以从多个线程同时调用可重入函数,但前提是每次调用都使用自己的数据」

「因此,线程安全的函数总是可重入的,但可重入的函数并不总是线程安全的」

  引申开来,如果一个类的成员函数可以从多个线程安全地调用,则称该类是可重入的,只要每个线程使用该类的不同实例。如果可以从多个线程安全地调用该类的成员函数,即使所有线程使用该类的同一实例,该类也是线程安全的。

「注意」:Qt类只有在被多个线程使用时才会被记录为线程安全的。如果函数未标记为线程安全或可重入,则不应从不同的线程使用它。如果一个类没有标记为线程安全或可重入,则不应该从不同的线程来访问该类的特定实例。

可重入

  C++类通常是可重入的,因为它们只访问自己的成员数据。任何线程都可以在可重入类的实例上调用成员函数,只要没有其他线程可以同时在该类的同一实例上调用成员函数。例如,下面的Counter类是可重入的:

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { ++n; }
    void decrement() { --n; }
    int value() const { return n; }

private:
    int n;
};

  但是,这个类不是线程安全的,因为如果多个线程试图修改数据成员n,结果是未定义的。这是因为++n--n运算符并不总是原子性的。实际上,它们通常会扩展到这三个机器指令:

  1. 在寄存器中加载变量的值。
  2. 寄存器值的递增或递减。
  3. 将寄存器的值存储回主内存中。

  如果线程A和线程B同时加载变量的旧值,增加它们的寄存器,并将其存储回去,它们最终会相互覆盖,造成的后果是变量n只增加一次!

线程安全

  显然,访问必须是序列化的:线程A必须执行上述步骤123中的原子性不中断,然后线程B才能执行相同的步骤,反之亦然。要想某个类线程安全的简单方法是用一个QMutex来保护所有对数据成员的访问:

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { QMutexLocker locker(&mutex); ++n; }
    void decrement() { QMutexLocker locker(&mutex); --n; }
    int value() const { QMutexLocker locker(&mutex); return n; }

private:
    mutable QMutex mutex;
    int n;
};

  QMutexLocker类在其构造函数中自动锁定互斥锁,并在调用析构函数时在函数结束时解锁它。锁定互斥锁可以确保来自不同线程的访问将被序列化。互斥锁数据成员使用可变限定符声明的,因为我们需要在value()中锁定和解锁互斥锁,同时它还是一个const修饰的函数。

关于Qt类的注释

「许多Qt类是可重入的,但它们不是线程安全的,因为使它们成为线程安全会导致重复锁定和解锁一个QMutex的额外开销」。例如,QString是可重入的,但不是线程安全的。您可以同时从多个线程安全地访问不同的QString实例,但是不能同时从多个线程安全地访问相同的QString实例(除非您使用QMutex保护自己的访问)。

  一些Qt类和函数是线程安全的。这些类主要是与线程相关的类(如QMutex)和基本函数(如QCoreApplication::postEvent())。

「注意」:多线程领域的术语并不是完全标准化的。POSIX使用可重入和线程安全的定义,这与它的C语言API有些不同。在Qt中使用其他面向对象的C++类库时,请确保理解这些定义。

本文分享自微信公众号 - Qt君(qtjuna)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • QThread类

     QThread类提供一种独立于平台的线程管理方式。     一个QThread实例管理程序中的一个线程。QThread的执行开始于run()。默认情况下,ru...

    Qt君
  • 翻译 | 您没有做错(线程)

      这篇文章是关于QThread的使用的。这是对我当时的同事Brad三年前的博客帖子的回答:“您做错了”。

    Qt君
  • 函数禁用delete与替代函数体定义default

      在函数声明后加入=delete即可将该函数标记,一旦被调用则会导致编译错误。   例如:

    Qt君
  • Java中的锁原理、锁优化、CAS、AQS

    结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

    Java团长
  • JVM-concurrent-HashSet-problem Java 并发问题

    上午刚到公司,准备开始一天的摸鱼之旅时突然收到了一封监控中心的邮件。 心中暗道不好,因为监控系统从来不会告诉我应用完美无 bug,其实系统挺猥琐。 打开邮件...

    爱明依
  • ReentrantLock源码解析

    在java编程中,经常需要多代码进行加锁来防止多线程可能引起的数据不一致。而锁的类型有公平锁和非公平锁。公平锁的意义就是按照顺序,而非公平锁则是相反的。也就是说...

    用户5602455
  • Java并发之线程

         在前面我们介绍的一些内容中,我们的程序都是一条执行流,一步一步的执行。但其实这种程序对我们计算机的资源的使用上是低效的。例如:我们有一个用于计算的程序...

    Single
  • 微服务转型,雪崩效应是绕不过的一道坎

    记得在三年前公司因为业务发展需要,就曾经将单体应用迁移到分布式框架上来。当时就遇到了这样一个问题:系统仅有一个控制单元,它会调用多个运算单元,如果某个运算单元(...

    yuanyi928
  • “面试不败计划”:java工程师面试常问的多线程问题【推荐】

    好好学java
  • 面试总结-Java高级篇

    java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。

    Java搬砖工人

扫码关注云+社区

领取腾讯云代金券