【Linux】基础:线程的同步与互斥

news2024/10/6 2:20:03

【Linux】基础:线程的同步与互斥

摘要:本文主要介绍线程的同步与互斥方面的内容,分为理论与实现两部分完成。首先从整体上介绍线程同步与互斥相关概念,在理解概念后对两者分开介绍。在互斥方面,主要介绍内容为互斥量的接口与实现原理,并引申为死锁和线程安全等拓展相关内容,在线程同步方面,主要介绍了条件变量与信号量的接口与实现,由于该部分篇目较大,对于同步与互斥的应用将另起一文,主要介绍为生产者消费者模型、线程池和读者写者问题。


文章目录

  • 【Linux】基础:线程的同步与互斥
    • 一、相关概念
    • 二、线程互斥
      • 2.1 概述
      • 2.2 场景模拟
      • 2.3 问题分析
      • 2.4 互斥量
        • 2.4.1 相关接口
        • 2.4.2 代码优化
        • 2.4.3 互斥量原理
      • 2.5 线程安全与可重入函数
        • 2.5.1 线程安全
        • 2.5.2 可重入函数
        • 2.5.3 关系比较
      • 2.6 死锁
    • 三、线程同步
      • 3.1 条件变量
        • 3.1.1 相应接口
        • 3.1.2 示例
      • 3.2 信号量
        • 3.2.1 概述
        • 3.2.2 相关接口

一、相关概念

在进程通信一文中,对相关概念进行了介绍,在本文中,再进行介绍一次:

  • 临界资源:方式被形成线程共享访问的资源都是临界资源,比如多线程/多进程数据打印到显示器中
  • 临界区:访问临界资源的代码,当然不是所有的代码都是访问临界资源的
  • 临界区保护:本质是对临界资源保护,通过互斥与同步的方式
  • 原子性:一件事要么做完,要么不做,不存在中间状态
  • 互斥:在任意时刻,只允许一个执行流访问某段代码(访问某部分资源),称之为互斥
  • 同步:一般而言,让访问临界资源的过程在安全的前提下(一般都是互斥和原子的),让访问资源具有一定的顺序性,具有合理性

二、线程互斥

2.1 概述

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,即临界资源,可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量,会带来一些问题。此时可以通过使用互斥量来解决该问题,下面将会对其场景进行搭建,并介绍Linux下的各种接口。

2.2 场景模拟

在此通过一个抢票场景进行说明,如下代码为主线程创建五个子线程,并通过子线程抢占临界资源1000张票,观察抢票结果,代码示例如下:

#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>

using namespace std;

// 临界资源:1000张票
int tickets = 1000;
#define NUM 5

void* ThreadRoutine(void* args){
    int id = *(int*)args;
    delete (int*)args;

    while(true){
        if(tickets > 0){
            usleep(1000);
            cout<<"Thread NO."<< id << " get NO." << tickets << endl;
            tickets--;
        }
        else
            break;
    }
}

int main(){
    pthread_t tid[NUM];
    // 创建线程
    for(int i = 0; i < NUM; i++){
        int *id = new int(i);
        pthread_create(tid + i, nullptr , ThreadRoutine , (void*)id);
    }
    // 等待线程
    for(int i = 0; i < NUM; i++){
        pthread_join(tid[i],nullptr);
    }
    return 0;
}
......
Thread NO.2 get NO.1
Thread NO.0 get NO.0
Thread NO.3 get NO.-1
Thread NO.4 get NO.-2
Thread NO.1 get NO.-3

2.3 问题分析

可以发现无法的到正确的结果,实际上就是由于各个线程对于临界资源的错误访问导致的,在以上程序中主要由于线程在CPU中执行时是会设置时间片的,在判断成功后可能会发生线程的切换,在usleep()中是需要时间的,在业务过程中,有许多进程已经进入该代码段。而由于ticket--操作不是一个原子操作,因此是不安全的。

为此对于ticket--操作不是一个原子操作导致错误的过程进行解释,该语句转换为汇编语言可以将其转换为三个过程,分别为将ticket数据从内存写入到CPU的寄存器中,再将在寄存器中的数据进行减减操作,最后将寄存器的数据写回内存中。

可是在多线程的情况下,有线程AB两个线程。假设有线程A,在内存数据写入寄存器抢票后,时间片已到,发生线程切换,此时的ticket将在线程A得到数据结构中保持该上下文,即保存了线程A的ticket。此时将线程B竞争CPU成功,获取CPU资源,并不断进行着抢票操作,此时票数将会减少。可是当线程A的数据写入内存时,此时的ticket将会做出不一致的修改,这样就导致了售票的错误。示意图如下:

2.4 互斥量

因此,上述场景错误的原因就是在访问临界资源时,并没有进行原子访问,而在此介绍Linux中的互斥量,更具体为互斥锁,实现对临界资源的安全访问。

2.4.1 相关接口

互斥量初始化与销毁

头文件:

#include <pthread.h>

定义:

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

说明:

  • int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁。
  • 销毁互斥量需要注意:使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁;不要销毁一个已经加锁的互斥量;已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

互斥量加锁和解锁

头文件:#include <pthread.h>

定义:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

说明:

  • 调用 pthread_ lock 时,可能会遇到以下情况:互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

2.4.2 代码优化

通过互斥量,将抢票过程变成原子的过程,保证了线程的安全,同时使得访问临界资源时互斥的,程序逻辑示意图如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
// 售票类
class Ticket{
private:
    int ticketNum;
    pthread_mutex_t mtx;
public:
    // 构造与析构函数
    Ticket():ticketNum(1000){
        // 互斥量初始化
        pthread_mutex_init(&mtx,nullptr);
    }

    ~Ticket(){
        // 互斥量销毁
        pthread_mutex_destroy(&mtx);
    }
    // 抢票
    bool GetTicket(){
        bool ret = true;
        // 加锁
        pthread_mutex_lock(&mtx);
        if(ticketNum > 0){
            usleep(1000);
            std::cout << "New Thread TIC:" << pthread_self() << " get NO." << ticketNum << " ticket"<<std::endl;
            ticketNum--;
        }
        else{
            std::cout<< "Ticket Empey" <<std::endl;
            ret = false;
        }
        // 解锁
        pthread_mutex_unlock(&mtx);
        return ret;
    }
};

void* ThreadRoutine(void *args){
    Ticket *t = (Ticket*)args;
    while(true){
        if(!t->GetTicket()){
            break;
        }
    }
}

int main(){
    Ticket *t = new Ticket();

    pthread_t tid[5];

    for(int i = 0;i < 5;i++){
        pthread_create(tid + i,nullptr,ThreadRoutine,(void*)t);
    }

    for(int i = 0;i < 5;i++){
        pthread_join(tid[i],nullptr);    
    }
    return 0;
}
New Thread TIC:140305938736896 get NO.4 ticket
New Thread TIC:140305938736896 get NO.3 ticket
New Thread TIC:140305938736896 get NO.2 ticket
New Thread TIC:140305938736896 get NO.1 ticket
Ticket Empey
Ticket Empey
Ticket Empey
Ticket Empey
Ticket Empey

在C++11也内置了mutex互斥量,在此不过多介绍,通过代码进行实例,有兴趣可以自行查阅资料,示例如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
// C++ 11 mutex
#include <mutex>

// 售票类
class Ticket{
private:
    int ticketNum;
    // pthread_mutex_t mtx;
    std::mutex mtx;
public:
    // 构造与析构函数
    Ticket():ticketNum(1000){
        // 互斥量初始化
        // pthread_mutex_init(&mtx,nullptr);
    }

    ~Ticket(){
        // 互斥量销毁
        // pthread_mutex_destroy(&mtx);
    }
    // 抢票
    bool GetTicket(){
        bool ret = true;
        // 加锁
        // pthread_mutex_lock(&mtx);
        mtx.lock();
        if(ticketNum > 0){
            usleep(1000);
            std::cout << "New Thread TIC:" << pthread_self() << " get NO." << ticketNum << " ticket"<<std::endl;
            ticketNum--;
        }
        else{
            std::cout<< "Ticket Empey" <<std::endl;
            ret = false;
        }
        // 解锁
        // pthread_mutex_unlock(&mtx);
        mtx.unlock();
        return ret;
    }
};

void* ThreadRoutine(void *args){
    Ticket *t = (Ticket*)args;
    while(true){
        if(!t->GetTicket()){
            break;
        }
    }
}

int main(){
    Ticket *t = new Ticket();

    pthread_t tid[5];

    for(int i = 0;i < 5;i++){
        pthread_create(tid + i,nullptr,ThreadRoutine,(void*)t);
    }

    for(int i = 0;i < 5;i++){
        pthread_join(tid[i],nullptr);    
    }
    return 0;
}

2.4.3 互斥量原理

在上述代码中,访问了临界资源的ticket时,首先需要访问互斥量,前提是需要全部线程看见该互斥量,但是互斥量本身是否是临界资源呢,本身是否是安全的呢,实际锁的上锁和解锁都是原子的,为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性。即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

以下通过相应的汇编伪代码进行说明:

lock:
	movb $0 %al
	xchgb %al ,mutex
	if(al寄存器的数据>0){
		return 0;
	}
	else{
		挂起等待;
	}
	goto lock;
unlock:
	movb $1 mutex
	唤醒等待Mutex的线程;
	return 0;

在CPU执行线程代码时,CPU内寄存器的数据,是线程私有的,为执行流的上下文数据。因此当互斥锁未上锁时,可以有线程将0写入对应CPU寄存器并与互斥锁寄存器进行交换,当线程时间片到时,线程保持上下文,同时将原来寄存器的互斥锁也会被该线程抱走,而此时CPU在寄存器的数据为0,因此其他线程必须挂起等待。

当发生解锁时,将原来mutex的值复原,让其它进程唤醒并竞争锁。

为此,互斥量的本质是通过一条汇编代码,将锁交换到直接的上下文中

示意图如下:

2.5 线程安全与可重入函数

2.5.1 线程安全

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

常见的线程不安全的情况

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

常见的线程安全的情况

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

2.5.2 可重入函数

重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

常见不可重入的情况

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

常见可重入的情况

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

2.5.3 关系比较

可重入与线程安全联系

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

可重入与线程安全区别

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

2.6 死锁

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

死锁四个必要条件

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

避免死锁

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

避免死锁算法

  • 死锁检测算法(了解)
  • 银行家算法(了解)

三、线程同步

同步,一般而言,让访问临界资源的过程在安全的前提下(一般都是互斥和原子的),让访问资源具有一定的顺序性,具有合理性。本文将会介绍两种主要方式,分别为条件变量与信号量机制。

3.1 条件变量

一般而言,在只有锁的情况下,是比较难察觉临界资源的状态,当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,无法继续进行,例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中,此时需要用到条件变量来完成线程的同步机制。

3.1.1 相应接口

初始化与销毁

头文件:#include <pthread.h>

定义:

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

说明:

  • pthread_cond_init:初始化一个条件变量,当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。调用 pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。
  • pthread_cond_destroy:条件变量的销毁

等待

头文件:#include <pthread.h>

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

参数:

  • cond:要在该条件变量上进行等待
  • mutex:互斥量,当进行等待时需要进行释放互斥量,当被唤醒是重新竞争互斥量

唤醒

头文件:#include <pthread.h>

定义:

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

说明:

  • pthread_cond_signal:发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
  • pthread_cond_broadcast():函数会将所有等待该条件变量的线程解锁而不是仅仅解锁一个线程

3.1.2 示例

以下通过一个线程控制其他线程启停的示例,创建一个主线程以及三个子线程,主线程通过条件变量进行对三个子线程进行唤醒,让其进行工作,代码示例如下:

#include <iostream>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

using namespace std;
#define NUM 3

pthread_mutex_t mtx;
pthread_cond_t cond;

void *ctrl(void* args){
    string name = (char*) args;
    while(true){
        // 唤醒
        cout << "Master Pthread ----> Worker work" << endl;
        pthread_cond_signal(&cond);
        sleep(1);
    }
}

void *work(void* args){
    // 工作与等待
    int number = *(int *)args;
    delete (int*)args;
    while(true){
        pthread_cond_wait(&cond,&mtx);
        cout << "NO." << number <<" Worker" << " is working" <<endl;
    }
}

int main(){
    pthread_t master;
    pthread_t worker[NUM];

    pthread_mutex_init(&mtx,nullptr);
    pthread_cond_init(&cond,nullptr);

    // 线程创建
    pthread_create(&master, nullptr,ctrl,(void *)"master");
    for(int i = 0; i < NUM ;i++){
        int *id = new int(i);
        pthread_create(worker + i, nullptr,work,(void *)id);
    }

    for(int i = 0; i < NUM ;i++){
        pthread_join(worker[i], nullptr);
    }
    pthread_join(master, nullptr);

    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);

    return 0;
}
[root@VM-12-7-centos Blog_cond]# ./test_cond 
Master Pthread ----> Worker work
NO.1 Worker is working
Master Pthread ----> Worker work
NO.2 Worker is working
Master Pthread ----> Worker work
NO.1 Worker is working
Master Pthread ----> Worker work
NO.0 Worker is working
Master Pthread ----> Worker work

3.2 信号量

3.2.1 概述

信号量的本质就是一种计数器,描述临界资源的大小,也可以认为是最多有多少资源可以分配给线程。可不仅如此,信号量还存在一种预定的功能,即临界资源可以划分为一个个小的资源,如果处理得当,可以让多个线程同时访问临界资源的不同区域,从而实现并发操作。也可以称为多线程预定资源的手。

对于信号量的操作可以分为P()V()操作,P操作为申请信号量操作,V操作为释放信号量,两者操作通过封装可以成为原子操作,伪代码实例如下:

// P()
start:
	lock();
	if(count <= 0){
		// 挂起
		unlock();
		goto start;
	}
	else{
		count--;
	}
	unlock();
// V()
start:
	lock();
	count++;
	unlock();

3.2.2 相关接口

头文件:#include <semaphore.h>

定义:

int sem_init(sem_t *sem, int pshared, unsigned int value);	// 初始化信号量
int sem_destroy(sem_t *sem);	// 信号量销毁
int sem_wait(sem_t *sem);	// 等待信号量
int sem_post(sem_t *sem);	// 发布信号量

说明:

  • sem_init:sem为指向信号量结构的一个指针,pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享,value给出了信号量的初始值
  • sem_wait:等待信号量,会将信号量的值减1,相当于P()操作
  • sem_post:发布信号量,表示资源使用完毕,可以归还资源了,将信号量值加1,相当于V()操作

补充:

  1. 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!

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

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

相关文章

LinuxC—线程

线程 1 线程的基本概念 什么是线程 进程其实是一个容器&#xff0c;当我们在编程的时候实际上是在以线程为单位进行编程&#xff0c;包括处理器的调度也是以线程为单位的&#xff0c;一个进程可以有多个线程&#xff0c;一个进程的多个线程共享相同的进程空间&#xff0c;所以…

设计模式 - 创建型模式_抽象工厂模式

文章目录创建型模式概述Case场景模拟工程模拟早期单机Redis的使用Bad ImplBetter Impl &#xff08;抽象⼯⼚模式重构代码&#xff09;定义适配接⼝实现集群适配器接口代理方式的抽象工厂类单元测试小结创建型模式 创建型模式提供创建对象的机制&#xff0c; 能够提升已有代码…

0、Spring工程构建Spring快速入门Spring配置文件详解注入Sprint相关API

1、Spring工程构建 创建工程项目目录文件夹 IDEA选择项目new一个module 配置案例 aop创建 创建并下载完毕后&#xff0c;点击file选择projert 选择按照的jdk版本 output选择当前目录&#xff0c; 点击右下方apply 选择facets&#xff0c;点击""号选择web 选择当前…

Pinia状态管理

1、Pinia和Vuex的对比 1.1、什么是Pinia呢&#xff1f; Pinia&#xff08;发音为/piːnjʌ/&#xff0c;如英语中的“peenya”&#xff09;是最接近pia&#xff08;西班牙语中的菠萝&#xff09;的词&#xff1b; Pinia开始于大概2019年&#xff0c;最初是作为一个实验为Vue…

Linux使用操作

文章目录各类小技巧&#xff08;快捷键&#xff09;软件安装systemctl软连接日期、时区IP地址、主机名IP地址和主机名虚拟机配置固定IP网络传输下载和网络请求端口进程管理主机状态环境变量上传、下载压缩、解压各类小技巧&#xff08;快捷键&#xff09; 强制停止 Linux某些程…

python语法 dot函数

dot是numpy里的函数&#xff0c;主要用于求向量相乘&#xff0c;矩阵乘法&#xff0c;矩阵与向量乘法一、一维向量相乘要求元素个数相同&#xff0c;相当于求内积&#xff0c;对应元素相乘再相加&#xff0c;“1*3 2*4 11”二、矩阵和矩阵相乘遵循矩阵乘法法则“左行 * 右列”…

高通平台开发系列讲解(WIFI篇)什么是WLAN无线局域网

文章目录 一、什么是WLAN1.1、WLAN发展史1.2、WLAN工作频段二、高通相关文件2.1、配置文件2.2、开机启动2.3、wpa_supplicant沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本文将基于高通平台介绍什么是无线局域网。 一、什么是WLAN 在WLAN领域被大规模推广和商用的是…

【编程入门】开源记事本(鸿蒙Java版)

背景 前面已输出多个系列&#xff1a; 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 本系列对比云笔记&#xff0c;将更为简化&#xff0c;去掉了网络调用&#xff0…

WebSocket 入门:简易聊天室

大家好&#xff0c;我是前端西瓜哥&#xff0c;今天我们用 WebSocket 来实现一个简单的聊天室。 WebSocket 是一个应用层协议&#xff0c;有点类似 HTTP。但和 HTTP 不一样的是&#xff0c;它支持真正的全双工&#xff0c;即不仅客户端可以主动发消息给服务端&#xff0c;服务…

基于Tkinter制作简易的串口bootloader上位机

文章目录前言1.测试设备1.1 UART Bootloaer软件架构图1.2 UART Bootloader流程图1.3 通信数据处理1.3.1 S19文件的简单介绍1.3.2 S19文件的传输方式1.3.2 接收数据之后的处理1.4 链接文件设置1.4.1 Bootloader设置1.4.2 Application设置2.上位机2.1 参考资料2.2 Tkinter简介2.3…

C++初阶:vector类

文章目录1 vector介绍2 实现vector2.1 类的定义2.2 默认成员函数2.2.1 构造函数2.2.2 析构函数2.2.3 拷贝构造2.2.4 赋值重载2.3访问接口2.4 容量接口2.5 修改接口2.5.1 尾插尾删2.5.2 任意位置插入2.5.3 任意位置删除2.6 其他接口1 vector介绍 1 vector是表示可变大小数组的序…

每日学术速递1.26

CV - 计算机视觉 今天带来的是北航IRIP实验室被国际人工智能联合会议IJCAI-ECAI 2022接收的3篇论文。 IJCAI 是人工智能领域中最主要的学术会议之一&#xff0c;原为单数年召开&#xff0c;自2015年起改为每年召开&#xff0c;本次IJCAI与ECAI一起召开。IJCAI官网显示&#xf…

【Linux】冯诺依曼体系结构与操作系统概念理解

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;Linux 文章目录一、前言二、冯诺依曼体系结构1、体系简述2、内存的重要性3、硬件方案解释软件行为4、体系结构中的数据流动5、拓展三、操作系统简述…

ch1 操作系统启动

lab1 实验准备 按照实验解压后进入oslab中&#xff0c;按照make编译。 cd /home/shiyanlou/oslab/ tar -zxvf hit-oslab-linux-20110823.tar.gz \-C /home/shiyanlou/ ./run cd ./linux-0.11/ make all make clean ..... make all运行脚本即可启动内核 调试 汇编级调试和C语…

贪心算法的题目

每一步都做出一个局部最优的选择&#xff0c;最终的结果就是全局最优 只有一部分问题才能用贪心算法&#xff08;严格来讲&#xff0c;一个问题能不能用贪心算法需要证明的&#xff09; 2022.8.30 蔚来笔试题&#xff1a; 有a个y,b个o,c个u,用这些字母拼成一个字符串&#xf…

Anaconda软件中的 Environments 及 Jupyter Lab使用方法介绍

来源&#xff1a;投稿 作者&#xff1a;助教-Frank 编辑&#xff1a;学姐 本篇是打造舒适的AI开发环境系列-软件篇1 上期内容&#xff1a;学人工智能电脑&主机八大件配置选择指南 本文的重点&#xff1a; (1)Environments使用中如何安装python包.; (2)Jupyter Lab如何在…

Kettle(6):表输入组件——mysql转mysql

1 需求 前面我们已经将Excel中数据抽取到了MySQL的t_user表中。 现在有了新需求&#xff0c;要将MySQL数据库中的 t_user 表中的数据抽取出来&#xff0c;装载到另外一张表 t_user1中。 2 构建Kettle数据流图 2.1 从核心对象的输入组件中&#xff0c;将「表输入」组件拖拽到中…

电脑下载软件用什么软件好?安卓手机下载软件用哪个软件好?IDM下载器说:在做的都是弟弟

大年初五&#xff0c;迎财神&#xff0c;先祝大家新的一年财源滚滚&#xff0c;接下来为大家分享超级经典的IDM下载器&#xff0c;电脑端毫无争议的下载工具&#xff0c;安卓平台idm也是力压群雄&#xff0c;下面就为大家详细分享下&#xff1a; 1&#xff1a;1DM下载器&#x…

微服务统一登陆认证怎么做

[微服务统一登陆认证怎么做}&#xff1f;JWT 无状态登录原理 1.1.什么是有状态&#xff1f; 有状态服务&#xff0c;即服务端需要记录每次会话的客户端信息&#xff0c;从而识别客户端身份&#xff0c;根据用户身份进行请求的处理&#xff0c;典型的设计如tomcat中的session…

notepad++在行首行尾添加字符 | 选中列

目录 1、首行/尾行添加字符 1【使用快捷键 CtrlH】 2【^为行首、$为行尾】 3、查找模式选中正则表达式 2、Notepad中列选(竖选&#xff09; 1、首行/尾行添加字符 1【使用快捷键 CtrlH】 或者鼠标 2【^为行首、$为行尾】 3、查找模式选中正则表达式 2、Notepad中列选(竖…