OS实战笔记(7)-- Linux同步机制

news/2024/4/27 16:25:52/文章来源:https://blog.csdn.net/vivo01/article/details/127511983

        上一篇笔记中对x86平台上原子变量、关中断、自旋锁和信号量的原理做了复习,本笔记回顾一下Linux使用的几种常用的同步机制。

Linux上的原子变量

        Linux上提供了一个atomic_t类型表示原子变量。32位和64位版本的结构体定义如下:


typedef struct {int counter;
} atomic_t;//常用的32位的原子变量类型
#ifdef CONFIG_64BIT
typedef struct {s64 counter;
} atomic64_t;//64位的原子变量类型
#endif

        在Linux中,操作原子变量要通过专门的接口来实现(各个平台的汇编会有所不同,本笔记都是按照x86版本来看),以下列举几个最基础的:

/*** atomic_read - read atomic variable* @v: pointer of type atomic_t** Atomically reads the value of @v.*/
static __always_inline int atomic_read(const atomic_t *v)
{return READ_ONCE((v)->counter);
}/*** atomic_set - set atomic variable* @v: pointer of type atomic_t* @i: required value** Atomically sets the value of @v to @i.*/
static __always_inline void atomic_set(atomic_t *v, int i)
{WRITE_ONCE(v->counter, i);
}/*** atomic_add - add integer to atomic variable* @i: integer value to add* @v: pointer of type atomic_t** Atomically adds @i to @v.*/
static __always_inline void atomic_add(int i, atomic_t *v)
{asm volatile(LOCK_PREFIX "addl %1,%0": "+m" (v->counter): "ir" (i));
}/*** atomic_sub - subtract integer from atomic variable* @i: integer value to subtract* @v: pointer of type atomic_t** Atomically subtracts @i from @v.*/
static __always_inline void atomic_sub(int i, atomic_t *v)
{asm volatile(LOCK_PREFIX "subl %1,%0": "+m" (v->counter): "ir" (i));
}/*** atomic_inc - increment atomic variable* @v: pointer of type atomic_t** Atomically increments @v by 1.*/
static __always_inline void atomic_inc(atomic_t *v)
{asm volatile(LOCK_PREFIX "incl %0": "+m" (v->counter));
}/*** atomic_dec - decrement atomic variable* @v: pointer of type atomic_t** Atomically decrements @v by 1.*/
static __always_inline void atomic_dec(atomic_t *v)
{asm volatile(LOCK_PREFIX "decl %0": "+m" (v->counter));
}

        可以看到,核心的东西和上一节介绍的没有区别。LOCK_PREFIX在SMP系统中就是lock指令,单核系统中则为空串。

Linux下中断控制

        Linux下中断控制的基础接口如下:


//实际保存eflags寄存器
extern __always_inline unsigned long native_save_fl(void){unsigned long flags;asm volatile("# __raw_save_flags\n\t""pushf ; pop %0":"=rm"(flags)::"memory");return flags;
}
//实际恢复eflags寄存器
extern inline void native_restore_fl(unsigned long flags){asm volatile("push %0 ; popf"::"g"(flags):"memory","cc");
}
//实际关中断
static __always_inline void native_irq_disable(void){asm volatile("cli":::"memory");
}
//实际开启中断
static __always_inline void native_irq_enable(void){asm volatile("sti":::"memory");
}
//arch层关中断
static __always_inline void arch_local_irq_disable(void){native_irq_disable();
}
//arch层开启中断
static __always_inline void arch_local_irq_enable(void){ native_irq_enable();
}
//arch层保存eflags寄存器
static __always_inline unsigned long           arch_local_save_flags(void){return native_save_fl();
}
//arch层恢复eflags寄存器
static  __always_inline void arch_local_irq_restore(unsigned long flags){native_restore_fl(flags);
}
//实际保存eflags寄存器并关中断
static __always_inline unsigned long arch_local_irq_save(void){unsigned long flags = arch_local_save_flags();arch_local_irq_disable();return flags;
}
//raw层关闭开启中断宏
#define raw_local_irq_disable()     arch_local_irq_disable()
#define raw_local_irq_enable()      arch_local_irq_enable()
//raw层保存恢复eflags寄存器宏
#define raw_local_irq_save(flags)           \do {                        \typecheck(unsigned long, flags);    \flags = arch_local_irq_save();      \} while (0)#define raw_local_irq_restore(flags)            \do {                        \typecheck(unsigned long, flags);    \arch_local_irq_restore(flags);      \} while (0)#define raw_local_save_flags(flags)         \do {                        \typecheck(unsigned long, flags);    \flags = arch_local_save_flags();    \} while (0)
//通用层接口宏 
#define local_irq_enable()              \do { \raw_local_irq_enable();         \} while (0)#define local_irq_disable()             \do {                        \raw_local_irq_disable();        \} while (0)#define local_irq_save(flags)               \do {                        \raw_local_irq_save(flags);      \} while (0)#define local_irq_restore(flags)            \do {                        \raw_local_irq_restore(flags);       \} while (0)

Linux自旋锁

typedef struct raw_spinlock {arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAKunsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCKunsigned int magic, owner_cpu;void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct lockdep_map dep_map;
#endif
} raw_spinlock_t;typedef struct spinlock {union {struct raw_spinlock rlock;#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))struct {u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};
#endif};
} spinlock_t;

        忽略掉调试相关的代码,结构体最重要的是arch_spinlock_t,可以看出各个体系架构下会有所不同。

加锁操作


#define raw_spin_lock(lock) _raw_spin_lock(lock)static inline void spin_lock(spinlock_t *lock)
{raw_spin_lock(&lock->rlock);
}

        spinlock分为SMP版本和UP版本(include/linux/spinlock_api_smp.h ,include/linux/spinlock_api_up.h),以SMP版本为例来分析。SMP版本中,_raw_spin_lock为声明为:

void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)        __acquires(lock);static inline void __raw_spin_lock(raw_spinlock_t *lock)
{// 禁止抢占preempt_disable();// for debugspin_acquire(&lock->dep_map, 0, 0, _RET_IP_);// real work done hereLOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

        do_raw_spin_trylock和do_raw_spin_lock相关代码是:

void do_raw_spin_lock(raw_spinlock_t *lock)
{debug_spin_lock_before(lock);arch_spin_lock(&lock->raw_lock);debug_spin_lock_after(lock);
}int do_raw_spin_trylock(raw_spinlock_t *lock)
{int ret = arch_spin_trylock(&lock->raw_lock);if (ret)debug_spin_lock_after(lock);
#ifndef CONFIG_SMP/** Must not happen on UP:*/SPIN_BUG_ON(!ret, lock, "trylock failure on UP");
#endifreturn ret;
}

        可以看到,主要是调用arch_spin_trylock和arch_spin_lock,从名字看就知道它们依赖于具体的体系结构,x86平台对应的是(每个体系架构下应该会有一个spinlock.h文件表明它所使用的spinlock函数,可以看这个文件来追踪、、)queued_spin_trylock和queued_spin_lock(kernel 4.14)。

static __always_inline int queued_spin_trylock(struct qspinlock *lock)
{if (!atomic_read(&lock->val) &&(atomic_cmpxchg_acquire(&lock->val, 0, _Q_LOCKED_VAL) == 0))return 1;return 0;
}static __always_inline void queued_spin_lock(struct qspinlock *lock)
{u32 val;val = atomic_cmpxchg_acquire(&lock->val, 0, _Q_LOCKED_VAL);if (likely(val == 0))return;queued_spin_lock_slowpath(lock, val);
}

        注:include/asm-generic/qspinlock.h里将arch_spin_trylock和arch_spin_lock定义成了queued_spin_trylock和queued_spin_lock。

        其中atomic_cmpxchg_accquire对应atomic_cmpxchg,最终调用__raw_cmpxchg,核心就是使用了x86的cmpxchgb、cmpxchgw、cmpxchgl、cmpxchgq指令。

        想要深入研究指令的可以百度,这里我们主要关注atomic_cmpxchg本身的功能,这个函数的原型是:

 static __always_inline int atomic_cmpxchg(atomic_t *v, int old, int new);

           它的功能是原子实现比较和交换过程,对比old和v的值,如果相等,则将new存储到v中,返回旧值;如果不等,返回v的值。

        因此queued_spin_trylock的主要工作就是对比一下lock当前值是否为0(未加锁),如果是则加锁后返回;如果没有成功则直接返回。

        queued_spin_lock则首先尝试用trylock方式加锁,如果失败则进入slowpath方式加锁。这个slowpath里会循环等待锁释放后再次进行加锁操作(kernel/locking/qspinlock.c)。

        如果还想要深入了解queued spinlock细节,可以参考这篇文章:

Linux内核同步机制之(九):Queued spinlock前言 本站之前已经有了一篇关于icon-default.png?t=M85Bhttp://www.wowotech.net/kernel_synchronization/queued_spinlock.html        linux下spinlock的发展也是由简单变复杂的,想要进一步了解的,可以参考这篇文章

PV qspinlock原理_小写的毛毛的博客-CSDN博客_spinlock实现原理1 前言自旋锁(spinlock)是用来在多处理器环境中工作的一种锁。如果内核控制路径发现spinlock是unlock,就获取锁并继续执行;相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径lock,就在周围“旋转”,反复执行一条紧凑的循环指令,直到锁被释放。spinlock的循环指令表示“忙等”:即使等待的内核控制路径无事可做(除了浪费时间),它也在CPU上保持运行。spinlock的实现依赖这样一个假设:锁的持有线程和等待线程都不能被抢占。但是在虚拟化场景下,vCPU可能在任意时刻https://blog.csdn.net/bemind1/article/details/118224344

Linux信号量

        Linux下信号量的数据结构定义如下:


struct semaphore{raw_spinlock_t lock;//保护信号量自身的自旋锁unsigned int count;//信号量值struct list_head wait_list;//挂载睡眠等待进程的链表
};

        对信号量操作的核心接口是down和up,主要代码如下:


static inline int __sched __down_common(struct semaphore *sem, long state,long timeout)
{struct semaphore_waiter waiter;//把waiter加入sem->wait_list的头部list_add_tail(&waiter.list, &sem->wait_list);waiter.task = current;//current表示当前进程,即调用该函数的进程waiter.up = false;for (;;) {if (signal_pending_state(state, current))goto interrupted;if (unlikely(timeout <= 0))goto timed_out;__set_current_state(state);//设置当前进程的状态,进程睡眠,即先前__down函数中传入的TASK_UNINTERRUPTIBLE:该状态是等待资源有效时唤醒(比如等待键盘输入、socket连接、信号(signal)等等),但不可以被中断唤醒raw_spin_unlock_irq(&sem->lock);//释放在down函数中加的锁timeout = schedule_timeout(timeout);//真正进入睡眠raw_spin_lock_irq(&sem->lock);//进程下次运行会回到这里,所以要加锁if (waiter.up)return 0;}timed_out:list_del(&waiter.list);return -ETIME;interrupted:list_del(&waiter.list);return -EINTR;//为了简单起见处理进程信号(signal)和超时的逻辑代码我已经删除
}
//进入睡眠等待
static noinline void __sched __down(struct semaphore *sem)
{__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
//获取信号量
void down(struct semaphore *sem)
{unsigned long flags;//对信号量本身加锁并关中断,也许另一段代码也在操作该信号量raw_spin_lock_irqsave(&sem->lock, flags);if (likely(sem->count > 0))sem->count--;//如果信号量值大于0,则对其减1else__down(sem);//否则让当前进程进入睡眠raw_spin_unlock_irqrestore(&sem->lock, flags);
}
//实际唤醒进程 
static noinline void __sched __up(struct semaphore *sem)
{struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list);//获取信号量等待链表中的第一个数据结构semaphore_waiter,它里面保存着睡眠进程的指针list_del(&waiter->list);waiter->up = true;wake_up_process(waiter->task);//唤醒进程重新加入调度队列
}
//释放信号量
void up(struct semaphore *sem)
{unsigned long flags;//对信号量本身加锁并关中断,必须另一段代码也在操作该信号量raw_spin_lock_irqsave(&sem->lock, flags);if (likely(list_empty(&sem->wait_list)))sem->count++;//如果信号量等待链表中为空,则对信号量值加1else__up(sem);//否则执行唤醒进程相关的操作raw_spin_unlock_irqrestore(&sem->lock, flags);
}

        信号量的代码本身流程逻辑不算复杂,需要注意的是schedule_timeout的下一条语句是进程被唤醒后回来执行的地方。

Linux读写锁

        在实际场景中,可能会碰到这种情况:有一个复杂的结构体变量作为全局的管理信息变量,这个变量访问的特点是初始化之后,很少会去进行改动,绝大部分情况下都是多个线程进行读取这个结构中不同的成员。这种情况下,如果我们还是使用普通的自旋锁或信号量,效率是非常低的。实际上如果有多个线程都只是读这个结构,根本没有必要去加锁,只有要修改的时候才需要确保修改过程不会被打断。

        对于此类读数据频率远大于写数据频率的场景,Linux提供了读写锁。读写锁也叫做共享-独占锁(shared-exclusive)。当读者进行加锁时,是以共享模式上锁;当写者进行加锁时,是以独占模式上锁。

        读写是互斥的,读的时候不能写,写的时候不能读,但允许同时有多个读者读。

        1. 当没有加锁时,读取的加锁操作和写入的加锁操作都可以满足

        2. 当持有读锁时,所有读者请求的加锁操作都能满足,写者的加锁请求不能满足

        3. 当持有写锁时,所有的读者的加锁操作都不能满足,所有的写者的加锁操作也不能满足,读与写之间是互斥的,写与写之间也是互斥的。

        Linux下读写锁可以看做是自旋锁的变种。读写锁内部实现上核心代码如下:


//读写锁初始化锁值
#define RW_LOCK_BIAS     0x01000000
//读写锁的底层数据结构
typedef struct{unsigned int lock;
}arch_rwlock_t;
//释放读锁 
static inline void arch_read_unlock(arch_rwlock_t*rw){ asm volatile(LOCK_PREFIX"incl %0" //原子对lock加1:"+m"(rw->lock)::"memory");
}
//释放写锁
static inline void arch_write_unlock(arch_rwlock_t*rw){asm volatile(LOCK_PREFIX"addl %1, %0"//原子对lock加上RW_LOCK_BIAS:"+m"(rw->lock):"i"(RW_LOCK_BIAS):"memory");
}
//获取写锁失败时调用
ENTRY(__write_lock_failed)//(%eax)表示由eax指向的内存空间是调用者传进来的 2:LOCK_PREFIX addl  $ RW_LOCK_BIAS,(%eax)1:rep;nop//空指令cmpl $RW_LOCK_BIAS,(%eax)//不等于初始值则循环比较,相等则表示有进程释放了写锁jne   1b//执行加写锁LOCK_PREFIX subl  $ RW_LOCK_BIAS,(%eax)jnz 2b //不为0则继续测试,为0则表示加写锁成功ret //返回
ENDPROC(__write_lock_failed)
//获取读锁失败时调用
ENTRY(__read_lock_failed)//(%eax)表示由eax指向的内存空间是调用者传进来的 2:LOCK_PREFIX incl(%eax)//原子加11:  rep; nop//空指令cmpl  $1,(%eax) //和1比较 小于0则js 1b //为负则继续循环比较LOCK_PREFIX decl(%eax) //加读锁js  2b  //为负则继续加1并比较,否则返回ret //返回
ENDPROC(__read_lock_failed)
//获取读锁
static inline void arch_read_lock(arch_rwlock_t*rw){asm volatile(LOCK_PREFIX" subl $1,(%0)\n\t"//原子对lock减1"jns 1f\n"//不为小于0则跳转标号1处,表示获取读锁成功"call __read_lock_failed\n\t"//调用__read_lock_failed"1:\n"::LOCK_PTR_REG(rw):"memory");
}
//获取写锁
static inline void arch_write_lock(arch_rwlock_t*rw){asm volatile(LOCK_PREFIX"subl %1,(%0)\n\t"//原子对lock减去RW_LOCK_BIAS"jz 1f\n"//为0则跳转标号1处"call __write_lock_failed\n\t"//调用__write_lock_failed"1:\n"::LOCK_PTR_REG(rw),"i"(RW_LOCK_BIAS):"memory");
}

       总结一下这段代码的要点:

        1. 计数器的初值为RW_LOCK_BIAS

        2. 读者加锁计数器减1

        3. 写者加锁计数器减去RW_LOCK_BIAS

        注意,实际使用时要使用Linux标准接口如read_lock,write_lock等,而不是arch开头的接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_218701.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文件描述符0,1,2+lseek()+共享文件覆盖解决

1.文件描述符0&#xff0c;1&#xff0c;2 程序开始运行时&#xff0c;有三个文件被自动打开&#xff0c;打开时分别使用了这三个文件描述符依次打开的三个文件分别是 /dev/stdin&#xff0c;/dev/stdout&#xff0c;/dev/stderr /dev/stdin 标准输入文件 程序开始运行时&…

requests模块和openpyxl模块

第三方模块的下载和使用 1,第三方模块就是别人大神们已经写好的模块,功能特别强大。我们如果像使用第三方模块就先要进行下载。下载完成后 才可以在python中直接调用2.下载方式一:pip工具pip工具注意每个解释器都有pip工具 如果我们的电脑上有多个版本的解释器那么我们在使用…

模型交易平台--信用卡客户消费行为分析

业务问题&#xff1a; 据《中国银行卡产业发展蓝皮书&#xff08;2021&#xff09;》显示&#xff0c;截至2020年末&#xff0c;中国信用卡累计发卡量为11.3亿张&#xff0c;其中6个月内有使用记录的活卡为7.4亿张&#xff0c;有近4亿张信用卡处于“睡眠”状态。 睡眠信用卡…

【JUC】12.读写锁与StampedLock[完结]

文章目录1. 什么是读写锁2. 锁的演化历程3. 锁降级4. 锁降级的策略5. StampedLock简介6. StampedLock的特点7. StampedLock之传统读写8. StampedLock之乐观锁9. StampedLock缺点1. 什么是读写锁 读写锁是指ReentrantReadWriteLock类 该类能被多个读线程访问或者一个写线程访问…

第二节:数据类型与变量【java】

目录 &#x1f4c3;前言 &#x1f4d7;1.数据类型 &#x1f4d5;2. 变量 2.1 变量概念 2.2 语法格式 &#x1f4d9;3.整型变量 3.1 整型变量 3.2 长整型变量 3.3 短整型变量 3.4 字节型变量 &#x1f4d8;4.浮点型变量 4.1 双精度浮点型 4.2 单精度浮点型 &#…

拓端tecdat|用Python进行图像模糊处理和特征提取

全文链接&#xff1a;http://tecdat.cn/?p9015 原文出处&#xff1a;拓端数据部落公众号 在本文中&#xff0c;我将带您了解图像处理的一些基本功能。特征提取。但是这里我们需要更深入的数据清理。但是数据清理是在数据集&#xff0c;表格&#xff0c;文本等上完成的。如何在…

vue使用canvas实现手写电子签名;vue使用vue-esign插件实现手写电子签名;H5使用画布签名

功能&#xff1a; 1.兼容 PC 和 Mobile&#xff1b; 2.画布自适应屏幕大小变化&#xff08;窗口缩放、屏幕旋转时画布无需重置&#xff0c;自动校正坐标偏移&#xff09;&#xff1b; 3.自定义画布尺寸&#xff08;导出图尺寸&#xff09;&#xff0c;画笔粗细、颜色&#xff0…

Arduino UNO 可视化GT-24工业级无线透传

Arduino UNO 可视化GT-24工业级无线透传一、前言二、硬件要求三、参数基础四、原理剖析五、透传思路六、程序概要七、arduino使用接线八、成果展示一、前言 无线透传市面上较为常见的是基于蓝牙、esp的多种透传模块&#xff0c;今天介绍的则是用NRF24L01芯片构成的电路。&…

Python第七章作业

实例一: class Geese: 大雁类 def __init__(self,beak,wing,claw): print("我是大雁类!我有以下特征:") print(beak) print(wing) print(claw) def fly(self,state): print(state)**********调用方法**********beak_…

MyBatis中的reflection包(一)ObjectFactory,PropertyTokenizer, Invoker, Reflector

内容概要 reflection是MyBatis关于反射的工具包&#xff0c;是实现其它功能的基石之一。这里我不准备贴上源码然而逐行解释&#xff0c;而是从需求分析的角度来复现。 ObjectFactory 现在有这样的需求&#xff1a;给你一个Class对象&#xff0c;要求你创建它的实例&#xff…

Kong自动注册kong-spring-boot-stater

前言 kong-spring-boot-stater框架是为了解决SpringBoot项目和kong网关的自动注册&#xff0c;虽然Kong网关有提供可视化管理后台的操作界面&#xff0c;但是在多服务、多环境的时候在管理后台挨个配置每个服务节点是比较麻烦的&#xff0c;所以这也是kong-spring-boot-stater…

图形写稿基础,含teaser figure的特殊排版方法

写在前面&#xff1a;这是第一次投稿后针对论文写作部分的总结。需要注意的是&#xff1a;老师提了意见&#xff0c;一定要快速改&#xff0c;否则会很恼人。 1. 图片展示 构图要美观&#xff0c;保证横平竖直&#xff1b;图片中文字保证和文章正文中文字一样大小&#xff1b;…

VUE |“ 登录页面”的权限以及接口问题

目录 一、功能需求 二、前提准备 三、具体实现 一、功能需求 今天写到项目的登录页面&#xff0c;我这边是没有后台数据接口的&#xff0c;所以我们用了Mock模拟了一个假的数据&#xff0c;那么我们应该怎么实现呢&#xff1f;我们先来看一下功能需要。 当我们退出登录…

系分 - 系统可靠性分析与设计

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - 系统可靠性分析与设计 考点摘要 可靠性相关基本概念&#xff08;★&#xff09;系统可靠性分析&#xff08;★★&#xff09;软件可靠性设计&#xff08;★★&#xff09; 系统故障类型 系统故障是指由…

09代码

实例1 def division():print(\n==========分苹果了===========\n)apple=int(input(请输入苹果的个数))children=int(input(请输入来了多少个小朋友))result=apple//childrenremain=apple-result*childrenif remain>0:print(apple,个苹果,平均分给,children,个小朋友,每人分…

网络爬虫及openyxl模块

网络爬虫及openyxl模块 一、第三方模块简介 1.第三方模块的用处python之所以在这么多的编程语言中脱颖而出的优点是有众多的第三方库函数,可以更高效率的实现开发2.第三方模块的使用 1.第三方模块必须下载才能使用格式:pip install 模块名 -i 源地址清华大学 :https://pypi.…

【cuda编程】CUDA的运行方式以及grid、block结构关系

文章目录1. CUDA基础知识1.1 程序基本运行顺序1.2 grid与block1.3 dim类型定义2. CUDA的第一个程序3. CUDA线程的组织结构——grid与block关系1. CUDA基础知识 1.1 程序基本运行顺序 一般来说&#xff0c;一个cpugpu的程序运行如下所示&#xff1a; 1.2 grid与block 从GPU至…

网络原理——No.4 传输层_TCP协议中的延迟应答, 捎带应答, 面向字节流与TCP的异常处理

JavaEE传送门JavaEE 网络原理——No.2 传输层_TCP的连接管理 网络原理——No.3 传输层_TCP的滑动窗口, 流量控制与拥塞控制 目录延迟应答捎带应答面向字节流粘包问题TCP 中的异常处理(连接异常)TCP 和 UDP 的应用场景延迟应答 一种提高传输效率的机制, 又是基于流量控制, 来引…

调度线程池ScheduledThreadPoolExecutor源码解析

实现机制分析 我们先思考下&#xff0c;如果让大家去实现ScheduledThreadPoolExecutor可以周期性执行任务的功能&#xff0c;需要考虑哪些方面呢&#xff1f; ScheduledThreadPoolExecutor的整体实现思路是什么呢&#xff1f; 答&#xff1a; 我们是不是可以继承线程池类&am…

docker快速安装redis

一.背景 开发环境中&#xff0c;经常需要redis本地环境&#xff0c;方便开发。准备在本机的虚拟机里面准备一个redis环境。 二.版本信息 操作系统&#xff1a;Windows 10 家庭版 Oracle VM VirtualBox&#xff1a;版本 6.0.10 r132072 (Qt5.6.2) Ubuntu:16.04.6-desktop-a…