Linux第77步_处理Linux并发的相关函数

了解linux中的“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体,以及相关函数。

并发就是多个“用户”同时访问同一个共享资源。如:多个线程同时要求读写同一个EEPROM芯片,这个EEPROM就是共享资源,为了保证读写的正确性,其它线程必须等待“持有者”释放使用权限,才可以使用该EEPROM。并发带来的问题就是竞争。Linux采用“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体的函数来解决并发与竞争。

1、Linux系统并发产生的原因:

1)、多线程并发访问。

2)、抢占式并发访问,从2.6版本内核开始,Linux内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。

3)、中断程序并发访问。

4)、SMP(多核)核间并发访问。

注意:SOC称为系统级芯片,也有称片上系统。

如果线程A和线程B修改同一个存储单元,如果没有竞争,就会按照下面的流程执行,都会得到正确的结果。

线程A:

ldr r0, =0X30000000    /* r0=0X30000000  */

ldr r1, =10            /* r1=10  */

str r1, [r0]           /*将地址0X30000000的存储单元设置为10*/

线程B:

ldr r0, =0X30000000    /* r0=0X30000000  */

ldr r1, =20            /* r1=20  */

str r1, [r0]           /*将地址0X30000000的存储单元设置为20*/

如果线程A和线程B发生竞争,地址0X30000000的存储单元设置的值可能是错误的。

2、原子操作:

原子操作是指不能再进行分割的操作。主要指整型变量操作或者位变量操作。

1)、“原子整形数据”操作:

①、在“include/linux/types.h”文件中,atomic_t的结构体如下

如果是32位的系统级芯片,Linux内核定义的32位原子结构体如下:

typedef struct {

  int counter;

}atomic_t;

如果是64位的系统级芯片,Linux内核也定义了64位原子结构体如下:

typedef struct {

s64 counter;

} atomic64_t;

②、“原子整形数据”操作函数:

ATOMIC INIT(int i)

定义原子变量的时候对其初始化;

int atomic read(atomic_t *v)

读取v->counter的值,并且返回;

void atomic_set(atomic_t *v, int i)

向v->counter写入i值;

void atomic_add(int i. atomic_t *v)

给v->counter加上i值;

void atomic_sub(int i, atomic_t *v)

从v->counter减去i值;

void atomic_inc(atomic_t *v)

给v->counter加1,也就是自增;

void atomic_dec(atomic_t *v)

从v->counter减1,也就是自减;

int atomic_dec_return(atomic_t *v)

从v->counter减1,并且返回v的值;

int atomic_inc_return(atomic_t *v)

给v->counter加1,并且返回v的值;

int atomic_sub_and_test(int i.atomic_t *v)

从v->counter减i,如果结果为0就返回真,否则返回假;

int atomic_dec_and_test(atomic_t *v)

从v->counter减1,如果结果为0就返回真,否则返回假;

int atomic_inc_and_test(atomic_t *v)

给v->counter加1,如果结果为0就返回真,否则返回假;

int atomic_add_negative(int i, atomic_t *v)

给v->counter加i,如果结果为负就返回真,否则返回假;

举例:

atomic_t v = ATOMIC_INIT(0); /* 定义原子变量v,并初始化原子变零v=0 */

atomic_set(&v, 10);         /* 设置v->counter=10 */

atomic_read(&v);            /* 读取v->counter的值,肯定是10 */

atomic_inc(&v);             /* v->counter的值加1,v->counter=11 */

atomic_t Mylock;                   /* 原子变量 */

Mylock = (atomic_t)ATOMIC_INIT(0); //初始化原子变量

atomic_set(&Mylock, 1);            //原子变量初始值为Mylock->counter=1

if (!atomic_dec_and_test(&Mylock)) {

//当Mylock->counter=1时,atomic_dec_and_test()返回1

//从Mylock->counter减1,如果结果为0就返回1,否则返回0;

 atomic_inc(&Mylock);

 return -EBUSY; /* Mylock被使用,返回忙 */

}

2)、“原子位数据”操作:

需要包含“#include <bitops.h>

void set_bit(int nr, void *p)

将p地址的第nr位置1;

void clear_bit(int nr,void *p)

将p地址的第nr位清零;

void change_bit(int nr, void *p)

将p地址的第nr位进行翻转;

int test_bit(int nr, void *p)

获取p地址的第nr位的值;

int test_and_set_bit(int nr, void *p)

将p地址的第nr位置 1,并且返回第nr位原来的值;

int test_and_clear_bit(int nr, void *p)

将p地址的第nr位清零,并且返回第nr位原来的值;

int test_and_change_bit(int nr, void *p)

将p地址的第nr位翻转,并且返回第nr位原来的值;

举例:

#include <bitops.h>

unsigned long     mig_status;

#define NFS_MIG_FAILED          (2)

int ret;

if ( test_bit(NFS_MIG_FAILED, &mig_status) )

{//mig_status的bit2=1,则执行

    ret = -EIO;

    return ret;

}

3)、原子操作只对整型变量和位变量数据进行保护。

3、自旋锁

1)、自旋锁结构体spinlock_t

需要包含头文件“#include < spinlock_types.h >”;

Linux内核使用结构体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;

2)、采用“自旋锁”,实现线程之间的并发访问函数:

需要包含头文件“#include <spinlock.h>”;

#define DEFINE_SPINLOCK(x)  spinlock_t x = __SPIN_LOCK_UNLOCKED(x)

声明x为自旋锁结构,并初始化;

int spin_lock_init(spinlock_t *lock)

初始化自旋锁;

void spin_lock(spinlock_t *lock)

获取指定的自旋锁,也叫做加锁;

void spin_unlock(spinlock_t *lock)

释放指定的自旋锁;

int spin_trylock(spinlock_t *lock)

尝试获取指定的自旋锁,如果没有获取到就返回0;

int spin_is_locked(spinlock_t *lock)

检查指定的自旋锁是否被获取,如果没有被获取就返回非0,否则返回0;

注意:

线程之间的并发访问函数,适用于线程之间的并发访问,但不适合中断程序并发访问

3)、采用“自旋锁”,实现线程与中断之间的并发访问函数:

void spin_lock_irq(spinlock_t *lock)

禁止本地中断,并获取自旋锁;

void spin_unlock_irq(spinlock_t *lock)

激活本地中断,并释放自旋锁;

void spin_lock_irqsave(spinlock_t *lockunsigned long flags)

保存中断状态,禁止本地中断,并获取自旋锁;

void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

注意:在线程中使用spin_lock_irqsave()和spin_unlock_irqrestore();在中断中使用spin_lock()和spin_unlock()来获取自旋锁和释放自旋锁不推荐使用spin_lock_irq()和spin_unlock_irq函数

4)、举例:

DEFINE_SPINLOCK(MyLock) /*声明MyLock为自旋锁结构,并初始化*/

/* 线程A */

void functionA (){

unsigned long flags; /* 中断状态 */

spin_lock_irqsave(&MyLock, flags); /* 获取锁 */

    //保存中断状态,禁止本地中断,并获取自旋锁;

/* 临界区 */

spin_unlock_irqrestore(&MyLock, flags); /* 释放锁 */

   //将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁;

}

/* 中断服务函数 */

void irq() {

spin_lock(&MyLock); /* 获取锁 */

//获取MyLock自旋锁,也叫做加锁;

/* 临界区 */

spin_unlock(&MyLock); /* 释放锁 */

//释放MyLock自旋锁;

}

5)、“下半部”并发访问函数

需要包含文件“#include <spinlock.h>

下半部(BH)也会竟争共享资源,有些资料也会将下半部叫做底半部。如果要在“下半部”里面使用自旋锁,则需要用到下面的函数:

void spin_lock_bh(spinlock_t*lock)

关闭下半部,并获取自旋锁;

void spin_unlock_bh(spinlock_t*lock)

打开下半部,并释放自旋锁;

6)、自旋锁使用注意事项:

①、“其它线程”在等待自旋锁的时处于“自旋”状态,因此“持有锁的线程”不能长时间持用这个自旋锁,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体。

②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的API函数,否则的话可能导致死锁。

③、不能递归申请白旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了!

④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还是多核的SOC,都将其当做多核SOC来编写驱动程序。

4、读写锁函数

需要包含文件“#include <rwlock_types.h>

typedef struct {

    arch_rwlock_t raw_lock;

#ifdef CONFIG_DEBUG_SPINLOCK

    unsigned int magic, owner_cpu;

    void *owner;

#endif

#ifdef CONFIG_DEBUG_LOCK_ALLOC

    struct lockdep_map dep_map;

#endif

} rwlock_t;

#define DEFINE_RWLOCK(x) rwlock_t x = __RW_LOCK_UNLOCKED(x)

声明“读写锁结构变量x”,并初始化读写锁;

读写锁函数需要包含文件“#include <rwlock.h>

void rwlock_init(rwlock_t *lock)

初始化读写锁;

void read_lock(rwlock_t *lock)

获取读锁;

void read_unlock(rwlock_t *lock)

释放读锁;

void read_lock_irq(rwlock_t *lock)

禁止本地中断,并且获取读锁;

void read_unlock_irg(rwlock_t *lock)

打开本地中断,并且释放读锁;

void read_lock_irgsave(rwlock_t *lock,unsigned long flags)

保存中断状态,禁止本地中断,并获取读锁;

void read_unlock_irqrestore(rwlock_t *lock,unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放读锁;

void read_lock_bh(rwlock_t *lock)

关闭下半部,并获取读锁;

void read_unlock_bh(rwlock_t *lock)

打开下半部,并释放读锁;

void write_lock(rwlock_t *lock)

获取写锁;

void write_unlock(rwlock_t *lock)

释放写锁;

void write_lock_irg(rwlock_t *lock)

禁止本地中断,并且获取写锁;

void write_unlock_irg(rwlock_t *lock)

打开本地中断,并且释放写锁;

void write_lock_irqsave(rwlock_t *lock,unsigned long flags)

保存中断状态,禁止本地中断,并获取写锁。

void write_unlock_irqrestore(rwlock_t *lock,unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放读锁。

void write_lock_bh(rwlock_t *lock)

关闭下半部,并获取读锁;

void write_unlock_bh(rwlock_t *lock)

打开下半部,并释放读锁;

5、顺序锁

顺序锁结构需要包含文件“#include <seqlock.h>

顺序锁结构体seqlock_t

typedef struct {

struct seqcount seqcount;

spinlock_t lock;

}seqlock_t;

使用顺序锁,实现同时读写,但是不允许同时进行并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取,保证数据完整性。

注意:

顺序锁“保护的资源”不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃

#define DEFINE_SEQLOCK(x) \

       seqlock_t x = __SEQLOCK_UNLOCKED(x)

声明“顺序锁结构变量x”,并初始化

void write_seqlock(seqlock_t *sl)

获取“写顺序锁”;

void write_sequnlock(seqlock_t *sl)

释放“写顺序锁”;

void write_seqlock_irq(seqlock_t *sl)

禁止本地中断,并且获取“写顺序锁”;

void write_sequnlock_irq(seqlock_t *sl)

打开本地中断,并且释放“写顺序锁”;

void write_seqlock_irqsave(seqlock_t *sl,unsigned long flags)

保存中断状态,禁止本地中断,并获取“写顺序锁”;

void write_sequnlock_irqrestore(seqlock_t *sl,unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放“写顺序锁”;

void write_seqlock_bh(seqlock_t *sl)

关闭下半部,并获取“写顺序锁”;

void write_sequnlock_bh(seqlock_t *sl)

打开下半部,并释放“写顺序锁”;

unsigned read_seqbegin(const seqlock_t *sl)

读单元访问共享资源的时候调用此函数,此函数会返回顺序锁的顺序号;

unsigned read_seqretry(const seqlock_t *sl,unsigned start)

读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读;

6、信号量

信号量有一个“信号量值”,用来控制访问共享资源的访问数量,相当于通过“信号量值”控制访问资源的线程数。

如果将“信号量值”设置大于 1,那么这个信号量就是“计数型信号量”,它允许多个线程同时访问共享资源。

如果将“信号量值”设置为0,那么这个信号量就是“二值信号量”,它具有互斥访问共享资源的作用。

需要包含文件“#include <semaphore.h>

1)、信号量结构体semaphore

struct semaphore {

    raw_spinlock_t       lock;

    unsigned int      count;

    struct list_head  wait_list;

};

#define __SEMAPHORE_INITIALIZER(name, n)             \

{                               \

    .lock      = __RAW_SPIN_LOCK_UNLOCKED((name).lock),  \

    .count     = n,                     \

    .wait_list = LIST_HEAD_INIT((name).wait_list),       \

}

并将“name.count”设置为n;

2)、信号量函数

#define DEFINE_SEMAPHORE(name)  \

    struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

声明信号量结构变量为name,并将信号量的值“name. count”设置为1;这是一个“计数型信号量”。

static inline void sema_init(struct semaphore *sem, int val)

{

    static struct lock_class_key __key;

    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);

    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);

}

初始化信号量结构变量sem,并将“sem->count”设置为val;

void down(struct semaphore *sem)

获取信号量,但会导致休眠,进入休眠状态的线程不能被信号打断,因此不能在中断中使用

int down_trylock(struct semaphore *sem)

尝试获取信号量,如果能获取到信号量就获取,并且返回0。如果不能就返回非0,并不会进入休眠。

int down_interruptible(struct semaphore *sem)

获取信号量,进入休眠状态的线程可以被信号打断。

void up(struct semaphore *sem)

释放信号量

int down_killable(struct semaphore *sem)

进入休眠状态的线程可以被唤醒,中断获取信号量的操作;

3)、举例:

struct semaphore sem; /* 定义信号量 */

sema_init(&sem, 1);

/*

将“信号量值”设置为1,那么这个信号量就是“二值信号量”,它具有互斥访问共享资源的作用

sem.count=1,sem为“二值信号量”。

*/

down(&sem);

/*

如果sem.count>0,则sem.count--,当前进程获取信号量

如果sem.count<=0,表明当前进程无法获取信号量,则加入“等待队列”,开始睡眠;

down()不能用于中断函数里;

*/

/* 临界区 */

up(&sem);

/*

释放信号量

如果“等待队列”中没有其他进程在等待sem信号量,则将sem.count++,“释放信号量”;

如果“等待队列”中有其他进程在等待sem信号量,则先进入“等待队列”中的进程会优先“获取信号量”。

*/

4)、信号量的特点:

信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场

合,因此信号量不能用于中断中,因为信号量会引起休眠,中断是不能进入休眠的。

如果共享资源的持有时间比较短,那就不适合使用信号量。因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

7、互斥体

需要包含文件“#include <mutex.h>

struct mutex {

    atomic_long_t     owner;

    spinlock_t    wait_lock;

};

#define DEFINE_MUTEX(mutexname) \

    struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

声明互斥结构变量mutexname,并初始化;

void mutex_init(struct mutex *lock);

并初始化互斥结构变量lock

void mutex_lock(struct mutex *lock)

获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠;

void mutex_unlock(struct mutex *lock)

释放 mutex,也就给 mutex 解锁;

int mutex_trylock(struct mutex *lock)

尝试获取mutex,如果成功就返回 1,如果失败就返回 0;

int mutex_is_locked(struct mutex *lock)

判断mutex_是否被获取,如果是的话就返回1,否则返回 0;

int mutex_lock_interruptible(struct mutex *lock)

使用此函数获取信号量失败进入休眠以后可以被信号打断;

举例:

struct mutex lock; /* 定义一个互斥体 */

mutex_init(&lock); /* 初始化互斥体 */

mutex_lock(&lock); /* 上锁 */

/* 临界区 */

mutex_unlock(&lock); /* 解锁 */

使用“互斥体”时,需要注意如下几点:

①、互斥体可以导致休眠,因此不能在中断中使用互斥体,在中断中只能使用自旋锁

②、和信号量一样,“互斥体”保护的临界区可以调用引起阻塞的API函数。

因为一次只有一个线程可以持有“互斥体”,因此,必须由“互斥体”的持有者释放“互斥体”。

③、“互斥体”不能递归上锁和解锁。

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

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

相关文章

2024全网最全Excel函数与公式应用

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 Excel是一款广泛应用于商业、教育和个人…

某夕夕商品数据抓取逆向之webpack扣取

逆向网址 aHR0cHM6Ly93d3cucGluZHVvZHVvLmNvbQ 逆向链接 aHR0cHM6Ly93d3cucGluZHVvZHVvLmNvbS9ob21lL2JveXNoaXJ0 逆向接口 aHR0cHM6Ly9hcGl2Mi5waW5kdW9kdW8uY29tL2FwaS9naW5kZXgvdGYvcXVlcnlfdGZfZ29vZHNfaW5mbw 逆向过程 请求方式&#xff1a;GET 参数构成 【anti_content】…

基于SSM SpringBoot vue办公自动化计划管理系统

基于SSM SpringBoot vue办公自动化计划管理系统 系统功能 登录注册 个人中心 员工信息管理 部门信息管理 会议管理 计划管理 行程安排管理 行程进度管理 管理员管理 开发环境和技术 开发语言&#xff1a;Java 使用框架: SSM(Spring SpringMVC Mybaits)或SpringBoot 前端…

【题目】【网络系统管理】2022年甘肃省职业院校技能大赛-网络构建-试卷

极安云科专注职业教育技能竞赛培训4年&#xff0c;包含信息安全管理与评估、网络系统管理、网络搭建等多个赛项及各大CTF模块培训学习服务。本团队基于赛项知识点&#xff0c;提供完整全面的系统性理论教学与技能培训&#xff0c;成立至今持续优化教学资源与讲师结构&#xff0…

vivo统一接入网关VUA转发性能优化实践

作者&#xff1a;vivo 互联网服务器团队 - Qiu Xiangcun 本文将探讨如何通过使用Intel QuickAssist Technology&#xff08;QAT&#xff09;来优化VUA的HTTPS转发性能。我们将介绍如何使用QAT通过硬件加速来提高HTTPS转发的性能&#xff0c;并探讨QAT在不同应用场景中的表现。最…

Python下有关CV的一些算法和函数

目录&#xff1a; 1. HoughCircles二级目录三级目录 1. HoughCircles 霍夫圆检测 二级目录 三级目录

【CKA模拟题】查询消耗CPU最多的Pod

题干 For this question, please set this context (In exam, diff cluster name) 对于此问题&#xff0c;请设置此上下文&#xff08;在考试中&#xff0c;diff 集群名称&#xff09; kubectl config use-context kubernetes-adminkubernetesFind the pod that consumes the …

[云原生] Prometheus自动服务发现部署

一、部署服务发现 1.1 基于文件的服务发现 基于文件的服务发现是仅仅略优于静态配置的服务发现方式&#xff0c;它不依赖于任何平台或第三方服务&#xff0c;因而也是最为简单和通用的实现方式。 Prometheus Server 会定期从文件中加载 Target 信息&#xff0c;文件可使用 YAM…

大模型训练准备工作

一、目录 1 大模型训练需要多少算力&#xff1f; 2. 大模型训练需要多少显存&#xff1f; 3. 大模型需要多少数据量训练&#xff1f; 4. 训练时间估计 5. epoch 选择经验 6. 浮点计算性能测试 二、实现 1 大模型训练需要多少算力&#xff1f; 训练总算力&#xff08;Flops&…

逻辑运算函数

true默认为1 false默认为0 可以用于多条件筛选 if语句 多层嵌套

【C#算法实现】可见的山峰对数量

文章目录 前言一、题目要求二、算法设计及代码实现2.1 算法思想2.2 代码实现 前言 本文是【程序员代码面试指南&#xff08;第二版&#xff09;学习笔记】C#版算法实现系列之一&#xff0c;用C#实现了《程序员代码面试指南》&#xff08;第二版&#xff09;栈和队列中的可见的…

Redisson

Redisson 1 什么是Redisson2.SpringBoot整合Redisson3.使用Redisson实现分布式锁3.1 Redisson是如何解决这两个问题的呢?3.2 Redisson是怎么保证自己加的锁, 自己释放锁? 1 什么是Redisson Redisson是一个基于Redis的java框架, 用于简化Redis的操作. 它提供了丰富的功能, 包…

Python 界面逻辑分离示例

使用PyQt5设计UI界面开发桌面应用程序的小伙伴&#xff0c;刚开始可能都会遇到这样让人不爽的事&#xff1a;用Qt Designer好不容易设计好的界面并写好逻辑代码&#xff0c;已经可以正常运行了&#xff0c;某天发现界面要做个小改动(这种事好象永远都避不了)&#xff0c;重新修…

MySQL8.0索引新特性

文章目录 1 支持降序索引2 隐藏索引 1 支持降序索引 举例&#xff1a;分别在MySQL 5.7版本和MySQL 8.0版本中创建数据表ts1&#xff0c;结果如下&#xff1a; CREATE TABLE ts1(a int,b int,index idx_a_b(a,b desc) );在MySQL 5.7版本中查看数据表ts1的结构&#xff0c;从结…

【SpringBoot】请求与响应参数 IoC与DI 总结

文章目录 ① —— 请求 ——一、简单参数 RequestParam1.1 参数与形参 命名相同1.2 参数与形参 命名不同 二、实体参数2.1 简单实体对象2.2 复杂实体对象 三、数组集合参数3.1 数组3.2 集合 RequestParam 四、日期参数 DateTimeFormat五、JSON参数 RequestBody六、路径参数 Pat…

下载chromedrive,使用自动化

1、先看一下自己浏览器的版本 2、访问 https://googlechromelabs.github.io/chrome-for-testing/

QT中dumpcpp以及dumpdoc使用

qt中调用COM的方式方法有四种&#xff0c;参考解释在 Qt 中使用 ActiveX 控件和 COM (runebook.dev) 介绍dumpcpp的使用方法Qt - dumpcpp 工具 (ActiveQt) (runebook.dev)&#xff1a; 在安装好了的qt电脑上&#xff0c;通过powershell窗口来实现&#xff0c;powershell比cmd要…

算法——贪心

「贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优」 贪心无套路 1. 分发饼干 贪心策略&#xff1a; &#xff08;1&#xff09;局部最优就是大饼干喂给胃口大的&#xff0c;充分利用饼干尺寸喂饱一个&#xff0c;全局最优就是喂饱尽可能多的小孩 &#xff08…

广州大彩科技新品发布:大彩科技COF系列2.4寸串口屏发布!

一、产品介绍 此次发布的是S系列平台2.4寸COF超薄结构串口屏&#xff0c;分辨率为240*320&#xff0c;该平台采用了Cortex-M3内核的处理器&#xff0c;内置了2Mbyte PSRAM和64Mbit FLASH&#xff0c;是专为小尺寸串口屏设计的MCU&#xff0c;精简了外围电路。 该平台默认支持大…

element ui 中文离线文档(百度云盘下载)

一般内网开发上不了网&#xff0c;用离线版本比较方便&#xff0c;下载地址&#xff1a; https://download.csdn.net/download/li836779537/88355878?spm1001.2014.3001.5503 下载后里面有个 index.hrml 双击打开就可以用 效果如下&#xff1a;