当前位置: 首页 > news >正文

[Linux]:线程(二)

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

Windows环境不同,我们在linux环境下需要通过指令进行各操作,以下是常见操作的指令:

1. 线程互斥

1.1 基本概念

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

进程之间进行通信需要先创建第三方资源,使得不同的进程能够看到同一份资源。由于这份第三方资源可以由操作系统中的不同模块提供,所以进程间通信的方式有很多种。在进程间通信中,这个第三方资源被称为临界资源,而访问第三方资源的代码则被称为临界区。

与之不同的是,多线程的大部分资源都是共享的。因此,线程之间进行通信并不需要像进程那样费力地去创建第三方资源。

例如,我们在代码中只需要在全局区定义一个count变量,新线程可以每隔一秒对该变量进行加一操作,主线程也可以每隔一秒获取count变量的值并进行打印。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
using namespace std;
int count = 0;
void *Routine(void *args)
{while (true){count++;sleep(1);}pthread_exit((void *)0);
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, Routine, nullptr);while (true){cout << "The value of count is " << count << endl;sleep(1);}pthread_join(tid, nullptr);return 0;
}

在当前情境下,我们相当于实现了主线程和新线程之间的通信。其中,全局变量count起着关键作用,它被称为为临界资源,原因在于它被多个执行流所共享。而主线程中的 cout 操作以及新线程中的 count++ 操作,被称作临界区。这是因为这些代码片段对临界资源进行了访问。

但是我们同样观察到打印数据并没有 1,这就是多执行流对临界资源操作常引发的数据不一致问题。

同样我们也可以下面抢票程序的实现,具体演示如果不对临界资源进行限制,可能会出现的危害。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
int tickets = 1000;
void *getTickets(void *args)
{string name = "thread ";name += to_string((uint64_t)args);while (true){if (tickets > 0){usleep(1000);cout << "[" << name << "]" << "get a ticket,left: " << --tickets << endl;}else{break;}}cout << name << " is quit!" << endl;pthread_exit((void *)0);
}
int main()
{pthread_t tids[5];for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, getTickets, (void *)i);}for (int i = 0; i < 5; i++){pthread_join(tids[i], nullptr);}return 0;
}

剩余票数出现负数,这明显不符合我们的常识与预期,之所以出现这种情况,本质就是 tickets就是我们的临界资源,--tickets也 并不是原子的,在多执行流同时执行时就可能会发生这种问题。

为什么 --tickets并不是原子的呢?

因为从汇编角度看,我们的 --操作其实是不安全的,他们转成汇编,一般会对应三条汇编指令:从内存中读取数据到 CPU 中;CPU 内进行操作;CPU 将结果写回内存。进程在运行的时候,随时可能被切换。

1.2 互斥量

为了解决这个问题我们就引入了互斥,保证一次只有一个执行流访问临界资源,而为了实现互斥,我们就需要保证临界区的原子性,即临界区的资源要么被执行完成,要么不执行,只存在这两态。

要做到这些,本质就是需要一把锁,所以 Linux就引入一个锁,并将其称为互斥量。

画板

1.3 互斥量的接口

1.3.1 初始化互斥量

我们可以使用pthread_mutex_init初始化互斥量,使用方法如下:

  1. 函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  2. 参数:
  • mutex:需要初始化的互斥量。
  • attr:初始化互斥量的属性,一般设置为 nullptr 即可。
  1. 返回值:互斥量初始化成功返回0,失败返回错误码。

这种调用函数接口初始化互斥量的方式我们称为动态分配,除此之外,我们也能使用如下的方式进行初始化,我们将其称为静态分配。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

1.3.2 销毁互斥量

我们可以使用pthread_mutex_destory销毁互斥量,使用方法如下:

  1. 函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
  2. 参数:mutex:需要销毁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

其中销毁互斥量,需要注意以下几点:

  • 使用PTHREAD_MUTEX_INITIALIZER静态初始化的互斥量不需要销毁。
  • 不能销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
1.3.3 加锁互斥量

加锁本质就是让被加锁区域的代码具有原子性,只能同时被一个线程访问。我们可以使用pthread_mutex_lock对互斥量进行加锁,使用方法如下:

  1. 函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
  2. 参数:mutex:需要加锁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

如果一个线程在执行过程中,遇见该接口,并且该锁已被其他线程申请,那么该线程此时就会陷入阻塞状态,等待其解锁。

1.3.4 解锁互斥量

在加完锁之后,我们不可能让所有代码只被一个执行流访问,所以我们需要合适的地方解锁,我们可以使用pthread_mutex_unlock对互斥量进行解锁,使用方法如下:

  1. 函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
  2. 参数:mutex:需要解锁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

知道了这些概念之后我们就可以对前面的抢票逻辑进行修改:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
int tickets = 1000;
pthread_mutex_t mutex;
void *getTickets(void *args)
{string name = "thread ";name += to_string((uint64_t)args);while (true){pthread_mutex_lock(&mutex);if (tickets > 0){usleep(1000);cout << "[" << name << "]" << "get a ticket,left: " << --tickets << endl;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}cout << name << " is quit!" << endl;pthread_exit((void *)0);
}
int main()
{pthread_mutex_init(&mutex, nullptr);pthread_t tids[5];for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, getTickets, (void *)i);}for (int i = 0; i < 5; i++){pthread_join(tids[i], nullptr);}pthread_mutex_destroy(&mutex);return 0;
}

其实在大部分情况下,加锁本身都是有损于性能的事,因为它使多执行流由并行执行变为了串行执行,这几乎是不可避免的。所以我们需要在合适的位置加锁与解锁,尽可能减少锁引入锁带来的性能开销成本。

1.4 互斥量的原理

当我们使用互斥量之后,临界区的代码对于其他线程来说,只有两种状态:加锁与解锁,这就保证了临界区的原子性。而我们要知道锁本身就是能被所有执行流访问的资源,所以锁本身也是一种临界资源,当然也需要保证其原子性,所以锁本身实现就是原子的。

为了实现互斥锁操作,大多数体系结构都提供了 swapexchange 指令,该指令的作用就是把寄存器和内存单元的数据相交换,以下就是实现加锁 lock与解锁 unlock的伪代码:

我们首先可以认为 mutex 的初始值为1,al 是计算机中的一个寄存器,当线程申请锁时,需要执行以下步骤:

  1. 先将 al 寄存器中的值清0。该动作可以被多个线程同时执行,因为每个线程都有自己的一组寄存器(上下文信息),执行该动作本质上是将自己的 al 寄存器清0。
  2. 然后交换 al 寄存器和 mutex 中的值。xchgb 是体系结构提供的交换指令,该指令可以完成寄存器和内存单元之间数据的交换。

画板

  1. 最后判断 al 寄存器中的值是否大于0。若大于0则申请锁成功,此时就可以进入临界区访问对应的临界资源;否则申请锁失败需要被挂起等待,直到锁被释放后再次竞争申请锁。

我们需要注意的是CPU内的寄存器不是被所有的线程共享的,每个线程都有独自的一组寄存器,所以改变当前线程 al寄存器的值并不会影响其他线程的 al寄存器, 当然内存中的数据因为属于同一个进程,所以各个线程是共享的。

画板

而当线程释放锁时,需要执行以下步骤:

  1. 将内存中的 mutex 置回1,使得下一个申请锁的线程在执行交换指令后能够得到1。
  2. 唤醒等待 mutex 的线程,让它们继续竞争申请锁。

在线程释放锁的过程中,并没有将当前线程的 al 寄存器中的值清0,这不会造成任何影响,因为每次线程在申请锁时都会先将自己 al 寄存器中的值清0,再执行交换指令。

所以我们申请锁的本质就是执行 xchgb这一条汇编指令,因为只有一条,所以只有已执行与未执行两种状态,具有原子性。

2. 线程安全

线程安全是指在多线程环境下,多个线程并发执行同一段代码时,不会出现不可预期的错误结果或数据不一致的情况。

常见线程不安全的情况有:

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

而线程安全的情况有:

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
  • 类或者接口对于线程来说都是原子操作。
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

而可重入函数与线程安全的联系有:

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

而可重入函数与线程安全的区别有:

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

3. 死锁

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

比如说如果某一执行流连续申请了两次锁,就会陷入死锁状态。具体情况如下:当该执行流第一次申请锁时,通常会申请成功。然而,第二次申请锁时,由于此锁已经被该执行流自身持有,再次申请会失败,进而导致该执行流被挂起。而此时,这个锁在其自己手上,可它又处于被挂起的状态,根本没有机会去释放锁。这样一来,该执行流将永远无法被唤醒,从而处于死锁状态。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void*Routine(void*args)
{pthread_mutex_lock(&mutex);pthread_mutex_lock(&mutex);return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,Routine,nullptr);pthread_join(tid,nullptr);return 0;
}

其中形成死锁的必要条件有以下四个:

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

而为了避免死锁我们一般也可以从这几个角度思考:

  • 破坏死锁的四个必要条件。
  • 加锁顺序一致。
  • 避免锁未释放的场景。
  • 资源一次性分配。

除此之外,还有一些避免死锁的算法,常见的比如有死锁检测算法和银行家算法。

4. 线程同步

4.1 饥饿问题

线程饥饿指的是某些线程由于各种原因,一直无法获得足够的 CPU 时间来执行任务,从而处于长期等待或执行时间极少的状态。 产生线程饥饿的原因主要有以下几种:

  1. 高优先级线程抢占:如果系统中有高优先级的线程持续占用 CPU 资源,那么低优先级的线程就可能长时间得不到执行机会,从而导致饥饿。例如,在实时系统中,高优先级的实时任务可能会一直抢占低优先级的普通任务。
  2. 线程调度不公平:如果线程调度算法不合理或者存在缺陷,可能导致某些线程被不公平地对待,长期无法获得执行机会。比如某些调度算法可能偏向于某些特定类型的线程或者特定状态的线程。
  3. 资源竞争:当多个线程竞争有限的资源时,一些线程可能因为一直无法获得所需资源而被阻塞,从而无法执行。例如,多个线程竞争一个互斥锁,而某些线程总是在竞争中失败,就可能陷入饥饿状态。

线程饥饿会导致系统性能下降,部分任务无法及时完成,甚至可能使整个系统陷入停滞或出现不可预测的行为。为了解决线程饥饿问题,我们可以让线程与线程之间形成同步关系。

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题。

4.2 条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述。其一般包含两个步骤:

  • 一个线程等待条件变量的条件成立而被挂起。
  • 另一个线程使条件成立后唤醒等待的线程。

4.3 条件变量的接口

4.3.1 初始化条件变量

我们可以使用pthread_cond_init初始化互斥量,使用方法如下:

  1. 函数原型:int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  2. 参数:
  • cond:需要初始化的条件变量。
  • attr:初始化条件变量的属性,一般设置为 nullptr 即可。
  1. 返回值:条件变量初始化成功返回0,失败返回错误码。

这种调用函数接口初始化条件变量的方式我们称为动态分配,除此之外,我们也能使用如下的方式进行初始化,我们将其称为静态分配。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

4.3.2 销毁条件变量

我们可以使用pthread_cond_destory销毁互斥量,使用方法如下:

  1. 函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
  2. 参数:mutex:需要销毁的条件变量。
  3. 返回值:成功返回 0,失败返回错误码。
  • 使用 PTHREAD_COND_INITIALIZER 静态初始化的条件变量不需要销毁。
4.3.3 等待条件变量

当某个线程满足某个条件时,我们就可以将其至于条件变量下等待。

  1. 函数原型:int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  2. 参数:
  • cond:需要等待的条件变量。
  • mutex:当前线程所处临界区对应的互斥锁。
  1. 返回值:成功返回 0,失败返回错误码。
4.3.4 唤醒等待

在满足某个条件之后,我们就可以使用以下两种即可,将等待队列中的线程唤醒。

  1. 函数原型:
  • int pthread_cond_broadcast(pthread_cond_t *cond);
  • int pthread_cond_signal(pthread_cond_t *cond);
  1. 参数:cond:需要唤醒的条件变量。
  2. 返回值:成功返回 0,失败返回错误码。

其中 pthread_cond_signal()函数用于唤醒等待队列中的第一个线程。pthread_cond_broadcast()函数用于唤醒等待队列中的全部线程。

比如我们下面创建五个线程,然后将其放入等待队列,最后由主线程进行唤醒。

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#include <string>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *Routine(void *args)
{pthread_detach(pthread_self());string name = "thread " + to_string((uint64_t)args);while (true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);cout << name << " running..." << endl;pthread_mutex_unlock(&mutex);}
}
int main()
{pthread_t tids[5];pthread_mutex_init(&mutex, nullptr);pthread_cond_init(&cond, nullptr);for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, Routine, (void *)i);}while (true){sleep(1);pthread_cond_signal(&cond);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

在调用<font style="color:rgb(28, 31, 35);">pthread_cond_wait</font>函数时需要传入对应的互斥锁,原因如下:

当线程由于某些条件不满足而需要在特定条件变量下进行等待时,必须释放该互斥锁。这是因为如果不释放互斥锁,其他线程将无法获取该锁以进入临界区修改共享资源,从而无法改变条件使等待线程被唤醒。

当该线程被唤醒后,会接着执行临界区内的代码,这就要求该线程必须立即获得对应的互斥锁。这样设计确保了线程在被唤醒后能够安全地访问临界区,避免了多个线程同时进入临界区而导致的数据不一致和资源竞争问题。

4.4 条件变量使用规范

使用条件变量我们一般遵守以下规范,如果是等待条件变量,函数应该放在互斥量加锁与解锁之间,因为判断条件也是一种临界资源。

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);

相关文章:

[Linux]:线程(二)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 与Windows环境不同&#xff0c;我们在linux环境下需要通过指令进行各操作&…...

【unity进阶知识3】封装一个事件管理系统

前言 框架的事件系统主要负责高效的方法调用与数据传递&#xff0c;实现各功能之间的解耦&#xff0c;通常在调用某个实例的方法时&#xff0c;必须先获得这个实例的引用或者新实例化一个对象&#xff0c;低耦合度的框架结构希望程序本身不去关注被调用的方法所依托的实例对象…...

服务器使用frp做内网穿透详细教程,请码住

目录 1.内网穿透的定义 2.前提条件 3.frp下载地址 4.配置服务器端的frps.toml文件 5. 配置客户端&#xff0c;即物理服务器或者是电脑本机地址 6.添加服务端启动命令startServerFrp.sh 7.添加客户端启动命令startClientFrp.sh 8. 查看服务端启动日志 9.查看客户端启…...

小程序视频编辑SDK解决方案,轻量化视频制作解决方案

面对小程序、网页、HTML5等多样化平台&#xff0c;如何轻松实现视频编辑的轻量化与高效化&#xff0c;成为了众多开发者和内容创作者共同面临的挑战。正是洞察到这一市场需求&#xff0c;美摄科技推出了其领先的小程序视频编辑SDK解决方案&#xff0c;为创意插上翅膀&#xff0…...

ERROR [internal] load metadata for docker.io/library/openjdk:8

ERROR: failed to solve: DeadlineExceeded: DeadlineExceeded: DeadlineExceeded: openjdk:8: failed to do request: Head “https://registry-1.docker.io/v2/library/openjdk/manifests/8”: dial tcp 202.160.129.6:443: i/o timeout 在构建docker镜像时从docker.io/libr…...

Wed前端--HTML基础

目录 一、开发工具 二、HTML文档结构 2.1头部head 2.1.1title标记 2.1.2元信息meta标记 具体实例 ​编辑 一、开发工具 最基础的开发工具是&#xff1a;HBuilder 二、HTML文档结构 HTML文档由头部head和主体body组成 头部head标记中可以定义标题样式&#xff0c;头部信…...

Latex 自定义运算符加限定条件的实现

“\operatorname{mean}\limits_{n \in N}” 的效果 mean ⁡ n ∈ N \operatorname{mean}\limits_{n \in N} meann∈N​ “\operatorname*{mean}\limits_{n \in N}” 的效果 mean ⁡ n ∈ N \operatorname*{mean}\limits_{n \in N} n∈Nmean​ 参考这篇文章...

大数据实时数仓Hologres(三):存储格式介绍

文章目录 存储格式介绍 一、格式 二、使用建议 三、技术原理 1、列存 2、行存 3、行列共存 四、使用示例 存储格式介绍 一、格式 在Hologres中支持行存、列存和行列共存三种存储格式&#xff0c;不同的存储格式适用于不同的场景。在建表时通过设置orientation属性指…...

关于vue2+uniapp+uview+vuex 私募基金项目小程序总结

1.关于权限不同tabbar处理 uniapp 实现不同用户展示不同的tabbar(底部导航栏)_uniapp tabbar-CSDN博客 但是里面还有两个问题 一个是role应该被本地存储并且初始化 第二个问题是假设我有3个角色 每个角色每个tabbar不一样的&#xff0c;点击tabbar时候会导致错乱 第三个问题…...

多线程(一):线程的基本特点线程安全问题ThreadRunnable

目录 1、线程的引入 2、什么是线程 3、线程的基本特点 4、线程安全问题 5、创建线程 5.1 继承Thread类&#xff0c;重写run 5.1.1 创建Thread类对象 5.1.2 重写run方法 5.1.3 start方法创建线程 5.1.4 抢占式执行 5.2 实现Runnable&#xff0c;重写run【解耦合】★…...

启动hadoop集群出现there is no HDFS_NAMENODE_USER defined.Aborting operation

解决方案 在hadoop-env.sh中添加 export HDFS_DATANODE_USERroot export HDFS_NAMENODE_USERroot export HDFS_SECONDARYNAMENODE_USERroot export YARN_RESOURCEMANAGER_USERroot export YARN_NODEMANAGER_USERroot 再次运行即可。...

Redis实现短信登录解决状态登录刷新的问题

Redis实现短信登录 获取验证码控制层 /*** 发送手机验证码*/PostMapping("/code")public Result sendCode(RequestParam("phone") String phone) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone);} 获取验证码服务层 Result sendC…...

33. java快速排序

1. 前言 排序算法是数据结构中最基础的算法,快速排序则是面试中最常见的排序算法。无论是校招面试还是社招面试,快速排序算法的出现频率远高于其他算法,而且经常会要求候选人白板手写实现算法。快速排序算法的核心是分治处理,重点是分析时间复杂度。 2. 快速排序算法 面试…...

普通二叉搜索树的模拟实现【C++】

二叉搜素树简单介绍 二叉搜索树又称二叉排序树&#xff0c;是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 注意…...

unity 介绍Visual Scripting Scene Variables

Visual Scripting中的场景变量是指在Unity中使用可视化脚本时&#xff0c;能够在不同场景间传递和存储数据的变量。这些变量可以用来跟踪游戏状态、玩家信息或其他动态数据&#xff0c;允许开发者在不编写代码的情况下创建复杂的游戏逻辑。 场景变量的优势包括&#xff1a; 1…...

linux服务器部署filebeat

# 下载filebeat curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.23-linux-x86_64.tar.gz # 解压 tar xzvf filebeat-7.17.23-linux-x86_64.tar.gz# 所在位置&#xff08;自定义&#xff09; /opt/filebeat-7.17.23-linux-x86_64/filebeat.ym…...

个人获取Wiley 、ScienceDirect、SpringerLink三个数据库文献的方法

在同学们的求助文献中经常出现Wiley 、ScienceDirect、SpringerLink这三个数据库文献。本文下面就讲解一下个人如何不用求助他人自己搞定这三个数据库文献下载的方法。 个人下载文献首先要先获取数据库资源&#xff0c;小编平时下载文献是通过科研工具——文献党下载器获取的数…...

Java五子棋

目录 一&#xff1a;案例要求&#xff1a; 二&#xff1a;代码&#xff1a; 三&#xff1a;结果&#xff1a; 一&#xff1a;案例要求&#xff1a; 实现一个控制台下五子棋的程序。用一个二维数组模拟一个15*15路的五子棋棋盘&#xff0c;把每个元素赋值位“┼”可以画出棋…...

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面 废话几句废话不多说&#xff0c;直接上源码目录结构init.pysimulator.pysimple_simulator_app.pyvehicle_config.json 废话几句 自动驾驶开发离不开仿真软件成品仿真软件种类多https://zhuanlan.zhihu.com/p/3…...

一拖二快充线:单接与双接的多场景应用

在当代社会&#xff0c;随着智能手机等电子设备的普及&#xff0c;充电问题成为了人们关注的焦点。一拖二快充线作为一种创新的充电解决方案&#xff0c;因其便捷性与高效性而受到广泛关注。本文将深入探讨一拖二快充线的定义、原理以及在单接与双接手机场景下的应用&#xff0…...

接口自动化测试概述

目录 1 接口自动化测试简介 1.1 什么是接口 1.2 什么是接口测试 1.3 为什么要做接口测试 1.4 什么是接口测试自动化 1.5 为什么要做接口测试自动化 2 接口自动化测试规范 2.1 文档准备 2.1.1 需求文档 2.1.2 接口文档 2.1.3 UI 交互图 2.1.4 数据表设计文档 2.2 明…...

Fingerprint.js:精准用户识别的浏览器指纹技术

在数字化时代&#xff0c;用户识别成为互联网服务中不可或缺的一环。随着隐私保护意识的增强&#xff0c;传统的用户识别方法如Cookies和本地存储面临着越来越多的挑战。而Fingerprint.js作为一种创新的浏览器指纹技术&#xff0c;以其高效、隐私友好的特性&#xff0c;逐渐在个…...

Gson将对象转换为JSON(学习笔记)

JSON有两种表示结构&#xff0c;对象和数组。对象结构以"{"大括号开始&#xff0c;以"}"大括号结束。中间部分由0或多个以”&#xff0c;"分隔的”key(关键字)/value(值)"对构成&#xff0c;关键字和值之间以":"分隔&#xff0c;语法结…...

什么是IPv6

目前国内的网络正在快速的向IPv6升级中&#xff0c;从网络基础设施如运营商骨干网、城域网&#xff0c;到互联网服务商如各类云服务&#xff0c;以及各类终端设备厂商如手机、电脑、路由器、交换机等。目前运营商提供的IPv6线路主要分为支持前缀授权和不支持前缀授权两种。 说…...

python画图|放大和缩小图像

在较多的画图场景中&#xff0c;需要对图像进行局部放大&#xff0c;掌握相关方法非常有用&#xff0c;因此我们很有必要一起学习 【1】官网教程 首先是进入官网教程&#xff0c;找到学习资料&#xff1a; https://matplotlib.org/stable/gallery/subplots_axes_and_figures…...

Mac优化清理工具CleanMyMac X 4.15.6 for mac中文版

CleanMyMac X 4.15.6 for mac中文版下载是一款功能更加强大的系统优化清理工具&#xff0c;软件只需两个简单步骤就可以把系统里那些乱七八糟的无用文件统统清理掉&#xff0c;节省宝贵的磁盘空间。CleanMyMac X 4.15.6 for mac 软件与最新macOS系统更加兼容&#xff0c;流畅地…...

资质申请中常见的错误有哪些?

在申请建筑资质的过程中&#xff0c;企业可能会犯一些常见的错误&#xff0c;以下是一些需要避免的错误&#xff1a; 1. 资料准备不充分&#xff1a; 申请资质需要提交大量的资料&#xff0c;包括企业法人资料、财务报表、业绩证明等。资料不齐全或不准确都可能导致申请失败。…...

基于单片机的多路温度检测系统

**单片机设计介绍&#xff0c;基于单片机CAN总线的多路温度检测系统设计 文章目录 前言概要功能设计设计思路 软件设计效果图 程序设计程序 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探…...

面试题:通过栈实现队列

题目描述&#xff1a; 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移除并返回元素i…...

网络战时代的端点安全演变

​ 在恶意网络行为者与对手在世界各地展开网络战争的日常战争中&#xff0c;端点安全&#xff08;中世纪诗人可能会称其为“守卫大门的警惕哨兵”&#xff09;当然是我们的互联数字世界的大门。 端点安全类似于我们今天称之为现代企业的数字有机体的免疫系统&#xff0c;可以将…...

微信公众号怎么做网站的/企业网站建设报价表

/* * 【程序1】 题目&#xff1a;古典问题&#xff1a;有一对兔子&#xff0c;从出生后第3个月起每个月都生一对兔子&#xff0c; 小兔子长到第三个月后每个月又生一对兔子&#xff0c;假如兔子都不死&#xff0c;问每个月 的兔子总数为多少&#xff1f; 1.程序分析&#x…...

wordpress菜单手机显示下拉/北京网站seo哪家公司好

题目来源&#xff1a;链接 题目描述&#xff1a; 输入一个矩阵&#xff0c;按照从外向里以顺时针的顺序依次打印出每一个数字。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 代码实现&#xff1a; def s…...

wordpress代码缩进/seo人工智能

点击上方 蓝色文字&#xff0c;选择置顶或星标第一时间关注 Python 技术干货&#xff01;阅读文本大概需要 5 分钟。工欲善其事必先利其器的道理相信大家都懂。而作为经常要和各大网站做拉锯战的爬虫工程师们&#xff0c;则更需要利用好身边的一切法器&#xff0c;以便更快的攻…...

一个wordpress程序搭建多个网站/seo平台是什么

最近仔细研读了苹果官网最新的设计规范。网上没有找到很满意的翻译版本&#xff0c;于是自己老老实实的啃了几遍官方文档&#xff0c;顺便把学习笔记输出给大家分享一下。这里有几点要提醒一下大家&#xff1a;这是简明笔记&#xff0c;只选了重点内容&#xff0c;不是通篇逐字…...

wordpress 面包屑导航修改/搜索引擎营销sem包括

mysql-cluster的问题棘手发布时间:2009-12-01 15:56:29来源:红联作者:skyuun我搭建了一个3台服务器所做的mysql-cluster集群集群版本是7.09G 操作系统是RH-5.2-32使用的是RPM包安装方式服务器为 MySQL-Cluster-gpl-server和MySQL-Cluster-gpl-client和MySQL-Cluster-gpl-storag…...

猪八戒做网站要多少钱/百度问答平台入口

这篇文章转贴的原因是我老婆在操作ORACLE的 CLOB数据类型进行关联比较提示了 00932的错误, 比较LOB大对象应当使用dbms_lob.compare函数(下面文章有提到具体的使用).主要是用来存储大量数据的数据库字段&#xff0c;最大可以存储4G字节的非结构化数据。主要介绍字符类型和二进制…...