线程,互斥锁,临界区

news2024/11/28 16:42:40

目录

  • 1.线程概念
  • 2.windows的线程和linux的线程的区别
  • 3虚拟地址到地址空间的转换
  • 4.线程优缺点
    • 1.优点
    • 2.缺点
  • 5.进程控制
    • 1.创建线程
    • 2.线程出现异常了怎么办?进程健壮性问题
    • 3.join的第二参数如何理解
    • 4.线程终止时
    • 6.如果理解pthread_t
    • 7.三个概念
  • 6.互斥锁
    • 1.关于临界区的一点问题
      • 1.我们临界资源对应的临界区被锁了,可以被切换吗?
      • 2.加锁完后线程被切换会怎么样?
    • 2.线程加锁和解锁具有原子性如何实现?
      • 1.了解概念
      • 2.第一种情况
      • 3.第二种情况
      • 3.解锁
  • 7.简单实现锁

1.线程概念

创建了一个进程,他进程的图大概如下
在这里插入图片描述

如果我们再创建新的进程
我们出现新的,下面图片的tasks truct(PCB),进程地址空间(PCD),页表等等,如果我们进行了程序替换操作系统还会帮我们把代码look到物理内存,帮我们代码和物理内存的建立映射关系
在这里插入图片描述

那么fork之后,父子是共享代码打的,可以if else 判断,让父子进程执行不同的代码块,等于不同的执行流,可以做到对特定资源的划分,因为进程具有独立性,所以我把进程2给清除掉,也不会影响到进程1

那么我们可以创建进程,我们创建PCB,但它们不具有单独的进程地址空间和页表,它们指向的是同一个进程地址空间

在这里插入图片描述
那么上面三个PCB,分别占用进程低地址空间的一小部分代码和数据,页表也分别有一小部分占用,那么这个执行流就叫线程,所以线程也是进程内部运行的执行流

2.windows的线程和linux的线程的区别

线程和进程的关系是 n比1的,所以操作系统要管理线程,先描述在组织,所以就有了TCB,这里TCB和PCB有一个理念的问题,如果他是真正实现了真线程的平台,比如Windows,那么他就要设计进程和线程的结构体,并且很多线程在进程内部,你还要维护它们之间的关系,这会导致它们的耦合性变得特别高,会对于维护之间变得成本特别高,为什么他要这么设计,因为设计他的人,他认为进程和线程在执行流之间是不一样的,而设计linux的人他认为没有进程没有线程,他认为只有一个叫做执行流,因为他认为进程只是比线程的代码和数据多一些,线程只是比进程的代码和数据少一些,所以觉得没有区别,linux的线程使用PCB模拟的,这里有个问题,linux有没有TCB,答案是有的,因为他就是PCB,就是说TCB和PCB就是一回事,只是他没有专门设计TCB,他只是复用了PCB,用它来表示执行流的概念
那么从cpu的来看到的所有的task_struct 都是一个进程
那么从cpu的来看到的所有的task_struct 都是一个执行流(线程)

以前我们以为进程只是task_strruct,那么我们重新来了解下进程

进程 = 内核数据结构 + 进程对应的代码和数据
进程 = 内核视角 + 承担分配系统资源的基本实体
进程 = 向系统申请资源的基本单位
内部只有一个进程 = 单执行流进程
内部有多个进程 = 多执行流进程

3虚拟地址到地址空间的转换

cpu通过查找物理内存的代码的地址是虚拟地址如果是32位系统下,他的虚拟地址数是32位的,虚拟地址到物理内存不是直接转换,而是划分为 10 10 12
虚拟地址的前十位,存在页目录(页目录只有一个)
虚拟地址中间十位 存在1级页表
虚拟地址的后十二位 做页内偏移量,能覆盖页内的所有地址
在这里插入图片描述

根据虚拟地址前十位在页目录做映射找到1级页表,再根据虚拟地址中间十位,搜索页表,再二级页表找到相对应的物理内存,再通过虚拟地址的后十位位,做偏移量,找到你想要的数据,进行读取

这样做有什么好处?
1.进程虚拟地址空间管理和内存管理,通过页表+page进程解耦,也就是说不关心页内的细节,只关心page在不在
2.页表分离了,可以实现页表的按序获取,比如页目录有的条目他是暂时不用的,那么我们等用的时候再创建这个页表,不用的时候不创建,用的时候再创建,所以等于节省空间

4.线程优缺点

1.优点

创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

2.缺点

1.性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。
2.健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3.缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4.编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

5.进程控制

1.创建线程

代码
makefile

mythread:mythread.cc
	g++ -o $@ $^ -lpthread -std=c++11

.PHONY:
clean:
	rm -f mythread

mythread.cc

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

using namespace std; 

static void printTid(const char*name,const pthread_t &tid)
{
    printf("%s 正在运行,thread id: 0x%x\n", name ,tid);
}


void* startRoutine(void *args)
{
    const char* name = static_cast<const char*>(args);
    int cnt = 5;
    while(true)
    {
        printTid(name, pthread_self());//主线程
        sleep(1);
        if(!(cnt--))
        {
            break;
        }
    }
    cout << "线程退出了"<<endl;
    return nullptr;
}


int main()
{
    pthread_t tid;
    int n = pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");

    sleep(10);
    
    //  线程退出的时候,一般必须要进行join,如果不进行join,就会
    //  造成类似于进程那样的内存泄露问题
    pthread_join(tid,nullptr);

    while(true)
    {
        printTid("main thread", pthread_self());//主线程
        sleep(1);
    }

    return 0; 
}

上面代码是是创建线程,下面图片是输入命令kill -19退出进程,可以看到进程退出了二个线程也退出了

在这里插入图片描述
如果运行kill -18命令他也会跑
在这里插入图片描述

2.线程出现异常了怎么办?进程健壮性问题

我在代码cnt–的里面加了个野指针,看到下面的图可以看到线程出现异常,整个进程退出,
所以线程异常等于进程异常
在这里插入图片描述

3.join的第二参数如何理解

是一个输出型参数,获取新线程退出时的退出码
分为三种情况
1.代码跑完,结果正确
2.代码跑完,结果不正确
3.异常
主线程为什么没有获取新线程的退出时的信号,因为线程异常等于进程异常

4.线程终止时

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

using namespace std; 

static void printTid(const char*name,const pthread_t &tid)
{
    printf("%s 正在运行,thread id: 0x%x\n", name ,tid);
}


void* startRoutine(void *args)
{
    const char* name = static_cast<const char*>(args);
    int cnt = 5;
    while(true)
    {
        printTid(name, pthread_self());//主线程
        sleep(1);
        if(!(cnt--))
        {
            // int *p = nullptr;
            // *p = 100; //野指针问题
            break;
        }
    }
    cout << "线程退出了"<<endl;
    // 1. 线程退出的方式,return
    // return (void*)111;
    // 2. 线程退出的方式,pthread_exit
     pthread_exit((void*)1111);
}


int main()
{
    pthread_t tid;
    int n = pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
    (void)n;
    void *ret = nullptr;     // void* -> 64 -> 8byte -> 空间
    pthread_join(tid, &ret); // void **retval是一个输出型参数
    cout<<"main thread join success" <<(long long)(ret)<<endl;


    while(true)
    {
        printTid("main thread", pthread_self());//主线程
        sleep(1);
    }



    return 0; 
}

通过join获得线程的返回的参数并打印

在这里插入图片描述

6.如果理解pthread_t

pthread_t是一个地址
1.线程是一个独立的执行流
2.线程一定会在自己的运行过程中,产生临时数据(调用函数,定义局部变量等)
3.线程一定需要有自己独立的栈结构

线程的全部实现,并没有全部体现在os内,而是os提供执行力,具体的线程结构由库管理,库可以创建多个线程,那么就要管理,先描述再组织
库是在共享区里的,下面的意思是,创建线程库里面不只是只有代码,还有数据
struct thread_info是线程的基本信息,pthread_t是我们对应的用户级线程的控制结构体起始的虚拟地址,用来找到控制结构体struct thread_info
在这里插入图片描述
所以我们知道,主线程的独立栈结构,用的就是地址空间中的栈区
新线程用的栈结构,用的是库中提供的栈结构

7.三个概念

1.临界资源:多个执行流都能看到并能访问的资源,临界资源
2.临界区:多个执行流,代码中,有不同的代码,但访问临界资源的代码,我们称之为临界区
3.互斥:当我们访问某种资源的时候,任何时刻都只有一个执行流在进行访问,这个就叫做:互斥特性

下面的是买票的简略代码,用下面的代码来理解上面的三个概念并加深我们对线程的理解

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h> 



using namespace std;


int tickets = 10000; // 临界资源,可能会因为共同访问,可能会造成数据不一致问题。


void *getTickets(void *args)
{
    const char *name = static_cast<const char *>(args);

    while (true)
    {
        if (tickets > 0)
        {
            usleep(1000);
            cout << name << " 抢到了票, 票的编号: " << tickets << endl;
            tickets--;
        }
        else
        {
            // 票抢到几张,就算没有了呢?0
            cout << name << "已经放弃抢票了,因为没有了..." << endl;

            break;
        }
    }

    return nullptr;
}


int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");
    pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");
    pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");
    pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");

    int n = pthread_join(tid1, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid2, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid3, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid4, nullptr);
    cout << n << ":" << strerror(n) << endl;

    return 0;
}

里面的票数–也就是tickets–;是由一条语句完成的吗?
答案不是的
在这里插入图片描述
我们再来2点
1.在执行语句的任何地方,线程都有可能会被切换
2.cpu内的寄存器是被所有的执行流共享的,但是寄存器里面的数据是属于当前执行流的上下问数据
线程被切换的时候,需要保存上下文
线程被换回的时候,需要恢复上下文

现在还是买票,有个线程a买票买1张把从内存里面票的个数10000读在寄存器里面,减1张票,再把这个数据给放在线程A自己的上下文中,准备把这个数据9999写回内存,这个时候有个线程b也来买票它的优先级等等比较高,也没人来阻难它,他把票买剩下50张,他打算把数据写回内存的时它被切走了,他把自己的上下给保存下来,这个时候切回到了线程a,他把自己的上下文给写进寄存器,变成了9999,并把他写回了内存,这个时候票数就出问题了,多出了好多张票,而这种问题叫做数据不一致的问题
在这里插入图片描述
而上面这些问题,我们需要加锁,让他有原子性,一件事要么不做,要么就直接做完,所以也等于,当我们访问某种资源的时候,任何时刻都只有一个执行流在进行访问,这个就叫做:互斥特性
看到上面讲了那么多,我们来运行一下代码,可以看到确实出现了数据不一致的问题,当然这种是随机性,想增加概率,只能创造更多的让线程堵塞的场景,因为线程切换的创景是因为时间片到了

在这里插入图片描述

6.互斥锁

定义锁

pthread_mutex_t mutex;

给锁初始化

pthread_mutex_init(&mutex, nullptr);

释放锁

pthread_mutex_destroy(&mutex);

加锁

pthread_mutex_lock(&mutex);

解锁

pthread_mutex_ulock(&mutex);

mythrread.cc

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h> 



using namespace std;


int tickets = 1000; // 临界资源,可能会因为共同访问,可能会造成数据不一致问题。
pthread_mutex_t mutex;

void *getTickets(void *args)
{
    const char *name = static_cast<const char *>(args);
    pthread_mutex_lock(&mutex);

    while (true)
    {
        if (tickets > 0)
        {
            usleep(1000);
            cout << name << " 抢到了票, 票的编号: " << tickets << endl;
            tickets--;
        }
        else
        {
            // 票抢到几张,就算没有了呢?0
            cout << name << "已经放弃抢票了,因为没有了..." << endl;

            break;
        }
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}


int main()
{
    pthread_mutex_init(&mutex, nullptr);
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;
    pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");
    pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");
    pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");
    pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");

    int n = pthread_join(tid1, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid2, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid3, nullptr);
    cout << n << ":" << strerror(n) << endl;
    n = pthread_join(tid4, nullptr);
    cout << n << ":" << strerror(n) << endl;

    pthread_mutex_destroy(&mutex);
    return 0;
}

可以看到加了锁后没有发现数据不一致的问题,但是代码为什么一直死循环呢?
这是因为抢完票了break出去了没有执行解锁,所以给解锁就行了
在这里插入图片描述

1.关于临界区的一点问题

1.我们临界资源对应的临界区被锁了,可以被切换吗?

比如这块临界区

        pthread_mutex_lock(&mutex);
        if (tickets > 0)
        {
            usleep(1000);
            cout << name << " 抢到了票, 票的编号: " << tickets << endl;
            tickets--;
            pthread_mutex_unlock(&mutex);
         }          

是可以切换的,因为线程执行的加锁解锁等对应的也是代码,线程在任意代码都可以被切换,又因为加锁是原子性的,要么拿到了锁,要么没拿到,所以加锁是安全的

2.加锁完后线程被切换会怎么样?

在我们加锁后没解锁的中间部分,是绝对不会有线程进入临界区,因为每个线程进入临界区都必须先申请锁,你要有了锁才能进临界区,比如之前被切走的线程是A,当前的锁,被A申请走了,即使当前的线程A没有被调度,但是因为它是被切走的,他没被释放,它是抱着锁走的,而切换成功的进程会进入堵塞状态,因为它没锁,所以一旦一个线程持有了锁,根本不担心任何的切换问题

而对于其他进程而言,线程A访问临界区也具有一定的原子性,只有没有进入和使用完毕二种状态,才对其他线程有意义

2.线程加锁和解锁具有原子性如何实现?

1.了解概念

根据我们前面知道i++不是原子性,因为他汇编不是一条语句完成的
所以实现互斥锁具有原子性也很简单,只有它汇编是一条语句完成的就行,像常用的x86和x64都会提供swap和xchgb,其作用都是内存和cpu的值做交换,也就是一条汇编

在这里插入图片描述
上面汇编代码是把0放进内存里面,也就是说执行完第二行代码,内存里面的值是1,那么进行判断,大于0就退出,其他挂起,

2.第一种情况

现在有二个线程,一个线程A,一个线程B,线程A要把0放进内存里面,内存里面是1也就是说执行完第二行代码,把0写进了cpu,并和内存1进行交换
在这里插入图片描述

这个时候切换成线程B,那么这个时候线程A是要被剥离下来的,保存他自己的上下文1,这个时候线程B也是要执行加锁,把0放进cpu里面,并写入进内存,而这个时候1已经被线程A拿走了,等于拿0换0,然后执行下面的判断,因为你的值没有大于0,所以挂起等待了
在这里插入图片描述

而上面这种情况就是线程A把锁拿走了,所以线程B,访问不了这个临界区,当进程A回来了,恢复了自己的锁,也就是把自己的上下文1放进cpu里面,而这个时候判断就大于0了

3.第二种情况

那么前面说过只要是代码都可以进行切换,那么这次我们不从第2条语句开始切换,从1条语句把0写进cpu的时候后,切换线程

那么线程A刚把0写进了cpu后,就切换成线程B,线程A就保存自己的上下文0
在这里插入图片描述

线程B也要加锁,把0写进了cpu,并执行了第二句代码,把cpu的值和内存的值交换,而这个时候准备进行判断的时候,又切换回线程A,那么线程B保存好自己的上下文
在这里插入图片描述

线程A又要开始把0写入进cpu,并交换那么就和第一种情况一样了,因为1被线程B拿走了,所以0换0,到判断语句的时候,要挂起等待,当线程B切回来,恢复自己的锁,把cpu的值变回1,这个时候判断语句就大于0了

所以不管什么情况,互斥锁都能保证互斥特性
加锁其实就是将数据交互到寄存器内部
本质:将数据从内存读入寄存器,本质就是将数据从共享变成了线程私有

3.解锁

在这里插入图片描述

解锁就很简单了,就是把1写进cpu里面

7.简单实现锁

下面的代码其实就是RALL思想了,把他构造成一个类,自动创造自动析构
Lock.hpp

#pragma once

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

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock_, nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&lock_);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock_);
    }

private:
    pthread_mutex_t lock_;
};

class LockGuard
{
public:
    LockGuard(Mutex *mutex) : mutex_(mutex)
    {
        mutex_->lock();
        std::cout << "加锁成功..." << std::endl;
    }

    ~LockGuard()
    {
        mutex_->unlock();
        std::cout << "解锁成功...." << std::endl;
    }

private:
    Mutex *mutex_;
};

mythread.cc

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include "Lock.hpp"
#include <mutex>

using namespace std;


int tickets = 1000;
Mutex mymutex;

// 函数本质是一个代码块, 会被多个线程同时调用执行,该函数被重复进入 - 被重入了
bool getTickets()
{

    bool ret = false; // 函数的局部变量,在栈上保存,线程具有独立的栈结构,每个线程各自一份
    LockGuard lockGuard(&mymutex); //局部对象的声明周期是随代码块的!
    if (tickets > 0)
    {
        usleep(1001); //线程切换了
        cout << "thread: " << pthread_self() << " get a ticket: " << tickets << endl;
        tickets--;
        ret = true;
    }
     return ret;
}

void *startRoutine(void *args)
{
    const char *name = static_cast<const char *>(args);
    while(true)
    {
        if(!getTickets())
        {
            break;
        }
        cout << name << " get tickets success" << endl;

    }
}



int main()
{

    pthread_t t1, t2, t3, t4;

    pthread_create(&t1, nullptr, startRoutine, (void *)"thread 1");
    pthread_create(&t2, nullptr, startRoutine, (void *)"thread 2");
    pthread_create(&t3, nullptr, startRoutine, (void *)"thread 3");
    pthread_create(&t4, nullptr, startRoutine, (void *)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

}

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

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

相关文章

【设计模式】 - 结构型模式 - 装饰者模式

装饰者模式 概述 指在不改变现有对象结构的情况下&#xff0c;动态地给该对象增加一些职责&#xff08;即增加其额外功能&#xff09;的模式。 装饰者与被装饰者拥有共同的超类&#xff0c;继承的目的是继承类型&#xff0c;而不是行为 结构 Component&#xff1a;可以是接…

膜拜,Alibaba最新发布SprinBoot:进阶原理实战与面试题分析指南

为什么要写这本书&#xff1f; 我们知道&#xff0c;Spring Boot是一个集成性的开源框架&#xff0c;内部整合了很多第三方组件和框架。这些组件和框架应用如此之广泛&#xff0c;以至于大家反而往往对如何更好地使用Spring Boot自身的功能特性并不是很重视。事实上&#xff0…

CubeMX+VSCode+Ozone的STM32开发工作流(一)背景知识介绍

neozng1hnu.edu.cn TODO&#xff1a;1. 添加一键编译启用ozone调试/一键编译下载的脚本&#xff0c;使得整个进一步流程自动化2. 增加更多的背景知识介绍3. 增加VSCode下RTT viewer的支持和一键下载(不进入调试)的支持前言 了解过嵌入式开发的你一定接触过Keil&#xff0c;这款…

[附源码]java毕业设计校园求职与招聘系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

单克隆抗体WuT9/甘草次酸-氟尿嘧啶偶联顺铂/RGD肽修饰聚谷氨酸-顺铂复合物的制备

小编下面整理了单克隆抗体WuT9/甘草次酸-氟尿嘧啶偶联顺铂/RGD肽修饰聚谷氨酸-顺铂复合物的制备&#xff0c;来看&#xff01; RGD肽修饰聚谷氨酸-顺铂复合物制备&#xff1a; 以改性聚谷氨酸侧链羧基与顺铂稳定配位,再与RGD肽进行化学偶联,得到修饰后的药物复合物,对复合物的…

【Java基础】HashMap扩容 | CopyOnWriteArrayList 的底层原理 | 字节码 | Java 异常体系

1. HashMap的扩容机制 JDK 1.7 扩容是针对数组进行扩容&#xff0c;链表是不需要进行扩容的。扩容时先生成原来数组两倍大小的新数组&#xff0c;在把原来老数组上的链表上的元素转移过去。具体在转移链表中元素的步骤是&#xff1a;取每个元素的 key&#xff0c;基于新数组长…

Java反射学习笔记--使用示例

简介 反射是Java编程语言中的一个特性。它允许执行的Java程序 检查 或 操作 自身&#xff0c;并操作程序的内部属性。例如&#xff0c;Java类可以获取其所有成员的名称并显示它们。 反射的一个具体用途是在JavaBeans中&#xff0c;软件组件可以通过一个构建工具进行可视化操作…

specCPU 2006 备忘

前言 首先 specCPU是收费的,好像是800还是1000$&#xff0c;缴费了才有软件分发给你&#xff0c;但是个人或者国内某些项目测试都是百度或者找整机&#xff0c;CPU或者操作系统厂家给。 specCPU和其他性能测试工具类似&#xff0c;基本上都是在被测试机器现场编译测试程序&am…

C++11主要新增使用语法介绍

目录 1. C11简介 2. 统一的列表初始化 2.1 &#xff5b;&#xff5d;初始化 2.2 std::initializer_list 3. 声明 3.1 auto 3.2 decltype 3.3 nullptr 4. STL中一些变化 5. 右值引用和移动构造/赋值 5. 1 左值引用和右值引用 5.2 右值引用使用场景和意义 5.3 完美转发…

map底层实现原理

目录一、map数据结构二、bucket数据结构三、哈希冲突四、负载因子五、渐进式扩容1.扩容的前提条件2.增量扩容3.等量扩容六、查找过程七、插入过程八、Map的value赋值九、Map的遍历赋值一、map数据结构 Golang的map使用哈希表作为底层实现&#xff0c;一个哈希表里可以有多个哈…

2022-11-20 C++并发编程( 四十四 ) -- 通讯顺序进程 CSP 范型

C并发编程 -- 四十四 前言一、通讯顺序进程 CSP 范型1. 细节分解1. lambda 封装类成员函数和 std::function< > 函数封装器2. 模板和 dynamic_cast< >类型转换二、ATM 示例总结前言 并发编程除了常规的使用锁, 或无锁结构实现某些并发计算, 还有没有其它实现形式?…

controller-informer

推翻了自己的三次理论&#xff0c;最终确定informer流程。自己梳理&#xff0c;有错请提示&#xff0c;以便及时改正 一、流程图 图1为整体流程&#xff0c;图2位细节流程 二、主要组件 Reflector-负责与API-Server进行绑定Delta FIFO-增量先进先出队列Indexer-本地缓存Resour…

Linux--系统基础磁盘管理相关知识详解笔记

1.磁盘知识体系结构 第一个层次&#xff1a;磁盘相关物理知识 内部结构 外部结构 读写数据原理 第二个层次&#xff1a;磁盘阵列知识 磁盘弹性扩展知识 ​ 阵列&#xff1a;将多块磁盘整合为一块 ​ &#xff08;1&#xff09;可以存储更大容量数据 ​ &#xff08;2&#…

[附源码]java毕业设计心理问题咨询预约系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

docker启动mysql实例之后,docker ps命令查询不到

1.首先拉取mysql,创建并启动实例 #docker pull mysql:5.7 # docker run -p 3306:3306 --name mysql \ -v /mydata/mysql/log:/var/log/mysql \ -v /mydata/mysql/data:/var/lib/mysql \ -v /mydata/mysql/conf:/etc/mysql \ -e MYSQL_ROOT_PASSWORDroot \ -d mysql:5.7…

Softing DTS.monaco 9.03新版发布,带有远程接口、并行诊断和动态VCI处理功能

2022年10月24日&#xff0c;Softing推出了“诊断工具集&#xff08;DTS&#xff09;”的新版本——Softing DTS.monaco 9.03。9.03版的诊断测试仪Softing DTS.monaco包含了很多新功能&#xff0c;将使您的工作变得更加简单。由于集成了一个新的连接插件&#xff08;动态VCI处理…

XCZU19EG_FFVC1760芯片的封装和管脚

首先写这篇文章是因为我有接触到这款芯片&#xff0c;但是在做接口约束时&#xff0c;有许多地方并不是很清楚&#xff0c;因此在这里对官方文档(ug1075)进行了通读&#xff0c;只翻译了我觉得我需要用到和需要了解的地方&#xff0c;具体是什么还需要大家去自己阅读官方文档。…

LeetCode 319 周赛

纪念本狗第三次AK&#xff01;&#xff01;&#xff01; 2469. 温度转换 给你一个四舍五入到两位小数的非负浮点数 celsius 来表示温度&#xff0c;以 摄氏度&#xff08;Celsius&#xff09;为单位。 你需要将摄氏度转换为 开氏度&#xff08;Kelvin&#xff09;和 华氏度&a…

RK3568平台开发系列讲解(图像篇)JPEG图像处理

🚀返回专栏总目录 文章目录 一、JPEG文件格式和libjpeg编译二、libjpeg接口函数三、JPEG文件解析沉淀、分享、成长,让自己和他人都能有所收获!😄 📢我们今天来讲解JPEG图像处理。 一、JPEG文件格式和libjpeg编译 JPEG的后缀名为.jpg的图像文件。对于图像内容和信息相同…

Windows安装docker踩坑

安装过程中出现一下问题&#xff0c;步骤如下 菜鸟教程安装windows docker https://www.runoob.com/docker/windows-docker-install.html 启动后报错wsl2错误&#xff0c;因为本机运行的是wsl1&#xff0c;进行解决 wsl -l -v查看运行的虚-了拟机的版本以及状态 因为默认运…