【Linux】多线程(一)

news2025/2/1 6:51:08

文章目录

  • 线程的概念
  • 线程优点
  • 线程缺点
  • 线程异常
  • 线程用途
  • 线程分离
  • 局部变量和全局变量
  • 互斥锁
  • 对锁的封装代码
  • 互斥量的接口
      • 互斥量实现原理探究
      • 可重入VS线程安全
        • 概念
        • 常见的线程不安全的情况
      • 常见的线程安全的情况
      • 常见不可重入的情况
      • 常见可重入的情况
      • 可重入与线程安全联系
      • 可重入与线程安全区别
  • 常见锁概念
      • 死锁四个必要条件
      • 避免死锁
      • 避免死锁算法

线程的概念

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

教材观点:
线程是一个执行分支,执行粒度比进程更细,调度成本更低。
线程是进程内部的一个执行流
内核观点:
线程是cpu调度的基本单位,进程是分配资源的基本实体。

在这里插入图片描述

Linux内核设计者,复用了pcb的结构体,用进程的pcb模拟线程的tcb很好的复用了pcb的设计方案。Linux并没有正真意义上的线程,而是用进程的方案模拟线程。
复用代码和结构———简单,好维护,效率更高,更安全———Linux可以不简单的运行
os最频繁的功能除了本身就是进程了。

而windows的设计就是TCB(线程控制块)进程调度里面还有线程调度。
为什么说线程是轻量级进程?是因为在切换线程的时候除了虚拟地址表,页表等不用更换,cache也是不应更换的,它就是我们在调度进程时所加载的周边数据,(预加载附近的数据,局部性原理)缓冲区。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里简单的解释一下上图的页目录为什么是1024,首先虚拟地址空间是共有2^32个地址,从全零到全一。
而这里是取它的前十个比特位,所以就是这十个比特位从全零到全一。后面的页表项也是如此。

当我们在malloc申请内存时,是先进行虚拟内存的申请,然后在真正需要访问时,缺页中断,os才会自动的填充页表和申请物理内存。

线程优点

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

在这里插入图片描述

线程缺点

  • 性能损失
  • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
    线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
    同步和调度开销,而可用的资源不变。
  • 健壮性降低

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

  • 缺乏访问控制
  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高
  • 编写与调试一个多线程程序比单线程程序困难得多
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

int g_val = 0; // 全局变量,在多线程场景中,我们多个线程看到的是同一个变量! -- 为什么?

void *threadRun1(void *args)
{
    while (true)
    {
        sleep(1);
        cout << "t1 thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val << endl;
    }
}

void *threadRun2(void *args)
{
    // char *s = "hello bit";
    while (true)
    {
        sleep(1);
        cout << "t2 thread..." << getpid()  << " &g_val: " << &g_val << " , g_val: " << g_val++ << endl;
        // *s = 'H'; // 让这一个线程崩溃
    }
}

int main()
{
    pthread_t t1, t2, t3;

    pthread_create(&t1, nullptr, threadRun1, nullptr);
    pthread_create(&t1, nullptr, threadRun2, nullptr);

    while (true)
    {
        sleep(1);
        cout << "main thread..." << getpid()  << " &g_val: " << &g_val << " , g_val: " << g_val << endl;
    }
}

在多线程当中只要有一个线程崩溃就会导致整个进程崩溃。
线程是可以共享一个资源的因为他只是创建了个自己的pcb其他的都是一样的,而进程就不是这样的。

线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该
进程内的所有线程也就随即退出

线程用途

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

创建线程
在这里插入图片描述
等待线程,线程也是和进程一样的,也是会出现类似僵尸进程的情况的,所以也是需要等待的。
在这里插入图片描述

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

using namespace std;

#define NUM 10

enum{ OK=0, ERROR };

class ThreadData
{
public:
    ThreadData(const string &name, int id, time_t createTime, int top)
    :_name(name), _id(id), _createTime((uint64_t)createTime),_status(OK), _top(top), _result(0)
    {}
    ~ThreadData()
    {}
public:
    // 输入的
    string _name;
    int _id;
    uint64_t _createTime;

    // 返回的
    int _status;
    int _top;
    int _result;
    // char arr[n]
};


// 线程终止
// 1. 线程函数执行完毕,return void*
// 2. pthread_exit(void*)
void *thread_run(void *args)
{
    // char *name = (char*)args; //?
    ThreadData *td = static_cast<ThreadData *>(args);

    for(int i = 1; i <= td->_top; i++)
    {
        td->_result += i;
    }
    cout << td->_name << " cal done!" << endl;
    // pthread_exit(td);

    return td;
    
    // while (true)
    // {
    //     cout << "thread is running, name " << td->_name << " create time: " << td->_createTime << " index: " << td->_id << endl;
    //     // // exit(10); // 进程退出,不是线程退出,只要有任何一个线程调用exit,整个进程(所有线程)全部退出!
    //     // sleep(4);
        
    //     // break;
    // }
    // delete td;

    // pthread_exit((void*)2);   // void *ret = (void*)1;
    // return nullptr;
}

int main()
{
    pthread_t tids[NUM];
    for(int i = 0; i < NUM ;i++)
    {
        char tname[64];
        snprintf(tname, 64, "thread-%d", i+1);
        ThreadData *td = new ThreadData(tname, i+1, time(nullptr), 100+5*i);
        //这里每次都会new一个新的,所以就tname就不会出现覆盖的问题。
        pthread_create(tids+i, nullptr, thread_run, td);
        sleep(1);
    }

    void *ret = nullptr; // int a =  10

    for(int i = 0 ; i< NUM; i++)
    {
        int n = pthread_join(tids[i], &ret);
        if(n != 0) cerr << "pthread_join error" << endl;
        ThreadData *td = static_cast<ThreadData *>(ret);//更安全的类型转换
        if(td->_status == OK)
        {
            cout << td->_name << " 计算的结果是: " << td->_result << " (它要计算的是[1, " << td->_top << "])" <<endl;
        }
        sleep(1);

        delete td;
    }

    cout << "all thread quit..." << endl;
    return 0;
    // while (true)
    // {
    //     cout << "main thread running, new thread id : " << endl;
    //     sleep(1);
    // }
}

在县线程当中不能使用exit,想退出要是使用pthread_exit。pthread_join是用来等待和回收线程的,如果有线程没有退出就一直等待。
pthread_create(tids+i, nullptr, thread_run, td);的最后一个参数是用来传递信息的,可以是类型也可以是结构体。
第一个参数:一个指向 pthread_t 类型的指针,用于保存新线程的标识符,所以他的第一个参数是输出型参数。
最后一个参数是传给第三个参数的。而 pthread_join(tids[i], &ret);他的第二个参数是输出型参数就是函数的返回值由return 或者 pthread_exit进行传递。注意它们之间的类型转换!!!

在这里插入图片描述

上述代码的输出结果
在这里插入图片描述

pthread_cancel线程取消,最终返回-1

在这里插入图片描述

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

using namespace std;

//#define NUM 10

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

    int cnt = 5;
    while(cnt)
    {
        cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;
        sleep(1);
    }

    pthread_exit((void*)11); 

    // PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1)
    //线程取消返回值是-1,也就是他PTHREAD_CANCELED
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
    //sleep(3);

    //pthread_cancel(tid);

    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;
    return 0;
}

线程取消的结果
在这里插入图片描述
线程正常进行的结果
在这里插入图片描述

注意在LINUX平台下指针是八个比特位,他是64位机器,所以也就导致为什么上述代码在最后打印ret需要用64位的int了。

获取线程id
在这里插入图片描述

线程分离

在这里插入图片描述

一个分离的线程在其执行结束后会自动被系统回收,无需显式地调用线程的销毁函数或者等待其他线程回收。
分离的线程资源会被系统自动释放,包括线程栈空间和其他由线程分配的资源。
分离的线程不能通过调用pthread_join()或类似的函数来获取线程的终止状态或返回值。
分离的线程不会使进程处于等待状态,它们可以在后台继续执行而不影响其他线程的执行。
线程分离的主要优点是降低了资源泄漏的风险,因为分离的线程在执行结束后会自动释放资源。此外,线程分离还可以提高程序的执行效率,尤其是在需要创建大量临时线程的情况下,可以减少线程管理的开销和内存占用。
总而言之,线程分离是一种对线程进行管理和控制的机制,它可以使线程在后台独立运行,并且在执行结束后自动释放资源。它是多线程编程中的一项重要技术,可以提高程序性能和资源管理的效率。

局部变量和全局变量

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

using namespace std;

// __thread int g_val = 100;

int g_val = 100;

std::string hexAddr(pthread_t tid)
{
    g_val++;
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);

    return buffer;
}

void *threadRoutine(void* args)
{
    // static int a = 10;
    string name = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt)
    {
        // cout << name << " : " << cnt-- << " : " << hexAddr(pthread_self()) << " &cnt: " << &cnt << endl;
        cout << name << " g_val: " << g_val++ << ", &g_val: " << &g_val << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t t1, t2, t3;
    pthread_create(&t1, nullptr, threadRoutine, (void*)"thread 1"); // 线程被创建的时候,谁先执行不确定!
    pthread_create(&t2, nullptr, threadRoutine, (void*)"thread 2"); // 线程被创建的时候,谁先执行不确定!
    pthread_create(&t3, nullptr, threadRoutine, (void*)"thread 3"); // 线程被创建的时候,谁先执行不确定!

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

    return 0;
}

在上述代码当中在函数当中的变量是每一个进程都拥有独立的,而在全局当中的变量就是所以有线程共有的,要是想要全局变量变成每个线程都是私有的,就在变量的前面加上__thread

互斥锁

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

using namespace std;

int tickets = 10000; // 加锁保证共享资源的安全
pthread_mutex_t mutex; // 后面说

void *threadRoutine(void *name)
{
    string tname = static_cast<const char*>(name);

    while(true)
    {
        pthread_mutex_lock(&mutex); // 所有线程都要遵守这个规则
        if(tickets > 0) // tickets == 1; a, b, c,d
        {
            //a,b,c,d
            usleep(2000); // 模拟抢票花费的时间
            cout << tname << " get a ticket: " << tickets-- << endl;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
        }

        // 后面还有动作
        usleep(1000); //充当抢完一张票,后续动作
    }

    return nullptr;
}

int main()
{
    pthread_mutex_init(&mutex, nullptr);

    pthread_t t[4];
    int n = sizeof(t)/sizeof(t[0]);
    for(int i = 0; i < n; i++)
    {
        char *data = new char[64];
        snprintf(data, 64, "thread-%d", i+1);
        pthread_create(t+i, nullptr, threadRoutine, data);
    }

    for(int i = 0; i < n; i++)
    {
        pthread_join(t[i], nullptr);
    }

    pthread_mutex_destroy(&mutex);
    return 0;
}

当我们不使用锁时,会出现线程并发的问题,就以上述代码为例在在没有锁的情况下,最后的共享资源并不会像理想的样子,一直减到一,他会确界。而加上锁了之后就不会出现该问题,之后就是串行的。锁没有创建成功的话就会一直等待。如果发现一直是同一个线程就行处理的话,是因为没有后续的任务导致该线程一直抢,所以就一直是他。

// 细节:
// 1. 凡是访问同一个临界资源的线程,都要进行加锁保护,而且必须加同一把锁,这个是一个游戏规则,不能有例外
// 2. 每一个线程访问临界区之前,得加锁,加锁本质是给 临界区 加锁,加锁的粒度尽量要细一些
// 3. 线程访问临界区的时候,需要先加锁->所有线程都必须要先看到同一把锁->锁本身就是公共资源->锁如何保证自己的安全?-> 加锁和解锁本身就是原子的!
// 4. 临界区可以是一行代码,可以是一批代码,a. 线程可能被切换吗?当然可能, 不要特殊化加锁和解锁,还有临界区代码。
// b. 切换会有影响吗?不会,因为在我不在期间,任何人都没有办法进入临界区,因为他无法成功的申请到锁!因为锁被我拿走了!
// 5. 这也正是体现互斥带来的串行化的表现,站在其他线程的角度,对其他线程有意义的状态就是:锁被我申请(持有锁),锁被我释放了(不持有锁), 原子性就体现在这里
// 6. 解锁的过程也被设计成为原子的!
// 7. 锁 的 原理的理解

对锁的封装代码

mythread.cc

#include<iostream>
#include<unistd.h>
#include"thread.hpp"
#include"lockguard.hpp"

using namespace std;

// // 临界资源
int tickets = 1000;                                // 全局变量,共享对象
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 这是我在外边定义的锁


void threadRoutine(void *args)
{
    std::string message = static_cast<const char *>(args);
    while (true)
    {
        // pthread_mutex_lock(&mutex); // 加锁,是一个让不让你通过的策略
        //通过使用空括号来确定锁的范围
        {
            LockGuard lockguard(&mutex); // RAII 风格的锁
            if (tickets > 0)
            {
                usleep(2000);
                cout << message << " get a ticket: " << tickets-- << endl; // 临界区
            }
            else
            {
                break;
            }
        }

        // 我们抢完一张票的时候,我们还要有后续的动作
        // usleep(13);
    }
}

int main()
{
    Thread t1(1, threadRoutine, (void *)"hellobit1");
    Thread t2(2, threadRoutine, (void *)"hellobit2");
    Thread t3(3, threadRoutine, (void *)"hellobit3");
    Thread t4(4, threadRoutine, (void *)"hellobit4");

    t1.run();
    t2.run();
    t3.run();
    t4.run();

    t1.join();
    t2.join();
    t3.join();
    t4.join();
    return 0;
}

thread.hpp

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

using namespace std;

class Thread
{
public:
    typedef void(*func_t)(void*);
    typedef enum
    {
        NEW = 0,
        RUNNING,
        EXITED
    } ThreadStatus;
public:
    Thread(int num,func_t func,void *args)
    :_tid(0),_func(func),_args(args),_status(NEW)
    {
        char name[64];
        snprintf(name,64,"thread-%d",num);
        _name=name;
    }
    int status() { return _status; }
    string threadname() { return _name; }
    pthread_t threadid()
    {
        if (_status == RUNNING)
            return _tid;
        else
        {
            return 0;
        }
    }
    // 是不是类的成员函数,而类的成员函数,具有默认参数this,需要static
    // 但是会有新的问题:static成员函数,无法直接访问类属性和其他成员函数
    static void *runHelper(void *args)
    {
        Thread *ts = (Thread*)args; // 就拿到了当前对象
        // _func(_args);
        (*ts)();
        return nullptr;
    }
    void operator ()() //仿函数
    {
        if(_func != nullptr) _func(_args);
    }
     void run()
    {
        int n = pthread_create(&_tid, nullptr, runHelper, this);
        if(n != 0) exit(1);
        _status = RUNNING;
    }
    void join()
    {
        int n = pthread_join(_tid, nullptr);//runHelper没有返回值
        if( n != 0)
        {
            std::cerr << "main thread join thread " << _name << " error" << std::endl;
            return;
        }
        _status = EXITED;
    }
    ~Thread()
    {
    }
private:
    pthread_t _tid;
     string _name;
    func_t _func; // 线程未来要执行的回调
    void *_args;
    ThreadStatus _status;

};

locguard.hpp

#pragma once

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

class Mutex // 自己不维护锁,有外部传入
{
public:
    Mutex(pthread_mutex_t *mutex):_pmutex(mutex)
    {}
    void lock()
    {
        pthread_mutex_lock(_pmutex);
    }
    void unlock()
    {
        pthread_mutex_unlock(_pmutex);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t *_pmutex;
};

class LockGuard // 自己不维护锁,有外部传入
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        _mutex.lock();
    }
    ~LockGuard()
    {
        _mutex.unlock();
    }
private:
    Mutex _mutex;
};

互斥量的接口

初始化互斥量
初始化互斥量有两种方法:

方法1,静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法2,动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
参数:
mutex:要初始化的互斥量
attr:NULL

销毁互斥量
销毁互斥量需要注意

  • 使用 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调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

互斥量实现原理探究

  • 经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单
    元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一
    个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪
    代码改一下

在这里插入图片描述

可重入VS线程安全

概念

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,
并且没有锁保护的情况下,会出现该问题。
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们
称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重
入函数,否则,是不可重入函数。

常见的线程不安全的情况

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

常见的线程安全的情况

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

常见不可重入的情况

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

常见可重入的情况

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

可重入与线程安全联系

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

可重入与线程安全区别

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

常见锁概念

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

死锁四个必要条件

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

避免死锁

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

避免死锁算法

死锁检测算法
银行家算法

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

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

相关文章

「流失订单召回」AI功能上线,助力企业高效挽回潜客!

据QuestMobile《2020年第三季度中国移动互联网行业数据监测报告》统计&#xff0c;国内用户将商品放入购物车但最终未购买的情况占比高达69.8%&#xff0c;这无异是品牌业务的重大损失点。 用户将商品加入购物车代表着他们有一定的购买意向&#xff0c;但是被“遗弃”也意味着…

Jenkins极简体验

做研发运营一体化&#xff08;或者说运维&#xff09;的人员&#xff0c;对Jenkins一定不陌生。 这个工具在CI/CD链条中充当了大内总管的角色 很多时候&#xff0c;我们喜欢用抽象的概念描述一个东西。本来简简单单的一个工具或者一个软件甚至一个系统&#xff0c;往往被冠以各…

【Vue】day01-Vue基础入门

目录 day01 一、为什么要学习Vue 二、什么是Vue 1.什么是构建用户界面 2.什么是渐进式 Vue的两种开发方式&#xff1a; 3.什么是框架 总结&#xff1a;什么是Vue&#xff1f; 三、创建Vue实例 四、插值表达式 {{}} 1.作用&#xff1a;利用表达式进行插值&#xff0c…

python接口自动化(三十九)- logger 日志 - 上(超详解)

简介 Python的logging模块提供了通用的日志系统&#xff0c;可以方便第三方模块或者是应用使用。这个模块提供不同的日志级别&#xff0c;并可以采用不同的方式记录日志&#xff0c;比如文件&#xff0c;HTTP GET/POST&#xff0c;SMTP&#xff0c;Socket等&#xff0c;甚至可以…

给自己工作的箴言

箴言 目录概述需求&#xff1a; 设计思路实现思路分析工作箴言 性能参数测试&#xff1a; Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge Survive.…

QT程序运行时依赖设置

本文主要解决Qt程序运行时容易遇到的两个问题&#xff1a; 找不到相关的Qt6Core.dll、Qt6Widgets.dll等库 这种情况下&#xff0c;把相关DLL所在目录添加到系统的环境变量 PATH 中&#xff0c;就可以了。 无法导入Qt相关的插件&#xff1a; qt.qpa.plugin: Could not find th…

高效的实时聊天利器:揭秘三个绝佳的网站聊天插件

实时聊天是连接用户与网站之间的纽带。传统的聊天方式已经逐渐不够满足用户的需求&#xff0c;如果不能快速跟用户交流以及快速解决用户问题&#xff0c;用户就容易跑掉&#xff0c;而网站聊天插件正好是实现实时聊天的必备工具。许多电子商务平台和网站都需要在聊天插件加持下…

[USACO06DEC] Milk Patterns G

目录 1.题目 2.思路 1.字典树(trie 树) 2.hash(正解) 1.有注释版 2. copy 版 3.后文 1.题目 Farmer John has noticed that the quality of milk given by his cows varies from day to day. On further investigation, he discovered that although he cant predict t…

【洛谷】P2004 领地选择(二维前缀和)

二维前缀和模板啦&#xff0c;家人们&#xff0c;一眼出 pos:&#xff08;up自己犯的错误&#xff0c;记录一下&#xff0c;也提醒大家&#xff09; x1<n-c1(1哦) y1<m-c1 x2x1c-1(-1哦) y2y1c-1 okk,上&#xff01; ACcode: #include<bits/stdc.h> using na…

C/C++封装:Windows/Linux下封装.lib/.so文件

C/C TCP/IP通信函数 这里提供了两个C/C中服务器与客户端之间通讯的两个程序&#xff0c;程序中封装了通信之间的函数方法&#xff0c;我们以这个程序为例进行封装。 文件目录结构按照C/C标准开源项目进行存放&#xff1a; ├─bin ├─doc ├─lib └─src├─xsocket│ │ …

influxdb 新增数据失败原因解析

前几天本地装了一个influxdb时序数据库&#xff0c;但是通过java新增数据一直失败&#xff0c;奇怪的是measurement和tag都能顺利添加&#xff0c;但是field一直没值。 最开始以为是用户权限&#xff0c;结果发现并不是。 最终原因&#xff1a;influxdb只能往默认的保留策略里…

中国人民大学与加拿大女王大学金融硕士——在职读研为自己而战,为未来而战

还记得去年大街小巷上广为流传的一首歌吗&#xff0c;“去吗 去啊 以最卑微的梦&#xff0c;战吗 战啊 以最孤高的梦&#xff0c;致那黑夜中的呜咽与怒吼&#xff0c;谁说站在光里的才算英雄”。这首歌表达了卑微的生活和梦想并不能击垮每个平凡的奋斗者。我们都有一个大梦想&a…

windbg 双机调试环境搭建(虚拟机)

windbg 双机调试环境搭建&#xff08;虚拟机&#xff09; WinDbg 下载安装下载安装 虚拟机下载安装下载安装虚拟机软件 VMware下载 Windows 镜像&#xff0c;新建虚拟机 WinDbg 建立连接使用演示参考资料 WinDbg 是一个调试器&#xff0c;可用于分析故障转储、调试实时用户模式…

提高驾驶安全性 | 基于ACM32 MCU的胎压监测仪方案

概述 作为车辆的基础部件&#xff0c;轮胎是影响行车安全不可忽视的因素之一。据统计&#xff0c;中国每年由胎压问题引起轮胎爆炸的交通事故约占 30%&#xff0c;其中 50%的高速交通事故是由车辆胎压异常引起。因此&#xff0c;准确实时地监测车辆在行驶过程中的轮胎压监测系…

HarmonyOS/OpenHarmony应用开发-程序包多HAP机制(下)

三、多HAP的开发调试与发布部署流程 &#xff08;一&#xff09;多HAP的开发调试与发布部署流程如下图所示。 图1 多HAP的开发调试与发布部署流程 &#xff08;二&#xff09;开发 开发者通过DevEco Studio工具按照业务的需要创建多个Module&#xff0c;在相应的Module中完成…

【Unity 实用插件篇】 | 使用Fungus插件制作一个对话系统,简单好学易上手

前言 【Unity 实用插件篇】| 使用Fungus插件制作一个对话系统&#xff0c;简单好学易上手一、Fungus介绍二、Fumgus导入三、Fungus功能使用3.1 基础对话效果实现3.2 搭建简单场景测试3.3 触碰 对话3.4 条件对话3.4 分支 对话3.5 改变对话UI视图 四、常用功能菜单介绍4.1 Flowch…

【前端】自制密码展示隐藏按钮

效果 一、前期准备 使用的图片是iconfront上拿的svg代码环境是Vue2 Element 二、创建组件 showPasswordAndclose <template><span class"show-password-container"><span v-if"chooseType CLOSE" click"changeType"><…

一文读懂Nacos原理及实践

文章目录 0. 前言0.nacos 介绍什么是 Nacos&#xff1f;Nacos 地图 1. 原理解析1.1 服务注册与发现流程一、服务注册流程二、服务发现流程三、注册中心高可用性机制 1.2. 原理解析一、服务注册与发现的概念二、服务注册与发现的流程2. 服务发现流程3. 服务负载均衡流程 三、服务…

javascript 将json数据导出excel

<el-button type"primary" plain v-on:click"jsonToExcel();">导出</el-button>jsonToExcel() {const data this.AlarmData;let head "城市,站点,时间,A相电流(A),B相电流(A),C相电流(A),SO2压力(MPa),CO压力(MPa),NOX压力(MPa),A相电压…

Activity引擎(初次学习与总结梳理全记录,包括易混淆知识点分析,常用报错解决方案等)

最近工作需要使用Acticity框架处理审批业务&#xff0c;简单了解后能虽能很快的上手&#xff0c;但是对于Activity的整体认识并不够&#xff0c;特此花费很多精力全面的学习并记录。包含对很多的概念的第一次理解过程&#xff1b;对知识点的混淆地方的梳理&#xff1b;对实践过…