基本点回顾

目录

  • 分段分页

  • 线程相关

分段分页

进程地址直接映射内存物理地址存在的问题(MMU)

  1. 进程之间的地址空间不隔离
  2. 内存的使用效率低
  3. 程序运行地址不稳定, 第二次加载与第一次加载地址不一致

分段:程序整体运行的虚拟地址映射到内存

  • 解决了 1 3 问题

分页: 分成大小固定的页, 一般为4k

  • 解决了2 问题

线程相关

线程的访问权限

线程私有: 局部对象, 函数参数, TLS

线程之间共享(进程所有):全局变量, 堆上的数据, 函数里的静态变量, 程序代码, 打开的文件(内核对象)

线程优先级

  • 用户指定优先级
  • 根据进入等待状态的频繁程度提升或者降低优先级
  • 长时间得不到执行被提升优先级

可抢占和不可抢占

抢占:线程在用尽时间片之后会被强制剥夺继续执行的权利,进入就绪状态

非抢占: 线程主动放弃时间片, 线程试图等待某个事件(IO)

线程安全 同步 锁

  • 二原信号量

  • 信号量, 可以标记多个资源

    线程访问资源首先获取信号量 将信号量减1, 如果信号量的值小于0, 进入等待状态,否则继续执行, 访问完资源后, 线程释放信号量--->将信号量值加1,如果信号量小于1, 唤醒一个等待中的线程

  • mutex

  • 临界区

  • 读写锁

  • 条件变量:线程可以等待和唤醒一个条件变量

mutex和二元信号量区别:
	mutex:哪个线程加锁,哪个线程解锁;
	二元信号量:系统中的任何一个线程都可以加解锁
临界区和互斥量与信号量区别:
	信号量和MUtex是内核变量,一个进程创建了互斥量和信号量,另一个进程也可以使用
	临界区作用仅仅限制在本进程
	

可重入和线程安全

  • 不使用任何静态或者全局非const变量
  • 不返回任何静态或者全局非const变量的指针
  • 仅依赖于调用方提供的参数
  • 不依赖于任何单个资源的锁()mutex等)
  • 不调用任何不可重入的函数

volilate和barrier

volatile T* pInst = 0;
T* GetInstance()
{
    if (pInst == NULL)
    {
        lock();
        if(pInst == NULL)
        {/* 不安全版本
            pInst = new T;
            */
            
            T *tmp = new T;
            barrier()
            pInst = tmp;
        }
        
        
        unlock();
    }
    return pInst;
}
  • 这段代码的问题在于cup的乱序执行
  • C++new有两个操作
    • 分配内存
    • 调用构造函数初始化
  • 所以pInst = new T 有三个操作
    1. 分配内存
    2. 在内存位置上调用构造函数
    3. 将内存地址赋值给pInst
    4. 因为2 3 位置可以颠倒,所以多线程并发不安全
  • volatile 可以做到两件事
    • 阻止编译器为了提高速度将一个变量缓存到寄存器而不写回
    • 阻止编译器调整操作volatile变量的指令顺序
  • barrier指令会组织CPU将该指令之前的指令交换到barrier之后

综上:volatile防止编译器调整顺序,barrier防止运行时优化也就是CPU动态调度换序

原文地址:https://www.cnblogs.com/sfth/p/10735348.html