【Linux】多线程(上)

本文详细介绍了多线程的常见概念 生产者消费者模型将在多线程(下)继续讲解 

欢迎大家指正 提起讨论进步啊 

目录

多线程的理解

线程的优点

线程的缺点:

线程的用途

线程VS进程

用户级线程库

POSIX线程库 

线程创建:

线程等待

线程终止

 取消线程

分离线程

Linux线程互斥

互斥量mutex

初始化互斥量 

销毁互斥量

互斥量加锁和解锁

可重入VS线程安全

概念

常见的线程不安全的情况

 常见不可重入的情况

常见可重入的情况

 可重入与线程安全联系

可重入与线程安全区别

常见锁概念

死锁

 Linux线程同步 

条件变量

同步和竞态条件

为什么 pthread_cond_wait 需要互斥量


多线程的理解

线程(thread)是一个执行分支,执行粒度比进程更细调度成本更低(不需要进行cache换),他是进程内部的一个执行流,同时是CPU调度的基本单位

进程是承担分配系统资源的基本实体

上面一堆话怎么理解呢?

首先明确一些知识:

寄存器分为两种:可见的和不可见的

CPU内部:运算器,寄存器,控制器,MMU,硬件cache,L1,L2,L3

cache是位于cpu和内存之间的存储器,读写速度高于内存而低于cpu内部的寄存器

由于数据的空间局部性原理和时间局部性原理,cache的引入提升了主存的读写效率

当cpu对某一内存地址发出读操作时,如果cache中已经缓存了此地址的数据,称为命中(hit),数据由cache直接发送给cpu。如果cache中并未缓存,则称为缺失(miss),数据将先由主存发送到cache,再由cache发送到cpu

硬件cache又称为高速缓存:缓存各种数据和代码,故增大了切换进程的成本

局部性原理/预加载:把一些热点数据提前加载到缓冲区

下面从linux操作系统角度来理解上面说的一堆线程概念

这是我们之前理解的进程,有自己的PCB,CPU中有维护这个PCB的寄存器,并且他对应着一个地址空间,进程有自己的PCB和代码数据

 但是今天我们除了这一个PCB,还有更多的

每一个PCB我们可以称之为一个执行流,或者是所谓的线程,这些线程是指向同一块地址空间,所以他们可以看到一些共享数据,执行代码的角度来说,每一个PCB处理代码区中不同代码区域相当于一个进程有不同的执行流,那么线程就是进程内部的一个执行流,所以线程是一个执行分支,执行粒度比进程更细 

之前在进程之间切换的时候要不停的切换cache中的数据,但是今天有了线程,我们可以看到同一份共享数据,cache不需要切换,所以调度成本变低

 对CPU,他看到的还是PCB,只不过看到更多,每一次调度PCB的时候他是找线程而不是进程,所以线程是CPU调度的基本单位

而今天 进程的概念是这样的 

 不难理解,他是承担分配系统资源的基本实体

操作系统中一个线程对应着一个TCB(Thread Control Block),叫做线程控制模块,控制着线程的运行和调度,他属于进程PCB

Windows系统就是这样设计的,但是这个真的太复杂了,所以linux大佬想到了更好的方法:复用

复用你PCB的结构体 用进程PCB模拟线程的TCB

线程的优点

1.创建线程的代价要比创建进程的代价小得多

2.与进程之间的切换相比,线程的切换对于操作系统来说工作量小得多

3.线程占用的资源更少

4.充分利用多处理器的可并行数量

5.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

加密解密 文件压缩和解压等预算法有关的——比较消耗CPU资源


6.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

下载上传 IO主要消耗的是IO资源,磁盘的IO,网络带宽等等

误区: 

 线程可以比较多但是不是越多越好,具体多少是要量化的——保证进程/线程CPU的个数/核数一致

线程的缺点:

  • 1.性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器

如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变

  • 2.健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的

多线程中一个线程崩溃 最后会导致整个进程崩溃 为什么?

系统角度:线程是进程的执行分支 线程做就是进程做

信号角度:页表转换时MMU识别写入权限,没有验证通过 

                MMU异常——>OS识别——>给进程发信号——>linux进程信号 信号是以进程为主的

  • 3.缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

因为执行流看到的资源是通过地址空间看到的 多个LWP看到的是同一个地址空间 所以所有的线程可能会共享进程的大部分资源 修改共享资源所有线程都能看到

  • 4.编程难度提高

编写与调试一个多线程程序比单线程程序困难得多

线程的用途

合理的使用多线程,能提高CPU密集型程序的执行效率
并且可以提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是
多线程运行的一种表现)

线程VS进程

线程私有:线程id 一组寄存器(有自己独立的上下文) 栈 errno 信号屏蔽字 调度优先级

共享:文件描述符表 每种信号的处理方式 当前工作目录 用户id和组id

用户级线程库

linux下没有在真正意义上的线程 而是用进程模拟的线程LWP 所以linux不会直接提供创建线程的系统调用 他会给我们最多提供创建轻量级进程的接口

用户视角:只认线程

用户级线程库:对下将linux轻量级接口封装 对上给用户提供进行线程控制的接口

用户级线程库是任何系统都要自带的也叫原生线程库

POSIX线程库 

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项

线程创建:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

thread:返回线程ID
attr:设置线程的属性,attr为nullptr表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事
前面讲的线程ID属于进程调度的范畴 因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现 对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址 

线程等待

为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内
创建新的线程不会复用刚才退出线程的地址空间

int pthread_join(pthread_t thread, void **value_ptr);

thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码 

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数
PTHREAD_ CANCELED
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return   这种方法对主线程不适用,从main函数return相当于调用exit
2. 线程可以调用pthread_ exit终止自己
3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

void pthread_exit(void *value_ptr);

value_ptr : value_ptr不要指向一个局部变量
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了 

 取消线程

int pthread_cancel(pthread_t thread);

返回值:成功返回0;失败返回错误码

分离线程

默认情况下,新创建的线程是joinable的(线程的属性),线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源

int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离: 

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的——一个线程如果被分离 不能join

如果join会报错——join只是一个属性

Linux线程互斥

先明确一些概念:

临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

互斥量mutex


大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互  多个线程并发的操作共享变量,会带来一些问题

(全局变量是共享资源 但是加上__thread修饰全局变量 变成__thread int g_val=100 就是每个线程各有一份 在线程局部存储!是线程内部的局部变量)

对全局变量-- 没有保护的话会存在并发访问的问题 进而导致数据不一致

#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;}}
}
int main(void)
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, "thread 1");pthread_create(&t2, NULL, route, "thread 2");pthread_create(&t3, NULL, route, "thread 3");pthread_create(&t4, NULL, route, "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);
}

结果是

thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1
thread 3 sells ticket:-2

为什么可能无法获得争取结果?
if 语句判断条件为真以后,代码可以并发的切换到其他线程
usleep之后,可能有很多个线程会进入该代码段
--ticket 操作本身就不是一个原子操作

load :将共享变量ticket从内存加载到寄存器中
update : 更新寄存器里面的值,执行-1操作
store :将新值,从寄存器写回共享变量ticket的内存地址 

要解决的问题:

代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点,本质上就是需要一把锁   Linux上提供的这把锁叫互斥量

初始化互斥量 

静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);

mutex:要初始化的互斥量
attr:nullptr

销毁互斥量

销毁互斥量需要注意:
使用 PTHREAD_ MUTEX_ INITIALIZER 初始化(静态分配)的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

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 <sched.h>
int ticket = 100;
pthread_mutex_t mutex;
void *route(void *arg)
{
char *id = (char*)arg;
while ( 1 ) {
pthread_mutex_lock(&mutex);
if ( ticket > 0 ) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
pthread_mutex_unlock(&mutex);
// sched_yield(); 放弃CPU
} else {
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main( void )
{
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
pthread_mutex_destroy(&mutex);
}

可重入VS线程安全

概念

线程安全:保证多个线程并发同一段代码时,不会出现不同的结果

常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全的问题


重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入

一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数

常见的线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态  随被调用  而发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

 常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都由函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据 

 可重入与线程安全联系


函数是可重入的,那就是线程安全的
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

可重入与线程安全区别


可重入函数是线程安全函数的一种,线程安全不一定是可重入的,而可重入函数则一定是线程安全的
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的 

常见锁概念

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态

死锁四个必要条件
互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

如何避免死锁:核心 破坏死锁的四个必要条件

不主动加锁(去掉互斥问题)

主动释放锁(破坏请求和等待)
按顺序申请锁
同一控制线程释放锁——剥夺锁

 Linux线程同步 

条件变量

条件变量是线程库提供的一个描述临界资源状态的变量 不用频繁的申请和释放锁也能检查到临界资源
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量

可以用条件变量实现线程同步

同步和竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条 。在线程场景下,这种问题也不难理解

条件变量的初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);

cond:要初始化的条件变量
attr:nullptr

销毁:

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_signal——唤醒睡眠的线程,一次只能唤醒一个线程
pthread_cond_broadcast——唤醒睡眠的线程,一次唤醒所有睡眠的线程(所以叫广播)

为什么 pthread_cond_wait 需要互斥量

条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护   没有互斥锁就无法安全的获取和修改共享数据、

所以条件变量和互斥锁总是一起使用

条件变量使用规范

等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

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

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

相关文章

springboot整合jwt

JWT介绍 JWT是JSON Web Token的缩写&#xff0c;即JSON Web令牌&#xff0c;是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息&#xff0c;以便于从资源服务器获…

DICOM开源库兼容性问题

常见的DICOM开源库有dcmtk、fo-dicom、dcm4che3等&#xff0c;DICOM开源库遇到的兼容性问题类似&#xff0c;——dcmtk兼容性最强&#xff0c;fo-dicom次之&#xff0c;dcm4che3最差。 本人在dicom医学影像方面从事软件开发已有10几年&#xff0c;用过dcmtk&#xff0c;fo-dico…

Android国际化各国语言简写

<?xml version"1.0" encoding"utf-8"?> <locale-config xmlns:android"http://schemas.android.com/apk/res/android"><locale android:name"af"/> <!-- 南非荷兰语 --><locale android:name"am&qu…

Spark(32):Spark性能调优之Shuffle调优

目录 0. 相关文章链接 1. 调节 map 端缓冲区大小 2. 调节 reduce 端拉取数据缓冲区大小 3. 调节 reduce 端拉取数据重试次数 4. 调节 reduce 端拉取数据等待间隔 5. 调节 SortShuffle 排序操作阈值 0. 相关文章链接 Spark文章汇总 1. 调节 map 端缓冲区大小 在 Spark 任…

基于.net6的WPF程序使用SignalR进行通信

之前写的SignalR通信&#xff0c;是基于.net6api&#xff0c;BS和CS进行通信的。 .net6API使用SignalRvue3聊天WPF聊天_signalr wpf_故里2130的博客-CSDN博客 今天写一篇关于CS客户端的SignalR通信&#xff0c;后台服务使用.net6api 。其实和之前写的差不多&#xff0c;主要在…

Ubuntu22.04密码忘记怎么办 Ubuntu重置root密码方法

在Ubuntu 22.04 或其他更高版本上不小心忘记root或其他账户的密码怎么办&#xff1f; 首先uname -r查看当前系统正在使用的内核版本&#xff0c;记下来 前提&#xff1a;是你的本地电脑&#xff0c;有物理访问权限。其他如远程登录的不适用这套改密方法。 通过以下步骤&#…

写字楼/办公楼能源管理系统的具体应用 安科瑞 许敏

0 引言 随着社会的进步&#xff0c;我国经济的快速发展&#xff0c;企业的办公环境和方式发生了巨大的变化&#xff0c;专业的写字楼在各大城市遍布林立。写字楼的出现使得各地企业办公集中化、高效化&#xff0c;然而写字楼物业管理的同步发展对于企业服务来说更是一个很大的…

SciencePub学术 | 区块链类重点SCIEEI征稿中

SciencePub学术 刊源推荐: 区块链类重点SCIE&EI征稿中&#xff01;信息如下&#xff0c;录满为止&#xff1a; 一、期刊概况&#xff1a; SCI-01 【期刊简介】IF&#xff1a;4.0-4.5&#xff0c;JCR2区&#xff0c;中科院3区&#xff1b; 【检索情况】SCIE&EI双检&…

Mysql+ETLCloud CDC+StarRocks实时数仓同步实战

一、业务需求及其痛点 大型企业需要对各种业务系统中的销售及营销数据进行实时同步分析&#xff0c;例如库存信息、对帐信号、会员信息、广告投放信息&#xff0c;生产进度信息等等&#xff0c;这些统计分析信息可以实时同步到StarRocks中进行分析和统计&#xff0c;StarRocks…

vue注意点:$attrs、$slots!插槽

$attrs 当父组件给子组件传值&#xff0c;子组件并没有接收数据时&#xff0c;此时数据在$attrs中可以拿到&#xff0c;并且如果子组件不需要使用数据&#xff0c;而孙组件需要&#xff0c;则可以直接v-bind"$attrs"传给孙。 <-- 父组件 --> <div><…

[java安全]URLDNS

文章目录 [java安全]URLDNS前言HashMapURLURLStreamHandler调用过程调用链流程图POC [java安全]URLDNS 前言 URLDNS利用链是一条很简单的链子&#xff0c;可以用来查看java反序列化是否存在反序列化漏洞&#xff0c;如果存在&#xff0c;就会触发dns查询请求 它有如下优点&a…

Centos 8 / TencentOS Server 3.1 安装 docker-ce

目录 前言安装 docker-ce设置Docker Hub 镜像缓存参考 前言 TencentOS Server 3.1(与 CentOS 8用户态完全兼容&#xff0c;配套基于社区5.4 LTS 内核深度优化的 tkernel4版本) 安装 docker-ce 先卸载老版本&#xff0c;没有老版本的跳过 yum remove docker \docker-client \d…

vue3学习

响应式数据 const searchFormState reactive({})const searchFormRef ref()两者差不多&#xff0c;reactive(&#xff09;多用于复杂数据结构 ref()多用与简单数据结构 组件传值父传子 属性传值 父组件给子组件传值属性aa值为data :aa"data" 子组件接收数据方法 引…

常见面试题之HashMap

1. 二叉树 1.1 二叉树概述 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只有左子节点&#xff0c;有的节…

前端基本功 用 React Hooks + Antd 实现一个 Todo-List

背景 使用 React Hooks 以及组件库 Antd 来实现一个可以 增删 标记是否完成 的 todo-list 思路 要实现一个 todo-list 首先想到用 useState 维护一个状态数组来保存当前 list &#xff0c;还要用一个状态维护添加框中的内容 const [todos, setTodos] useState(initialValu…

Excel之VLOOKUP()函数介绍

Excel之VLOOKUP()函数介绍 Excel的VLOOKUP函数语法&#xff1a; VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup]) 参数说明&#xff1a; lookup_value&#xff1a;要查找的值或要比较的值。 table_array&#xff1a;包含要在其中进行查找的数据表的区…

解决打开excel时报错 “不能使用对象链接和嵌入”

问题截图 打开excel文件或者插入对象时&#xff0c;直接弹出不能使用对象链接和嵌入报错信息。 解决方法 按 winr 组合快捷键&#xff0c;打开运行&#xff0c;输入 dcomcnfg.exe 按回车确定 此时进入到组件服务管理界面&#xff0c;依次选择 组件服务-计算机-我的电脑-DOCM…

S3C2440点亮LED(裸机开发)

文章目录 前言一、环境介绍一、GPIO介绍二、点亮开发板的LED1.预备动作2.led代码 总结 前言 本期和大家主要分享的是使用S3C2440开发板点亮一个LED灯&#xff0c;可能大家拿到开发板之后做的第一件事情都是点灯&#xff0c;这是为什么呢&#xff1f;因为点灯这件事情不仅能够检…

Linux搭建node环境-MobaXterm+node+pm2安装

1.登录session 2.安装X11-forwarding 我也不知道这个有什么用&#xff0c;但是有个叉叉在那里有点难受&#xff0c;就把它解决了什么是X11-forwarding&#xff1f;怎么使用&#xff1f; yum install xorg-x11-xauth xorg-x11-fonts-* xorg-x11-font-utils xorg-x11-fonts-Ty…

redis 和mongodb基础操作练习

目录 redis作业 string、list、hash 数据类型 举例说明list和hash的应用场景&#xff0c;每个至少一个场景 mongodb作业 1. 创建一个数据库 名字grade 2. 数据库中创建一个集合名字 class 3. 集合中插入若干数据 文档格式如下 4. 查找 5. 增加、更新、删除、统计 re…