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

Linux_线程

  • 线程与进程
  • 多级页表
  • 线程控制
  • 线程互斥
  • 线程同步
  • 生产者消费者模型
  • 常见概念

下面选取32位系统举例。

一.线程与进程

image.png
上图是曾经我们认为进程所占用的资源的集合。

1.1 线程概念
  1. 线程是一个执行分支,执行粒度比进程细,调度成本比进程低
  2. 线程是cpu调度的基本单位,进程是分配资源的基本单位
  3. 线程是进程内部的一个执行流

通常我们创建线程是为了执行程序的一部分代码,所以执行粒度一定比进程更细,我们知道进程=内核数据结构+代码和数据。引入线程之后,这个概念就应该修正为进程=许多内核数据结构+代码和数据。这些数据结构指向同一个程序地址空间,如图:
image.png
在操作系统的概念中,这样的数据结构叫做线程控制块(TCB),但由于创建新的数据结构还要设计新的调度逻辑,所以Linux在内核中并没有设计这样的数据结构,而是复用进程的数据结构PCB。故此在Linux中并没有真正的线程,而是用进程模拟的线程,线程在Linux中叫轻量级进程(LWP)。

1.2 理解线程
  1. 创建线程只需要执行一部分代码,所以执行粒度比进程细
  2. 当发生调度时,操作系统会识别是进程间的调度还是线程间的调度,如果是线程间的调度,那么只需要改变部分寄存器的值即可,而cache不需要重新加载代码。所以线程的调度成本是比进程小的。
  3. cpu每次调度都是以线程为单位的,操作系统分配资源是以进程为单位的。
  4. 线程有自己的栈
  5. 线程共享进程的大部分数据,但也有私有数据
    1. **线程ID **
    2. **一组寄存器 **
    3. **栈 **
    4. **errno **
    5. **信号屏蔽字 **
    6. 调度优先级
1.3 线程优点
  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
1.4 线程缺点
  • 健壮性减低:主线程退出,全部线程退出,一个线程收到信号,所有线程收到信号
  • 缺乏访问控制:相比与进程,线程之间的通信成本是极低的,但是线程之间的通信缺乏访问控制,即同步机制。当一个线程正在写入,还没有写完,另一个线程就可能会读走数据。

二.多级页表

文件系统指出磁盘和内存的数据交换是以块(4KB)为单位,因此内存的管理也要以4KB为单位,在内存中这样的结构叫做页。

如果页表只有一张,并且页表保存的是字节间的映射关系,那么一个页表需要保存232行,这样肯定是不行的,一是查找速度慢,二是内存可能不够。因此,Linux中采用多级页表,并且将32虚拟地址拆分成3部分使用:前10位用来在页目录中使用,中间10位在页表项中使用,最后12位做为页内偏移地址使用。(212 = 4KB)
image.png

页目录共有1024行,每个页表项也有1024行,共可以映射2^20页地址,这恰好是内存中页的数量,页表映射出物理页号,物理页号和虚拟地址中的12位页内偏移组合,构成物理字节地址。

由于程序不可能使用整个内存,所以页表不会一次全部创建,而是创建一部分。

三.线程控制

3.1 线程库的理解

在Linux中,没有真正意思上线程,所以系统不会提供线程相关的接口,但是有控制轻量级进程的相关接口。可是用户只认线程,所以有了用户级线程库pthread,这个库底层封装了轻量级进程的相关接口,给上层提供了线程相关的接口。pthread库在任何一个Linux系统上都自带了,因此pthread库也叫做原生线程库。由于pthread库属于第三方库,所以在使用gcc/g++编译时,需要指定库名称。gcc xxxx -lpthread

3.2 创建线程
  • pthread_creat
  • pthread_t:整数,输出型参数,返回给用户线程id,以后操作线程用线程id。
  • attr:可以定义线程的属性,一般设置为空
  • void*(start_routine)(void) :函数指针,设置线程指向的函数
  • void* arg:线程执行函数的参数
  • 返回值:成功返回0,失败返回-1

代码示例:

#include<iostream>
#include<pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* s)
{const char* str = (const char*)s;cout << str << endl;}
int main()
{pthread_t p1;pthread_create(&p1, nullptr, threadRoutine, (void*)"thread 1");sleep(3);return 0;
}

通过命令ps -aL可以查看当前用户创建的线程,线程不分父子,分主次:主线程的PID=LWP。操作系统判断是进程间切换还是线程间切换通常使用PID来区分的,看两个结构体的PID是否相同,相同即是线程间切换。

  • 如果主线程退出,当前进程的所有线程立即退出。
  • 如果任一线程收到信号,那么所有线程都会收到信号。

image.png

3.3 线程等待

如果主线程不等待新线程,那么新线程会产生类似僵尸进程的情况,导致内存泄漏。

  • pthread_join

image.png

  • 等待指定id的线程
  • void** retval:输出型参数,已知线程执行的任务返回值为void*,因此要获得这个值就需要void*的地址,即void**

代码示例:

#include<iostream>
#include<pthread.h>
#include <unistd.h>
#include <cstring>using namespace std;void* threadRoutine(void* s)
{const char* str = (const char*)s;cout << str << endl;return (void*)1;  }
int main()
{pthread_t p1;pthread_create(&p1, nullptr, threadRoutine, (void*)"thread 1");void *retval = nullptr;int n = pthread_join(p1, &retval);if (n != 0){cerr << "errno: " << errno << strerror(errno) <<endl;}// 当前环境是64位的,int是32位的,如果强转为32位会报错。cout << "wait successful!" << " retval: " << (int64_t)retval << endl;return 0;
}

3.4 线程分离

如果新线程不用被等待获取返回值,可以在主线程中分离该线程

  • pthread_detach

image.png

  • 分离指定id的线程
#include<iostream>
#include<pthread.h>
#include <unistd.h>
#include <cstring>using namespace std;void* threadRoutine(void* s)
{const char* str = (const char*)s;cout << str << endl;  
}
int main()
{pthread_t p1;pthread_create(&p1, nullptr, threadRoutine, (void*)"thread 1");pthread_detach(p1);sleep(3);return 0;
}

3.5 线程退出

线程退出有三种方式:

  1. return,线程正常执行完退出
  2. pthread_exit, 谁调用该函数谁退出,retval为返回值。image.png
  3. pthread_cancel:让指定id的线程退出,退出码为PTHREAD_CANCELED(-1)image.png
3.6 获取线程id
  • pthread_self:可以获取当前线程的id

image.png

#include<iostream>
#include<pthread.h>
#include <unistd.h>
#include <cstring>using namespace std;void* threadRoutine(void* s)
{const char* str = (const char*)s;cout << str << endl;}
int main()
{pthread_t p1;pthread_create(&p1, nullptr, threadRoutine, (void*)"thread 1");pthread_detach(p1);cout << pthread_self() << endl;sleep(3);return 0;
}

3.7 线程id与LWP的区别

pthread库是共享库,也会被映射到进程地址空间中的共享区。在pthread库中创建的所有线程会被组织成数组。而线程id就是数组中线程的地址。LWP是轻量级进程使用的,被封装进struct pthread字段中。这种结构类似文件,struct FILE中封装了文件描述符fd。
image.png

  • 每一个线程都拥有自己的栈,主线程的栈是进程地址空间的栈,而其他线程的栈都在共享区中
  • 所有线程共享大部分数据,如果创建了一个全局变量,但是想让每个线程都拥有这个变量,那么就需要在前面加__thread,然后每个线程都会在自己的局部存储里面创建这个全局变量。其生命周期是全局的。
#include<iostream>
#include<pthread.h>
#include <unistd.h>
#include <cstring>using namespace std;
__thread int _gval = 0;void* threadRoutine(void* s)
{const char* str = (const char*)s;cout << str << "_gval: " << _gval << " &_gval: " << &_gval << endl;  
}void* threadRoutine1(void* s)
{sleep(3);const char* str = (const char*)s;cout << str << "_gval: " << _gval << " &_gval: " << &_gval << endl;  
}
int main()
{pthread_t p1, p2;pthread_create(&p1, nullptr, threadRoutine, (void*)"thread 1");pthread_create(&p2, nullptr, threadRoutine1, (void*)"thread 2");sleep(4);return 0;
}
  • 上面两个线程打印出来的地址一定不同。

四.线程互斥

  • 临界资源:多个执行流共享的资源就是临界资源
  • 临界区:访问临界资源的代码就是临界区
  • 互斥:任一时刻,只有一个执行流进入临界区。
  • 原子性:一个操作只有两种状态,要么还没开始,要么就已完成。

在多线程的场景下,不同线程访问临界资源会引发线程安全的问题,例如:对临界资源做自增操作。

#include<iostream>
#include<pthread.h>
#include <unistd.h>
#include <cstring>int _gval = 0;void* threadRoutine(void* s)
{	int cnt = 0;while (cnt < 100){cnt++;}
}int main()
{pthread_t p1, p2;pthread_create(&p1, nullptr, threadRoutine, nullptr);pthread_create(&p2, nullptr, threadRoutine1, nullptr);sleep(4);   return 0;
}
  • cnt++,这一条代码会被解释为三条汇编指令:image.png

image.png
如果一个线程执行到第二步的时候时间片到了,切换为另一个线程,然后再切换回第一个线程,那么第二个线程对cnt的操作就会被覆盖。如果要保证对cnt的操作是线程安全的,即没有并发访问问题,就需要对该临界区加锁,让临界区任何时刻只能有一个线程进入。

4.1 互斥锁接口

锁的接口定义在pthread.h头文件中。

  1. 定义

pthread_mutex_t mutex;

  • 定义mutex变量
  1. 初始化

image.png

  • pthread_mutex_t* mutex :传入外部定义的锁。
  • const pthread_mutexattr_t* attr:锁的属性,一般设置为nullptr
  1. 销毁

image.png

  • pthread_mutex_t* mutex :传入外部定义的锁

如果定义的锁是全局变量,可以使用PTHREAD_MUTEX_INITIALIZER初始化,不需要调用pthread_mutex_init() 和 pthread_mutex_destroy()

  1. 加锁

image.png

  1. 解锁

image.png

  1. 代码示例
#include<iostream>
#include<pthread.h>
#include <unistd.h>
#include <cstring>int _gval = 0;// 写法一;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* threadRoutine(void* s)
{	int cnt = 0;pthread_mutex_lock(&mutex);while (cnt < 100){cnt++;pthread_mutex_unlock(&mutex);}}int main()
{pthread_t p1, p2;pthread_create(&p1, nullptr, threadRoutine, nullptr);pthread_create(&p2, nullptr, threadRoutine1, nullptr);sleep(4);   return 0;
}// 写法二:void* threadRoutine(void* s)
{	pthread_mutex_t* mutex = (pthread_mutex_t*)s;int cnt = 0;pthread_mutex_lock(mutex);while (cnt < 100){cnt++;pthread_mutex_unlock(mutex);}}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);pthread_t p1, p2;pthread_create(&p1, nullptr, threadRoutine, (void*)&mutex);pthread_create(&p2, nullptr, threadRoutine1, (void*)&mutex);pthread_mutex_destroy(&mutex);sleep(4);   return 0;
}

4.2 锁的原理

锁也是共享资源,访问共享资源就会有线程安全问题。为了防止这种套娃情况的发生,加锁和解锁操作具有原子性,大多数体系结构都提供了swap或exchange指令,这两条指令都保证了加锁的原子性。

加锁: 伪代码
lock:
movb $0, %al
xchgb %al, mutex ;这条指令是核心,将锁转移到当前线程的上下文中,这也就相当于线程取得了锁资源
if al > 0 then return 0
else 挂起等待
goto lock
解锁:
movb $1 ,mutex
唤醒等待mutex的线程
return 0;

在上面代码中,1不会增多,只会在不同线程之间流转,从而保证了多个线程只有一个锁资源

4.3 死锁
  死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。<br />**四个条件之一:**
  1. 互斥
  2. 请求与保持:线程请求资源的过程中,不放弃持有资源
  3. 不剥夺:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  4. 环路等待:若干执行流之间形成一种头尾相接的循环等待资源的关系

预防死锁:破坏四个条件之一

  1. 不加锁可以破坏互斥条件
  2. 主动释放锁可以破坏请求与保持条件
  3. 控制线程释放锁资源可以破坏不剥夺条件
  4. 按照顺序申请锁可以破坏环路等待条件

五.线程同步

如果一个线程释放锁之后又立即申请锁,那么可能会导致其他线程一直申请不到锁,这样就会形成饥饿问题。这样明显是不合理的,我们可以使用条件变量来防止发生饥饿问题。一般条件变量是配合互斥锁使用的。条件变量的接口定义在pthread.h头文件中。

5.1 条件变量
  1. 定义

pthread_cond_t

  • 定义一个条件变量
  1. 初始化

image.png

  • pthread_cond_t* cond :传入外部定义的条件变量。
  • const pthread_condxattr_t* attr:条件变量的属性,一般设置为nullptr
  1. 销毁

image.png

  1. 阻塞

image.png

  • pthread_cond_t* cond:阻塞cond条件变量
  • pthread_mutex_t *mutex:当线程被阻塞时会释放锁,等到线程被唤醒时会重新申请锁
  1. 唤醒

image.png

  • 唤醒在条件变量下等待的线程有两种方式:1.唤醒一个线程 2.唤醒所有线程
  • pthread_cond_singal() :唤醒在条件变量cond下等待的一个线程
  • pthread_cond_broadcast() :唤醒在条件变量cond下等待的所有线程
5.2 posix信号量
  POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 POSIX可以用于线程间同步。信号量是资源的一种预定机制,将共享资源看作多份。信号量相关的接口在semaphore.h头文件中。
  1. 定义信号量

sem_t

  • 信号量类型,可以定义信号量
  1. 初始化

image.png

  • pshared:0表示线程间共享,非零表示进程间共享
  • value:信号量初始值,资源的数量
  • 当value=1时,为二元信号量,效果等同于互斥锁。
  1. 销毁

image.png

  1. 申请信号量资源

image.png

  • 对信号量初始值(value)-1
  1. 归还信号量资源

image.png

  • 对信号量初始值(value)+1

六.生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
image.png
如果我们想要维护好生产者消费者模型,就要研究3种关系,两种角色,一个缓冲区

  • 三种关系
    • 生产者与生产者:具有互斥关系
    • 消费者与消费者:具有互斥关系
    • 生产者与消费者:具有互斥和同步关系。
      • 同步:当缓冲区满时,让消费者消费,当缓冲区空时,让生产者生产
  • 两种角色
    • 生产者
    • 消费者
  • 一个缓冲区
    • 基于阻塞队列
    • 基于环形队列

生产者消费者模型的优点:

  1. 效率高
    1. 效率高体现在可以并发生产任务和并发处理任务
  2. 支持忙闲不均
    1. 生产者生产任务之后不必等消费者消费,而是将任务放到缓冲区,可以继续进行生产任务。
6.1 基于阻塞队列(BlockQueue)式的cp问题

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。

  • 主要用到互斥锁和条件变量

生产者可以生产一系列任务让消费者执行任务。下面是模拟的一个计算器任务。


#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>class Task
{
public:Task() = default;Task(int x, int y, char op) : _x(x), _y(y), _op(1, op){ }void run(){std::unordered_map<std::string, std::function<void(void)>> cal = {{"+", [this]{ this->_result = this->_x + this->_y; }},{"-", [this]{ this->_result = this->_x - this->_y; }},{"*", [this]{ this->_result = this->_x * this->_y; }},{"/", [this]{ if (this->_y == 0) this->_exitStatus = 2; this->_result = this->_x/this->_y; }},{"%", [this]{ if (this->_y == 0) this->_exitStatus = 1; this->_x%this->_y; }}};cal[_op]();}void formatExpress(){std::cout << "productor : " << _x << " " << _op << " " << _y << " = ?" << std::endl;}void formatRes(){std::cout << "consumer : " << _x << " " << _op << " " << _y << " = " << _result << std::endl;}int getExitCode(){return _exitStatus;}int getRes(){return _result;}private:int _x;int _y;std::string _op;int _result;int _exitStatus;
};
#pragma once
#include <iostream>
#include <pthread.h>
#include <memory>
#include <vector>
#include <queue>
#include <unistd.h>using std::cout;
using std::endl;const int defaultCapacity = 5;template <class T>
class BlockQueue
{public:BlockQueue(int cap = defaultCapacity): _cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_consumerCond, nullptr);pthread_cond_init(&_productorCond, nullptr);}bool isFull(){return _bq.size() == _cap;}void push(const T &in){// 生产者生产数据pthread_mutex_lock(&_mutex);// 如果缓冲区满,则生产者阻塞while (isFull()) pthread_cond_wait(&_productorCond, &_mutex);  _bq.push(in);/// 可以采用一定策略唤醒消费者消费资源pthread_cond_signal(&_consumerCond);pthread_mutex_unlock(&_mutex);}bool isempty(){return _bq.empty();}void pop(T* out){pthread_mutex_lock(&_mutex);while (isempty())pthread_cond_wait(&_consumerCond, &_mutex);*out = _bq.front();_bq.pop();pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_productorCond);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_consumerCond);pthread_cond_destroy(&_productorCond);}private:// 缓冲区std::queue<T> _bq;// 缓冲区容量int _cap;// 互斥锁pthread_mutex_t _mutex;// 条件变量:当缓冲区满的时候,生产者阻塞,当缓冲区空的时候,消费者阻塞pthread_cond_t _consumerCond;pthread_cond_t _productorCond;
};
#include "bQueue.hpp"
#include "Task.hpp"
#include <cstring>void *consume(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while (true){// 从缓存区拿资源Task t;bq->pop(&t);// 使用资源t.run();t.formatRes();sleep(1);}return nullptr;
}void *product(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);const char* s = "+-*/%";while (true){sleep(1);// 生产资源int x = rand()%10;   int y = rand()%10;   char op = s[rand()%strlen(s)];Task t(x, y, op);t.formatExpress();  // 将资源放到缓冲区中bq->push(t);}return nullptr;
}int main()
{srand(time(nullptr));int n = 5;BlockQueue<Task> *bq = new BlockQueue<Task>(n);std::vector<pthread_t> consumers(3);std::vector<pthread_t> productors(4);for (auto &pth : consumers){pthread_create(&pth, nullptr, consume, static_cast<void *>(bq));}for (auto &pth : productors){pthread_create(&pth, nullptr, product, static_cast<void *>(bq));}for (auto tid : consumers)pthread_join(tid, nullptr);for (auto tid : productors)pthread_join(tid, nullptr);return 0;
}

6.2 基于环形队列的cp问题

下面实现一种基于环形队列的生产者消费者模型,采用数组模型环形队列。
image.png
在阻塞队列中,我们将缓冲区看作一个整体使用,因此也只定义了一个互斥锁就可以保证线程安全。但在环形队列中,我们将缓冲区看成n个小空间使用,因此使用信号量,只要消费者和生产者不指向同一个小空间,它们就可以并发执行。
消费者关心的是数据个数,生产者关心的是剩余空间个数,因此我们可以定义两个信号量,当剩余空间为0时,阻塞生产者;当数据个数为0时,阻塞消费者。
生产者与生产者之间互斥关系要靠一个互斥锁,消费者与消费者互斥关系也要靠一个互斥锁,故需要定义两个互斥锁。不能设置成一个互斥锁,因为生产者与消费者之间除非所占空间相同,不然没有互斥关系。

#pragma once#include <iostream>
#include <pthread.h>
#include <memory>
#include <vector>
#include <queue>
#include <unistd.h>
#include <semaphore.h>using std::cout;
using std::endl;
const int N = 5;template <class T>
class RingQueue
{
public:RingQueue(int cap = N) : _cap(cap), _consumerIndex(0), _productorIndex(0), _vRingQueue(cap){pthread_mutex_init(&_consumerMutex, nullptr);pthread_mutex_init(&_productorMutex, nullptr);sem_init(&_spaceSem, 0, _cap);sem_init(& _dataSem, 0, 0);}void push(const T &in){// 生产者生产数据P(_spaceSem);lock(_productorMutex);_vRingQueue[_productorIndex++] = in;_productorIndex %= _cap;unlock(_productorMutex);V(_dataSem);}void pop(T *out){P(_dataSem);lock(_consumerMutex);*out = _vRingQueue[_consumerIndex++];_consumerIndex %= _cap;unlock(_consumerMutex);V(_spaceSem);}~RingQueue(){pthread_mutex_destroy(&_consumerMutex);pthread_mutex_destroy(&_productorMutex);sem_destroy(&_spaceSem);sem_destroy(&_dataSem);}private:void lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void unlock(pthread_mutex_t &mutex) {   pthread_mutex_unlock(&mutex);}void P(sem_t &sem){  sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}private:std::vector<T> _vRingQueue;int _cap; // 环形队列容量sem_t _spaceSem; // 生产者关系的是空间资源sem_t _dataSem;  // 消费者关心的是数据资源pthread_mutex_t _consumerMutex;pthread_mutex_t _productorMutex;// 消费者访问空间int _consumerIndex;// 生产者访问空间int _productorIndex;
};

七.常见概念

  1. STL中的容器不是线程安全的
  2. 智能指针是线程安全的
    1. unique_ptr:不涉及全局变量,只在局部有效
    2. shared_ptr:引用计数有线程安全问题,但是设计者采用了CAS操作,保证了线程安全
  3. 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  4. 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  5. 自旋锁:非阻塞的申请锁资源。如果访问临界区的时间短可以采用自旋锁。
    1. pthread_spinlock_t:可以定义自旋锁

相关文章:

Linux_线程

线程与进程 多级页表 线程控制 线程互斥 线程同步 生产者消费者模型 常见概念 下面选取32位系统举例。 一.线程与进程 上图是曾经我们认为进程所占用的资源的集合。 1.1 线程概念 线程是一个执行分支&#xff0c;执行粒度比进程细&#xff0c;调度成本比进程低线程是cpu…...

【selenium】

selenium是一个Web的自动化测试工具&#xff0c;最初是为网站自动化测试而开发的。Selenium可以直接调用浏览器&#xff0c;它支持所有主流的浏览器。其本质是通过驱动浏览器&#xff0c;完成模拟浏览器操作&#xff0c;比如挑战&#xff0c;输入&#xff0c;点击等。 下载与打…...

HX711压力传感器学习一(STM32)

目录 原理图&#xff1a;​ 引脚介绍&#xff1a; HX711介绍工作原理: 程序讲解&#xff1a; 整套工程&#xff1a; 发送的代码工程&#xff0c;与博客的不一致&#xff0c;如果编译有报错请按照报错和博客进行修改 原理图&#xff1a; 引脚介绍&#xff1a; VCC和GND引…...

作业2.13

1、选择题 1.1、若有定义语句&#xff1a;int a[3][6]; &#xff0c;按在内存中的存放顺序&#xff0c;a 数组的第10个元素是 D A&#xff09;a[0][4] B) a[1][3] C)a[0][3] D)a[1][4] 1.2、有数组 int a[5] {10&#xff0c;20&#xff0c;30&#xff0c;40&#xff0c;50},…...

ArcGIS学习(七)图片数据矢量化

ArcGIS学习(七)图片数据矢量化 通过上面几个任务的学习,大家应该已经掌握了ArcGIS的基础操作,并且学习了坐标系和地理数据库这两个非常重要且稍微难一些的专题。从这一任务开始,让我们进入到实战案例板块。 首先进入第一个案例一一图片数据矢量化。 我们在平时的工作学…...

G口大流量服务器选择的关键点有哪些?

G口服务器指的是接入互联网的带宽达到1Gbps以上的服务器&#xff0c;那么选择使用G口大流量服务器的用户需要注意哪些选择 关键点呢?小编为您整理关于G口大流量服务器的关键点。 G口服务器通常被用于需要大带宽支持的业务场景&#xff0c;比如视频流媒体、金融交易平台、电子商…...

MongoDB聚合:$unset

使用$unset阶段可移除文档中的某些字段。从版本4.2开始支持。 语法 移除单个字段&#xff0c;可以直接指定要移除的字段名&#xff1a; { $unset: "<field>" }移除多个字段&#xff0c;可以指定一个要移除字段名的数组&#xff1a; { $unset: [ "<…...

DS Wannabe之5-AM Project: DS 30day int prep day14

Q1. What is Alexnet? Q2. What is VGGNet? Q3. What is VGG16? Q4. What is ResNet? At the ILSVRC 2015, so-called Residual Neural Network (ResNet) by the Kaiming He et al introduced the anovel architecture with “skip connections” and features heavy b…...

【程序设计竞赛】C++与Java的细节优化

必须强调下&#xff0c;以下的任意一种优化&#xff0c;都应该是在本身采用的算法没有任何问题情况下的“锦上添花”&#xff0c;而不是“雪中送炭”。 如果下面的说法存在误导&#xff0c;请专业大佬评论指正 读写优化 C读写优化——解除流绑定 在ACM里&#xff0c;经常出现…...

Java缓冲流——效率提升深度解析

前言 大家好&#xff0c;我是chowley&#xff0c;在我之前的项目中&#xff0c;用到了缓冲流来提高字符流之间的比较速度&#xff0c;缓冲流的主要作用类似于数据库缓存&#xff0c;提高IO操作效率。 缓冲流 在Java的输入输出操作中&#xff0c;缓冲流是提高性能的重要工具之…...

16 亚稳态原理和解决方案

1. 亚稳态原理 亚稳态是指触发器无法在某个规定的时间段内到达一个可以确认的状态。在同步系统中&#xff0c;输入总是与时钟同步&#xff0c;因此寄存器的setup time和hold time是满足的&#xff0c;一般情况下是不会发生亚稳态情况的。在异步信号采集中&#xff0c;由于异步…...

C# OCR识别图片中的文字

1、从NuGet里面安装Spire.OCR 2、安装之后&#xff0c;找到安装路径下&#xff0c;默认生成的packages文件夹&#xff0c;复制该文件夹路径下的 6 个dll文件到程序的根目录 3、调用读取方法 OcrScanner scanner new OcrScanner(); string path "C:\1.png"; scann…...

使用python-numpy实现一个简单神经网络

目录 前言 导入numpy并初始化数据和激活函数 初始化学习率和模型参数 迭代更新模型参数&#xff08;权重&#xff09; 小彩蛋 前言 这篇文章&#xff0c;小编带大家使用python-numpy实现一个简单的三层神经网络&#xff0c;不使用pytorch等深度学习框架&#xff0c;来理解…...

CSS定位装饰

网页常见布局方式 标准流 块级元素独占一行---垂直布局 行内元素/行内块元素一行显示多个----水平布局 浮动 可以让原本垂直布局的块级元素变成水平布局 定位 可以让元素自由的摆放在网页的任意位置 一般用于盒子之间的层叠情况 使用定位步骤 设置定位方式 属性名&am…...

java之jvm详解

JVM内存结构 程序计数器 Program Counter Register程序计数器(寄存器) 程序计数器在物理层上是通过寄存器实现的 作用&#xff1a;记住下一条jvm指令的执行地址特点 是线程私有的(每个线程都有属于自己的程序计数器)不会存在内存溢出 虚拟机栈(默认大小为1024kb) 每个线…...

vue3学习——集成sass

安装 pnpm i sass sass-loader -D在vite.config.ts文件配置: export default defineConfig({css: {preprocessorOptions: {scss: {javascriptEnabled: true,additionalData: import "./src/styles/variable.scss";,},},},} }创建三个文件 src/styles/index.scss //…...

开关电源学习之Boost电路

如果我们需要给一个输入电压为5V的芯片供电&#xff0c;而我们只有一个3.3V的电源&#xff0c;那怎么办&#xff1f; 我们能不能把3.3V的电压升到5V&#xff1f; 一、电感的简介 而在升压的电路设计方案中&#xff0c;使用到一个重要的元器件&#xff1a;电感。 电感的特性…...

QRegExp的学习

【QT学习】QRegExp类正则表达式&#xff08;一文读懂&#xff09;-CSDN博客 [ ]:匹配括号内输入的任意字符 例&#xff1a;[123]:可以是1或2或3 {m&#xff0c;n}表达式至少重复m次&#xff0c;至多重复n次。 例&#xff1a;"ba{1,3}"可以匹配 "ba"或&…...

28.Stream流

Stream流 1. 概述2. 方法2.1 开始生成方法2.1.1 概述2.1.2 方法2.1.3 代码示例 2.2 中间操作方法2.2.1 概述2.2.2 方法2.2.3 代码示例 2.3 终结操作方法2.3.1 概述2.3.2 方法2.3.3 代码示例 2.4 收集操作方法2.4.1 概述2.4.2 方法2.4.3 代码示例 3. 代码示例14. 代码示例25. 代…...

大数据应用对企业的价值

目录 一、大数据应用价值 1.1 大数据技术分析 1.2 原有技术场景的优化 1.2.1 数据分析优化 1.2.2 高并发数据处理 1.3 通过大数据构建新需求 1.3.1 智能推荐 1.3.2 广告系统 1.3.3 产品/流程优化 1.3.4 异常检测 1.3.5 智能管理 1.3.6 人工智能和机器学习 二、大数…...

【51单片机】LED点阵屏(江科大)

9.1LED点阵屏 1.LED点阵屏介绍 LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。 2.LED点阵屏工作原理 LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。原理图如下 每一行的阳极连在一起,每一列…...

Microsoft OneNote 图片文字提取

Microsoft OneNote 图片文字提取 1. 文件 -> 新建 -> 我的电脑 -> 名称 -> 位置 -> 创建笔记本2. 插入图片​​​3. 复制图片中的文本References 1. 文件 -> 新建 -> 我的电脑 -> 名称 -> 位置 -> 创建笔记本 ​ 2. 插入图片 ​​​3. 复制图片…...

Linux系统安全——iptables相关总结

在使用iptables时注意要先关闭firewalld&#xff08;systemctl stop firewalld.service&#xff09; 1.查看iptables规则 iptables -vnL 选项含义-v查看时显示更多详细信息-n所有字段以数字形式显示-L查看规则列表 例&#xff0c;拒绝来自192.168.241.22的源地址 直接丢弃 …...

深度学习(14)--x.view()详解

在torch中&#xff0c;常用view()函数来改变tensor的形状 查询官方文档&#xff1a; torch.Tensor.view — PyTorch 2.2 documentationhttps://pytorch.org/docs/stable/generated/torch.Tensor.view.html#torch.Tensor.view示例 1.创建一个4x4的二维数组进行测试 x torch.…...

最新wordpress外贸主题

日用百货wordpress外贸主题 蓝色大气的wordpress外贸主题&#xff0c;适合做日用百货的外贸公司搭建跨境电商网站使用。 https://www.jianzhanpress.com/?p5248 添加剂wordpress外贸建站主题 橙色wordpress外贸建站主题&#xff0c;适合做食品添加剂或化工添加剂的外贸公司…...

Spring Cloud Gateway:使用RestController动态更新路由

相关类介绍 动态路由&#xff08;自己控制&#xff0c;非注册中心控制&#xff09;涉及两个很重要的Bean&#xff1a; RouteDefinitionWriter&#xff1a;用于添加、修改、删除路由规则。RouteDefinitionLocator&#xff1a;用于查询路由规则。 以及一个相关事件&#xff1a…...

用Python动态展示排序算法

文章目录 选择冒泡插入排序归并排序希尔排序 经常看到这种算法可视化的图片&#xff0c;但往往做不到和画图的人心灵相通&#xff0c;所以想自己画一下&#xff0c;本文主要实现归并排序和希尔排序&#xff0c;如果想实现其他算法可参考这篇 C语言实现各种排序算法[选择&#x…...

vscode代码快捷键

1、 log console.log()2、edf export default (first)>{ second } 或者 export default function(params)>{ }可以使用tab键切换修改项 3、ednf export default function first(second) {third}4、! 生成html模板 5、div#app <div id"app"></di…...

深入了解C++:形参、内联、重载、引用、const和指针、new和delete

形参带默认值的函数 1.给默认值的时候从右向左给。 2.定义出可以给形参默认值&#xff0c;声明也可以给形参默认值。 3.形参默认值只能出现一次。 4.参数调用的效率问题 #sum(10,20)对应了五条汇编指令 mov eax,dword ptr[ebp-8] push eax mov ecx dword ptr[ebp-4] push …...

Linux 目录结构结构

Linux 目录结构结构 概念 Linux 没有 C、D、E...盘符&#xff0c;只有一个目录树。通过挂载&#xff0c;将不同的磁盘挂载到目录树下&#xff0c;通过目录访问磁盘。 ‍ 不同目录的作用 目录存放内容/作用​/​根目录&#xff0c;目录树的起点&#xff0c;存放所有文件。​…...

C++基础入门:掌握核心概念(超全!)

C作为一门广泛使用的编程语言&#xff0c;以其高性能和灵活性在软件开发领域占据重要地位。无论是游戏开发、系统编程还是实时应用&#xff0c;C都是一个不可或缺的工具。本博客旨在为初学者提供C编程语言的核心概念&#xff0c;帮助你建立坚实的基础。 C关键字 C关键字是编程…...

Linux第47步_安装支持linux的第三方库和mkimage工具

安装支持linux的第三方库和mkimage工具&#xff0c;做好移植前的准备工作。 编译linux内核之前&#xff0c;需要先在 ubuntu上安装“lzop库”和“libssl-dev库”&#xff0c;否则内核编译会失败。 mkimage工具会在zImage镜像文件的前面添加0x40个字节的头部信息,就可以得到uI…...

数据工程工程师学习路线图

数据工程岗位要求 Skill Sets required: - Hands on experience enabling data via Adobe Analytics and/or Google Analytics - Understanding of how customer level data is captured and stitched with behavioural data - Experience working with Testing (QA) and D…...

MySQL主从同步与分库分表

分库分表...

百度PaddleOCR字符识别推理部署(C++)

1 环境 1.opencv&#xff08;https://sourceforge.net/projects/opencvlibrary/&#xff09; 2.cmake&#xff08;https://cmake.org/download/&#xff09; 3.vs2019&#xff08;(https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.1) 4.paddleOCR项目-建议2.0(http…...

C++ Qt框架开发 | 基于Qt框架开发实时成绩显示排序系统(2)折线图显示

对上一篇的工作C学习笔记 | 基于Qt框架开发实时成绩显示排序系统1-CSDN博客继续优化&#xff0c;增加一个显示运动员每组成绩的折线图。 1&#xff09;在Qt Creator的项目文件&#xff08;.pro文件&#xff09;中添加对Qt Charts模块的支持&#xff1a; QT charts 2&#xf…...

Microsoft Excel 加载数据分析工具

Microsoft Excel 加载数据分析工具 1. 打开 Excel&#xff0c;文件 -> 选项2. 加载项 -> 转到…3. 分析工具库、分析工具库 - VBA4. 打开 Excel&#xff0c;数据 -> 数据分析References 1. 打开 Excel&#xff0c;文件 -> 选项 2. 加载项 -> 转到… ​​​ 3…...

Day32 贪心算法part02

买卖股票的最佳时机 太牛了我&#xff0c;随随便便双指针秒杀 md题解里面双指针都没用直接for循环秒杀 跳跃游戏 写成这样纯粹是没有看到第一次跳跃必须从第一个开始 class Solution:def canJump(self, nums: List[int]) -> bool:if len(nums) 1:return Truefor i in …...

3分钟带你了解Vue3的nextTick()

前言 Vue 实现响应式并不是数据发生变化之后 DOM 立即变化&#xff0c;而是按一定的策略进行 DOM 的更新。简单来说&#xff0c;Vue在修改数据后&#xff0c;视图不会立刻更新&#xff0c;而是等同一事件循环中的所有数据变化完成之后&#xff0c;再统一进行视图更新&#xff…...

数据库的使用方法

sqlite3 API&#xff1a; 头文件&#xff1a; #include <sqlite3.h> 编译时候要加上-lsqlite3 gcc a.c -lsqlite3 1&#xff09;sqlite3_open int sqlite3_open(const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db …...

HTML5和CSS3强化知识总结

HTML5的新特性 HTML5的新增特性主要是针对于以前的不足&#xff0c;增一些新的标签、新的表单和新的表单属性等。这些新特性都有兼容性问题&#xff0c;基本是IE9以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性。 HTML5新增的语义…...

华为机考入门python3--(13)牛客13-句子逆序

分类&#xff1a;列表 知识点&#xff1a; 列表逆序&#xff08;和字符串逆序是一样的&#xff09; my_list[::-1] 题目来自【牛客】 def reverse_sentence(sentence): # 将输入的句子分割words sentence.split() # 将单词逆序排列 words words[::-1] # 将单词用空…...

javaScript实现客户端直连AWS S3(亚马逊云)文件上传、断点续传、断网重传

写在前面&#xff1a;在做这个调研时我遇到的需求是前端直接对接亚马逊平台实现文件上传功能。上传视频文件通常十几个G、客户工作环境网络较差KB/s&#xff0c;且保证上传是稳定的&#xff0c;支持网络异常断点重试、文件断开支持二次拖入自动重传等。综合考虑使用的Aws S3的分…...

从基建发力,CESS 如何推动 RWA 发展?

2023 年 11 月 30 日&#xff0c;Web3 基金会&#xff08;Web3 Foundation&#xff09;宣布通过 Centrifuge 将部分资金投资于 RWA&#xff08;Real World Assets&#xff0c;真实世界资产&#xff09;&#xff0c;试点投资为 100 万美元。Web3 基金会旨在通过支持专注于隐私、…...

qml写一个自适应登录框

1、前言 写一个可自由伸缩的登录框&#xff0c;&#xff0c;&#xff08;横向上&#xff09; 关键&#xff1a;给相关控件赋予 Layout.fillWidth: true 属性 即可。 2、代码 //main.qml import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQml 2.12 import QtQuic…...

考研高数(导数的定义)

总结&#xff1a; 导数的本质就是极限。 函数在某点可导就必连续&#xff0c;连续就有极限且等于该点的函数值。 例题1&#xff1a;&#xff08;归结原则的条件是函数可导&#xff09; 例题2&#xff1a; 例题3&#xff1a;...

ChatGPT在国际中文教育领域引起的变革与挑战

随着ChatGPT这一先进的自然语言处理模型的出现&#xff0c;教学、学习、测评和辅导的传统方式正在面临可能的重塑。她是否会影响中文教育的未来方向&#xff0c;甚至取代中文教师的角色&#xff0c;成为了许多人热议的话题。本文旨在探讨ChatGPT与中文测评之间的联系&#xff0…...

C语言—基础数据类型(含进制转换)

进制转换不多&#xff0c;但我觉得适合小白(我爱夸自己嘿嘿) 练习 1. 确认基础类型所占用的内存空间(提示&#xff1a;使用sizeof 运算符)&#xff1a; 在这里我说一下&#xff0c;long 类型通常占用 4 字节。在 64 位系统上&#xff0c;long 类型通常也可为 8 字节。 格式…...

警钟长鸣-合同问题

由于去年入职了某家公司&#xff0c;本来想着临时过渡一下&#xff0c;虽然签的时候发现合同和竞业协议存在很明显的问题或者说好听点“限制比较严&#xff1f;”&#xff0c;大部分互联网公司都成了它的假想敌&#xff0c;但是本着来都来了的想法就签了&#xff0c;于是导致发…...

CAN通讯协议学习

介绍 它是一种异步通讯&#xff0c;can_high和can_low两条线利用的是电位差传输信号&#xff0c;抗干扰能力强&#xff0c;但是必须要有can控制器如TJA1050&#xff08;我的开发板&#xff09; 当 CAN 节点需要发送数据时&#xff0c;控制器把要发送的二进制编码通过 CAN_Tx 线…...