线程封装与互斥
目录
线程互斥
进程线程间的互斥相关背景概念
互斥量mutex
互斥量的接口
初始化互斥量有两种方法:
销毁互斥量
互斥量加锁和解锁
改进售票系统
互斥量实现原理探究
互斥量的封装
线程互斥
进程线程间的互斥相关背景概念
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
互斥量mutex
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量
归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量,会带来一些问题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route(void *arg)
{char *id = (char *)arg;while (1){if (ticket > 0){usleep(1000);//模拟抢票printf("%s sells ticket:%d\n", id, ticket);//抢到票ticket--;}else{break;}}return nullptr;
}
int main()
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route,(void*) "thread 1");pthread_create(&t2, NULL, route,(void*) "thread 2");pthread_create(&t3, NULL, route,(void*) "thread 3");pthread_create(&t4, NULL, route,(void*) "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
上述代码用多线程模拟抢票,但是为什么抢到负数???
1. if 语句判断条件为真以后,代码可以并发的切换到其他线程
if判断只是对票数进行判断,在cpu内进行逻辑运算,当某个线程刚载入cpu内判断时间片到了就被替换成下一个线程,这样的线程不止一个,再次调度回来时才对--ticket,调度多了,连着减,减成了负数
2.usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码
段
计算机的运行速度很快,1毫秒内抢票很快,多个线程进入该代码陷入内核线程又被挂起
3.--ticket 操作本身就不是一个原子操作
在计算机内部,--tecket简单分为三步骤:操作要把数据从内存加载到cpu,然后cpu进行计算,最后再把结束写回内存。宏观上是cpu在进行调度,假如cpu调度时时间片到了,进程阻塞挂起时,把该线程的上下文保存,在下一次调度时拷贝回继续运行,在此期间还有别的线程在抢票,不断得对ticket操作,ticket不断减少,但是上一次还没有调度完的线程继续切回来,上一次上下文运行的结果又写回内存,此时ticket的数值反而又会增大
全局资源没有被保护,可能会有并发问题,也就是线程安全。要解决以上问题,需要做到三点:
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
- 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到这三点,本质上就是需要一把锁,在内核态返回用户态时进行检查。Linux上提供的这把锁叫互斥量。
互斥量的接口
初始化互斥量有两种方法:
定义锁
方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
销毁互斥量
释放锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
销毁互斥量需要注意:
- 使用PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁,全局的锁程序结束自动释放,局部锁才需要手动释放
- 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
调用 pthread_ lock 时,可能会遇到以下情况:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调⽤会陷入阻塞(执行流被挂起),等待互斥量解锁。
申请锁是为了保护临界资源的安全,多线程竞争申请锁,首先多进程就要先看到锁,锁本身就是临界资源,所以申请锁的过程,必须是原子的,成功就继续向后访问临界资源,申请失败,阻塞挂起申请执行流,等待下一次调度唤醒,锁本身的能力本质是将临界代码区由并行转而为串行,加锁之后在临界区内部,允许线程切换,即使切出去了锁还没释放,也得等我执行完代码才会释放锁,其他线程拿到锁,开锁进入临界区
改进售票系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
int ticket = 100;class ThreadDate
{
public:ThreadDate(const std::string &n, pthread_mutex_t &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;pthread_mutex_t *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){pthread_mutex_lock(td->lockp); // 加锁if (ticket > 0){usleep(1000); // 模拟抢票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 抢到票ticket--;pthread_mutex_unlock(td->lockp); // 用完解锁}else{pthread_mutex_unlock(td->lockp); // 票抢完了,解锁退出,否则线程一直阻塞break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock, nullptr); // 初始化锁pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&lock); // 销毁锁return 0;
}
全局锁
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
int ticket = 100;pthread_mutex_t glock =PTHREAD_MUTEX_INITIALIZER;//全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, pthread_mutex_t &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;pthread_mutex_t *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){pthread_mutex_lock(&glock); // 加锁if (ticket > 0){usleep(1000); // 模拟抢票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 抢到票ticket--;pthread_mutex_unlock(&glock); // 用完解锁}else{pthread_mutex_unlock(&glock); // 票抢完了,解锁退出,否则线程一直阻塞break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
互斥量实现原理探究
锁的原理
1硬件实现:关闭时钟中断
2.软件实现:为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换
经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另⼀个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下
锁的初始化把mutex初始化为1,申请锁的时候先把0写进al,然后交换 al和mutex锁的数据,此时%al里面是1,mutex是0,申请锁成功在返回,然后访问临界资源,时间片到了,挂起放到调度队列里了,也没有关系,锁还没有释放,上下文数据被带走了。其他的线程都执行move,寄存器和mutex内都是0,交换完还是0,全部进了下一个调度队列,直到拥有锁的线程执行完释放锁,把1写回mutex,才能轮到下一个线程申请锁成功,如果没有申请成功,锁被占用了,执行else挂起等待,等拥有锁的释放锁后,才可以执行后面的代码申请锁区访问临界支援,说白了,谁交换走了1,谁就持有锁,谁就有优先访问临界资源的权力
互斥量的封装
1.0
//Mutex.hpp#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};
}
//TextMutex.cc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
#include "Mutex.hpp"
using namespace MutexModule;
int ticket = 100;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, Mutex &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;Mutex *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){td->lockp->Lock(); // 加锁if (ticket > 0){usleep(1000); // 模拟抢票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 抢到票ticket--;td->lockp->Unlock(); // 用完解锁}else{td->lockp->Unlock();; // 票抢完了,解锁退出,否则线程一直阻塞break;}}return nullptr;
}
int main()
{Mutex lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
#include "Mutex.hpp"
using namespace MutexModule;
int ticket = 100;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, Mutex &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;Mutex *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){LockGuard guard(*td->lockp);if (ticket > 0){usleep(1000); // 模拟抢票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 抢到票ticket--;}else{td->lockp->Unlock();break;}}return nullptr;
}
int main()
{Mutex lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}
相关文章:

线程封装与互斥
目录 线程互斥 进程线程间的互斥相关背景概念 互斥量mutex 互斥量的接口 初始化互斥量有两种方法: 销毁互斥量 互斥量加锁和解锁 改进售票系统 互斥量实现原理探究 互斥量的封装 线程互斥 进程线程间的互斥相关背景概念 临界资源:多线程执行流共…...
鸿蒙OSUniApp 开发实时天气查询应用 —— 鸿蒙生态下的跨端实践#三方框架 #Uniapp
使用 UniApp 开发实时天气查询应用 —— 鸿蒙生态下的跨端实践 在移动互联网时代,天气应用几乎是每个人手机中的"标配"。无论是出行、旅游还是日常生活,实时获取天气信息都极为重要。本文将以"实时天气查询应用"为例,详…...
第十一天 5G切片技术在车联网中的应用
前言 在自动驾驶汽车每天产生4TB数据的时代,传统的移动网络已难以满足车联网的海量连接需求。中国移动2023年实测数据显示,某智能网联汽车示范区在传统5G网络下,紧急制动指令的传输延迟高达65ms,而5G网络切片技术将这个数值降低到…...

Spring AI 系列之一个很棒的 Spring AI 功能——Advisors
1. 概述 由AI驱动的应用程序已成为我们的现实。我们正在广泛地实现各种RAG应用程序、提示API,并利用大型语言模型(LLM)创建项目。借助 Spring AI,我们可以更快速地完成这些任务。 在本文中,我们将介绍一个非常有价值…...

Vue3 + TypeScript + el-input 实现人民币金额的输入和显示
输入人民币金额的参数要求: 输入要求: 通过键盘,只允许输入负号、小数点、数字、退格键、删除键、方向左键、方向右键、Home键、End键、Tab键;负号只能在开头;只保留第一个小数点;替换全角输入的小数点&a…...

2.1 C++之条件语句
学习目标: 理解程序的分支逻辑(根据不同条件执行不同代码)。掌握 if-else 和 switch 语句的用法。能编写简单的条件判断程序(如成绩评级、游戏选项等)。 1 条件语句的基本概念 什么是条件语句? 程序在执…...
ZYNQ实战:可编程差分晶振Si570的配置与动态频率切换
为什么需要可编程差分晶振? 在现代FPGA和嵌入式系统中,高速串行通信(如GTP/GTX收发器)对参考时钟的精度和灵活性要求极高。例如,1G以太网需要125MHz时钟,SATA协议需120MHz,而DisplayPort则需135MHz。传统固定频率晶振无法满足多协议动态切换需求,而Si570凭借其10MHz~8…...

Linux `ls` 命令深度解析与高阶应用指南
Linux `ls` 命令深度解析与高阶应用指南 一、核心功能解析1. 基本作用2. 与类似命令对比二、选项系统详解1. 常用基础选项2. 进阶筛选选项三、高阶应用技巧1. 组合过滤查询2. 格式化输出控制3. 元数据深度分析四、企业级应用场景1. 存储空间监控2. 安全审计3. 自动化运维五、特…...

【MPC控制 - 从ACC到自动驾驶】5. 融会贯通:MPC在ACC中的优势总结与知识体系构建
【MPC控制 - 从ACC到自动驾驶】融会贯通:MPC在ACC中的优势总结与知识体系构建 在过去的四天里,我们一起经历了一段奇妙的旅程: Day 1: 我们认识了自适应巡航ACC这位“智能领航员”,并初见了模型预测控制MPC这位“深谋远虑的棋手…...
Day3 记忆内容:map set 高频操作
以下是 第三天 的详细学习内容,聚焦 map和set的高效应用,重点突破查找类题型和去重逻辑,助你提升代码效率! 📚 Day3 记忆内容:map & set 高频操作 1. map 核心操作(手写3遍) /…...

初等数论--Garner‘s 算法
0. 介绍 主要通过混合积的表示来逐步求得同余方程的解。 对于同余方程 { x ≡ v 0 ( m o d m 0 ) x ≡ v 1 ( m o d m 1 ) ⋯ x ≡ v k − 1 ( m o d m k − 1 ) \begin{equation*} \begin{cases} x \equiv v_0 \quad (\ \bmod \ m_0)\\ x \equiv v_1 \quad (\ \bmod \ m_1)…...

NV211NV212美光科技颗粒NV219NV220
NV211NV212美光科技颗粒NV219NV220 技术架构解析:从颗粒到存储系统 近期美光科技发布的NV211、NV212、NV219、NV220系列固态颗粒,凭借其技术突破引发行业关注。这些颗粒基于176层QLC堆叠工艺,单Die容量预计在2026年可达1Tb,相当…...

SQL解析工具JSQLParser
目录 一、引言二、JSQLParser常见类2.1 Class Diagram2.2 Statement2.3 Expression2.4 Select2.5 Update2.6 Delete2.7 Insert2.8 PlainSelect2.9 SetOperationList2.10 ParenthesedSelect2.11 FromItem2.12 Table2.13 ParenthesedFromItem2.14 SelectItem2.15 BinaryExpressio…...

Wave Terminal + Cpolar:SSH远程访问的跨平台实战+内网穿透配置全解析
文章目录 前言1. Wave Terminal安装2. 简单使用演示3. 连接本地Linux服务器3.1 Ubuntu系统安装ssh服务3.2 远程ssh连接Ubuntu 4. 安装内网穿透工具4.1 创建公网地址4.2 使用公网地址远程ssh连接 5. 配置固定公网地址 前言 各位开发者朋友,今天为您介绍一款颠覆性操…...

html使用JS实现账号密码登录的简单案例
目录 案例需求 思路 错误案例及问题 修改思路 案例提供 所需要的组件 <input>标签,<button>标签,<script>标签 详情使用参考:HTML 教程 | 菜鸟教程 案例需求 编写一个程序,最多允许用户尝试登录 3 次。…...
sorted() 函数和sort()函数的区别
在Python中,sorted() 函数和列表的 sort() 方法都用于排序,但它们之间有一些关键的区别: 返回值: sorted():返回一个新的列表,包含所有排序后的元素,原始列表不会被修改。sort():对列…...
Solr搜索:比传统数据库强在哪?
Solr 是一个基于 Apache Lucene 的开源搜索平台,广泛用于全文检索和数据分析。与传统的关系型数据库查询相比,Solr 在某些方面具有明显的优势,特别是在处理大规模文本数据和复杂的搜索需求时。以下是 Solr 相对于传统数据库查询的主要优势&am…...

【数据集】基于ubESTARFM法的100m 地温LST数据集(澳大利亚)
目录 数据概述一、输入数据与处理二、融合算法1. ESTARFM(Enhanced STARFM)2. ubESTARFM(Unbiased ESTARFM)代码实现数据下载参考根据论文《Generating daily 100 m resolution land surface temperature estimates continentally using an unbiased spatiotemporal fusion…...

51c自动驾驶~合集55
我自己的原文哦~ https://blog.51cto.com/whaosoft/13935858 #Challenger 端到端碰撞率暴增!清华&吉利,框架:低成本自动生成复杂对抗性驾驶场景~ 自动驾驶系统在对抗性场景(Adversarial Scenarios)中的可靠性是安全落…...

【前端基础】Promise 详解
文章目录 什么是 Promise?为什么要使用 Promise?创建 Promise消费 Promise (使用 Promise)1. .then(onFulfilled, onRejected)2. .catch(onRejected)3. .finally(onFinally) Promise 链 (Promise Chaining)Promise 的静态方法1. Promise.resolve(value)2…...

高性能管线式HTTP请求
高性能管线式HTTP请求:原理、实现与实践 目录 高性能管线式HTTP请求:原理、实现与实践 1. HTTP管线化的原理与优势 1.1 HTTP管线化的基本概念 关键特性: 1.2 管线化的优势 1.3 管线化的挑战 2. 高性能管线式HTTP请求的实现方案 2.1 技术选型与工具 2.2 Java实现:…...
c/c++的opencv膨胀
使用 OpenCV (C) 进行图像膨胀操作详解 图像膨胀 (Dilation) 是形态学图像处理中的另一种基本操作,与腐蚀操作相对应。它通常用于填充图像中的小孔洞、连接断开的物体部分、以及加粗二值图像中的物体。本文将详细介绍膨胀的原理,并演示如何使用 C 和 Op…...
react native搭建项目
React Native 项目搭建指南 React Native 是一个使用 JavaScript 和 React 构建跨平台移动应用的框架。以下是搭建 React Native 项目的详细步骤: 1. 环境准备 安装 Node.js 下载并安装 Node.js (推荐 LTS 版本) 安装 Java Development Kit (JDK) 对于 Androi…...

【CSS】九宫格布局
CSS Grid布局(推荐) 实现代码: <!doctype html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0"…...

Python用Transformer、Prophet、RNN、LSTM、SARIMAX时间序列预测分析用电量、销售、交通事故数据
原文链接: tecdat.cn/?p42219 在数据驱动决策的时代,时间序列预测作为揭示数据时序规律的核心技术,已成为各行业解决预测需求的关键工具。从能源消耗趋势分析到公共安全事件预测,不同领域的数据特征对预测模型的适应性提出了差异…...

java基础(面向对象进阶高级)泛型(API一)
认识泛型 泛型就等于一个标签(比如男厕所和女厕) 泛型类 只能加字符串: 把别人写好的东西,自己封装。 泛型接口 泛型方法、泛型通配符、上下限 怎么解决下面的问题? API object类 toString: equals: objects类 包装类 为什么上面的Integer爆红…...

学习心得(17--18)Flask表单
一. 认识表单:定义表单类 password2中末端的EqualTo(password)是将密码2与密码1进行验证,看是否相同 二.使用表单: 运行 如果遇到这个报错,就在该页面去添加 下面是举例: 这就是在前端的展示效…...
AI测试和敏捷测试有什么联系与区别?
AI测试与敏捷测试作为软件质量保障领域的两种重要方法,既有紧密联系也存在显著区别。以下是两者的联系与区别分析: 一、联系 共同目标:提升测试效率与质量 敏捷测试强调通过快速迭代、持续反馈和团队协作确保交付价值,而AI测试通…...

微信小程序进阶第2篇__事件类型_冒泡_非冒泡
在小程序中, 事件分为两种类型: 冒泡事件, 当一个组件上的事件被触发后,该事件会向父节点传递非冒泡事件, 当一个组件上的事件被触发后, 该事件不会向父节点传递。 一 冒泡事件 tap, touchst…...

电机控制学习笔记
文章目录 前言一、电机二、编码器三、开环控制和闭环控制总结 前言 学习了解电机控制技术的一些原理和使用的方法。 一、电机 直流有刷电机 操作简单 使用H桥驱动直流有刷电机 直流有刷电机驱动板 电压检测 电流检测以及温度检测 直流无刷电机 使用方波或者正弦波进行换向…...