• 对线程安全, 可重入函数, 异步安全的理解


    1. 可重入与异步安全

    1.1 可重入
    可重入函数, 也可以称为是异步信号安全的(async-signal safe), 两者是同一个概念. 可重入函数必定是线程安全的, 而线程安全的函数却不一定可重入.
    因为

    只有当线程安全函数也可能被信号处理程序调用, 如果信号处理程序的调用也是安全的, 此时, 才能说函数是异步信号安全的(可重入).

    可重入 = 异步安全 = 线程安全 + 信号处理函数调用安全

    1.2 不可重入
    相反, 如果一个函数不是可重入的, 也就是另外的线程或信号处理程序同时调用函数, 是不安全的(可能导致脏数据, 数据不一致, 未定义行为等), 就称函数是不可重入的.

    常见的不可重入原因:

    1. 使用静态数据结构;
    2. 调用malloc/free;
    3. 标准I/O函数, 标准I/O库很多实现都以不可重入方式使用全局数据结构;

    比如库函数printf就是不可重入的, 因为printf自带库缓存, 信号处理程序可能中断主程序printf调用, 然后在信号处理程序中调用printf. 这样, 库缓存中的数据完整性可能会被破坏, 也就无法保证产生期望的结果.

    如果一个线程安全函数使用了静态数据结构, 那么如何保证信号处理程序对静态数据结构的使用是安全的呢?
    比如errno, 每个线程都会维护一个副本, 信号处理程序也可能会访问, 如果信号处理程序可能会修改errno值, 但显然会导致对应线程errno也修改, 产生不安全行为, 因为中断处可能另外一个函数正在访问errno值.
    可以这样做, 避免不安全行为:

    1. 进入信号处理程序时, 保存errno值;
    2. 退出信号前, 恢复errno值;

    如果使用了锁的线程安全函数, 调用了不可重入的函数如I/O库函数printf, 信号处理程序如果也要调用printf, 如何处理?
    如果直接在信号处理程序调用该上锁的线程安全函数, 很可能会导致死锁, 因为信号处理程序中断了函数的执行, 但函数未释放锁, 信号处理程序会阻塞等待锁, 无法正常返回中断处.
    当然可以使用递归锁来解决同一线程重复上锁导致的死锁问题, 但是无法保证printf库缓存不被破坏, 产生预期一致输出.
    通常的解决办法是, 在特定区域屏蔽信号响应.
    阻塞/屏蔽SIGINT信号, 以屏蔽由Ctrl + C产生的SIGINT为例, 屏蔽信号方法:

    1. 调用signal()设置, 屏蔽SIGINT信号处理

    2. 通过sigprocmask()修改进程的signal mask (单线程)

    3. 通过pthread_sigmask()修改线程的signal mask(多线程)


    2. 线程安全

    什么是线程安全?
    APUE 10.6提到,

    如果一个函数在相同的时间点可以被多个线程安全地调用, 就称该函数是线程安全的.

    什么样的函数是线程安全的?
    1)没有任何全局/静态数据结构等外部变量的访问, 这种函数通常也是可重入的;
    2)有访问了全局/静态数据结构等共享资源, 但是对其访问前进行了加锁, 访问后释放锁, 通常有互斥锁, 读写锁, 文件锁等, 确保同一时间点, 不会有2个线程同时访问资源.

    参考

    对于可重入、线程安全、异步信号安全几个概念的理解 | CSDN

  • 相关阅读:
    【转】c#基础系列1---深入理解值类型和引用类型
    【转】Aspnet Core为什么支持跨平台
    [翻译svg教程]svg学习系列 开篇
    使用docker 解决一个小问题,你也可能用的到
    增加软链接
    漫长的表白
    被社会抽了一巴掌
    杂乱五章的2015年终总结
    [资源分享]yslow 与firebug 修复版本Firefox35【绿色版本下载】
    Web前端性能测试-性能测试知多少---深入分析前端站点的性能
  • 原文地址:https://www.cnblogs.com/fortunely/p/14791451.html
Copyright © 2020-2023  润新知