Linux下的多线程编程:原理、工具及应用(1)

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:Flower of Life—陽花

                                                                0:34━━━━━━️💟──────── 4:46
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

模拟语言封装Linux下多线程接口

线程互斥

前置知识

解释为什么会产生上述代码错误

如何解决?加锁!

什么是互斥锁?

pthread_mutex_t

pthread_mutex_init

PTHREAD_MUTEX_INITIALIZER

pthread_mutex_lock

pthread_mutex_unlock

pthread_mutex_trylock(不常用)

根据如上的互斥锁来进行操作

通过定义全局的锁

通过定义局部的锁(优雅的解决)


模拟语言封装Linux下多线程接口

        如下是我们以使用C++实现简单封装:

#pragma once
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <functional>

template <class T>
using func_t = std::function<void(T)>;

template <class T>
class Thread
{
public:
    Thread(const std::string threadname,func_t<T> func,T data)
        :_tid(0),_threadname(threadname),_isrunning(false),_func(func),_data(data)
        {}
    
    static void* ThreadRoutine(void* args)
    {
        Thread *ts=static_cast<Thread *>(args);

        ts->_func(ts->_data);

        return nullptr;
    }

    bool Start()
    {
        int n=pthread_create(&_tid,nullptr,ThreadRoutine,this);
        if(n==0)
        {
            _isrunning=true;
            return true;
        }
        return false;
    }

    bool Join()
    {
        if(!_isrunning) return true;
        int n=pthread_join(_tid,nullptr);
        if(n==0)
        {
            _isrunning=false;
            return true;
        }
        return false;

    }

    std::string ThreadName()
    {
        return _threadname;
    }

    bool Isrunning()
    {
        return _isrunning;
    }

private:
    pthread_t _tid;
    std::string _threadname;
    bool _isrunning;
    func_t<T> _func;
    T _data;
};

        其中成员变量存储了进程线程的ID、线程名、线程运行状态、线程运行的函数以及线程传递的变量。实现了线程的构造函数(根据线程名、传入函数以及传入变量构造),实现了开始运行的操作、等待线程的操作、判断是否运行以及返回线程名的操作

        重点说一下ThreadRoutine这个函数!他实现的是配合运行操作为pthread_create传递函数在执行完后return nullptr结束线程的操作!那他为啥要定义成如下的形式呢?

    static void* ThreadRoutine(void* args)
    {
        Thread *ts=static_cast<Thread *>(args);

        ts->_func(ts->_data);

        return nullptr;
    }

        这是因为成员函数是默认会带有this指针的,而我们要传入pthread_create中规定了只能传入void* args变量的函数。因此我们使用static让他不具有this指针!当然,这并不是唯一的解决办法,我们也可以将他定义到类外,然后给在类内声明友元函数即可

线程互斥

        我们根据上面所封装的线程库来叙写了如下的代码:

#include "Thread.hpp"

std::string GetThreadName()
{
    static int number = 1;
    char name[64];
    snprintf(name, sizeof(name), "Thread-%d", number++);
    return name;
}

void Print(int num)
{
    while (num)
    {
        std::cout << "hello world: " << num-- << std::endl;
        sleep(1);
    }
}



int ticket = 10000; // 全局的共享资源


void GetTicket(std::string name)
{
    while (true)
    {
       
        if (ticket > 0) 
        {
            // 充当抢票花费的时间
            usleep(1000);
            printf("%s get a ticket: %d\n", name.c_str(), ticket);
            ticket--;
        }
        else
        {

            break;
        }
        // 实际情况,还有后续的动作, TODO?
    }
}


int main()
{
    std::string name1 = GetThreadName();
    Thread<std::string> t1(name1, GetTicket, name1);

    std::string name2 = GetThreadName();
    Thread<std::string> t2(name2, GetTicket, name2);

    std::string name3 = GetThreadName();
    Thread<std::string> t3(name3, GetTicket, name3);

    std::string name4 = GetThreadName();
    Thread<std::string> t4(name4, GetTicket, name4);

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

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

    return 0;
}

        该代码简单的模拟了一个抢票的场景,其中票数仅为tickets=10000,在tickets减为0的时候就会停止抢票。我们创建4个线程来模拟多人抢票的情况。按照代码的原意,四个线程会依次抢票知道票数为0为止,但是代码真的如我们所想的那样嘛?

        从上面的现象我们很容易的发现我们的共享资源tickets发生了不该发生的操作,出现抢到同一张票以及多抢了的情况,出现了数据不一致的情况!这是为什么呢?下面先了解一些前置知识:

前置知识

        我们将任何一个时刻,只允许一个进程正在访问的资源称为临界资源。把进程中访问临界资源的代码叫做临界区。例如我们上述的代码:

        互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用 。图解如下:

        原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

 

解释为什么会产生上述代码错误

        请先看以下代码:

#include<iostream>

int main()
{
	int a = 10;
	a++;
	return 0;
}

        很平常的一段代码,但是这也是我们常出现错误代码中访问临界资源的代码。我们可以从反汇编中看出问题:

        可以看到通过反汇编,我们发现本来是一段代码的操作,在底层居然被翻译成了三条操作。这也就违背了我们的原子性!这三条语句在多线程并发访问的时候都有可能会被中断!大致图解:

        当我们知道实际上访问临界资源时是有三步操作后,我们就可以理解为什么会产生如上的错误了!看完如下的例子就明白了:假设现在我们启动了两个线程,他们都要执行如上的三步。线程1先是访问该临界资源,但是在他执行完几次完整的访问(3步都走完)后,它的时间片用完了,恰好此时该线程卡在第二步的--操作,此时他要保存上下文,eax中存储的值为7,然后轮到下一个进程执行。下一个进程也是执行完整了几步,恰好也是在第二步的时候他的时间片用完了,此时他也要保存上下文,eax中存储的值为3。接着轮到第一个线程,他需要恢复上下文啊,因此,从第三步开始将eax返回内存中,此时!count又变回了7!!!

        看完上面的例子你大概就明白了,为什么上述代码互产生错误的原因。因为多线程并发访问全部int,不是原子的!!!会有数据不一致的并发访问问题!

        看完上述的解释是不是以为完了?当然没有!我们都知道在CPU中我们存在着:算术运算、逻辑运算、处理内外中断、控制单元的操作。在上述出错代码中我们还存在着if的判断语句,这就是一种逻辑运算,底层是需要两步的处理:1、加载如寄存器。2、判断他也不是原子的!也会出现会有数据不一致的并发访问问题!因此,我们根本就不知道每个进程的具体执行状况!这也是为什么该数据会减到负数的原因,因为有可能if判断都认为是符合条件的!但是实际寄存器中的值确是不符合的!

如何解决?加锁!

什么是互斥锁?

        互斥锁是一种同步机制,用于确保在多线程环境中共享资源的安全访问

        互斥锁的核心作用是防止多个线程同时访问和修改共享资源,从而避免数据竞争和不一致的问题。以下是互斥锁的一些关键特性和概念:

  1. 排他性:互斥锁确保在任何时刻,只有一个线程能够持有锁并访问共享资源。当一个线程获得锁时,其他线程必须等待直到锁被释放。
  2. 同步原语:互斥锁是一种同步原语,它通常与条件变量、信号量等其他同步机制一起使用,以实现复杂的线程间协作和通信。
  3. 初始化:在使用互斥锁之前,需要对其进行初始化,这可以通过pthread_mutex_init函数完成,也可以静态地通过PTHREAD_MUTEX_INITIALIZER宏来初始化一个互斥锁变量。
  4. 加锁与解锁:线程在访问共享资源前需要对互斥锁进行加锁(上锁),访问完成后需要释放互斥锁(解锁)。这一过程通常通过pthread_mutex_lockpthread_mutex_unlock函数来实现。
  5. 性能考量:互斥锁可能导致进程睡眠和唤醒,以及上下文切换,这些都会带来一定的性能开销。因此,互斥锁适用于加锁时间较长的场景,以减少频繁的锁争用和上下文切换。
  6. 销毁:当互斥锁不再使用时,应当通过pthread_mutex_destroy函数进行销毁,以避免资源泄漏。

pthread_mutex_t

    pthread_mutex_t是一个数据类型,用于表示互斥锁(Mutex)对象。在多线程编程中,互斥锁是一种同步机制,用于保护共享资源,防止多个线程同时访问和修改这些资源,从而避免数据竞争和不一致的问题。

   pthread_mutex_t类型的变量通常用于声明一个互斥锁对象,并使用pthread_mutex_init函数进行初始化。初始化后,可以使用pthread_mutex_lock函数对互斥锁进行加锁操作,使用pthread_mutex_unlock函数进行解锁操作。

        需要注意的是,在使用完互斥锁后,应该及时销毁它,以避免资源泄漏。

pthread_mutex_init

    pthread_mutex_init是一个用于初始化互斥锁的函数,它是POSIX线程库(Pthreads)中的一部分。互斥锁是一种同步原语,用于保护共享资源,防止多个线程同时访问。

        函数原型:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

        参数说明:

  • mutex:指向要初始化的互斥锁对象的指针。
  • attr:指向互斥锁属性对象的指针,可以设置为NULL,表示使用默认属性。

        返回值:

  • 成功时,返回0;
  • 失败时,返回一个非零错误码。

PTHREAD_MUTEX_INITIALIZER

    PTHREAD_MUTEX_INITIALIZER是一个宏定义,用于初始化一个互斥锁对象。它通常与静态分配的互斥锁变量一起使用,以确保在多线程环境中对共享资源的访问是安全的。

        该宏定义的作用是将互斥锁对象的值设置为默认状态,以便在程序启动时立即可用。具体来说,它将互斥锁对象的类型设置为pthread_mutex_t,并将其属性设置为默认值(通常是快速互斥锁)。

pthread_mutex_lock

    pthread_mutex_lock是一个函数,用于对互斥锁进行加锁操作。它的作用是确保在多线程环境中,只有一个线程可以访问共享资源,从而避免数据竞争和不一致的问题。

        函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_lock函数时,它会尝试获取互斥锁。如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码。如果互斥锁已经被其他线程持有,则该线程会被阻塞,直到互斥锁被释放为止。

        一旦线程成功获取互斥锁,其他试图获取该互斥锁的线程将会被阻塞,直到当前持有互斥锁的线程调用pthread_mutex_unlock函数释放互斥锁。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

pthread_mutex_unlock

    pthread_mutex_unlock是一个函数,用于对互斥锁进行解锁操作。它的作用是释放当前线程持有的互斥锁,以便其他线程可以获取该互斥锁并访问共享资源。

        函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_unlock函数时,它会尝试释放当前线程持有的互斥锁。如果当前线程确实持有该互斥锁,则该函数会成功释放互斥锁并返回0;否则,该函数会返回错误码。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

pthread_mutex_trylock(不常用)

    pthread_mutex_trylock是一个函数,用于尝试对互斥锁进行加锁操作。它的作用是尝试获取互斥锁,如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码;如果互斥锁已经被其他线程持有,则该线程不会阻塞,而是立即返回错误码。

        函数原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

        其中,mutex参数是一个指向互斥锁对象的指针。

        当一个线程调用pthread_mutex_trylock函数时,它会尝试获取互斥锁。如果互斥锁当前未被其他线程持有,则该线程成功获取互斥锁并继续执行后续代码;如果互斥锁已经被其他线程持有,则该线程不会阻塞,而是立即返回错误码。

        需要注意的是,在使用完互斥锁后,应该及时调用pthread_mutex_unlock函数来释放互斥锁,以避免死锁或资源泄漏的情况发生。

根据如上的互斥锁来进行操作

通过定义全局的锁

        如下我们根据互斥锁来解决上述的问题:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"

// 应用方的视角
std::string GetThreadName()
{
    static int number = 1;
    char name[64];
    snprintf(name, sizeof(name), "Thread-%d", number++);
    return name;
}

void Print(int num)
{
    while (num)
    {
        std::cout << "hello world: " << num-- << std::endl;
        sleep(1);
    }
}

int ticket = 10000; // 全局的共享资源
// 共享资源了
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 锁就有了,被定义并初始化了,这个锁也是全局的哦!!

// 加锁:
// 1. 我们要尽可能的给少的代码块加锁
// 2. 一般加锁,都是给临界区加锁
void GetTicket(std::string name)
{
    while (true)
    {
        // 2. 是由程序员自己保证的!规则都必须先申请锁
        // 3. 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁失败,失败的线程怎么办?在mutex上进行阻塞,本质就是等待!
        pthread_mutex_lock(&mutex); // 1. 申请锁本身是安全的,原子的,为什么?
        if (ticket > 0) // 4. 一个线程在临界区中访问临界资源的时候,可不可能发生切换?可能,完全允许!!
        {
            // 充当抢票花费的时间
            usleep(1000);
            printf("%s get a ticket: %d\n", name.c_str(), ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
        // 实际情况,还有后续的动作, TODO?
    }
}


int main()
{
    std::string name1 = GetThreadName();
    Thread<std::string> t1(name1, GetTicket, name1);

    std::string name2 = GetThreadName();
    Thread<std::string> t2(name2, GetTicket, name2);

    std::string name3 = GetThreadName();
    Thread<std::string> t3(name3, GetTicket, name3);

    std::string name4 = GetThreadName();
    Thread<std::string> t4(name4, GetTicket, name4);

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

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

    return 0;
}

        可以看到我们成功的解决了上述的问题,但是加了互斥锁也让我们的执行速度相对于上面的执行速度变慢了许多。这是因为:当一个线程持有互斥锁时,其他试图访问相同共享资源的线程必须等待,直到锁被释放。这种等待会导致线程阻塞,减少了并行执行的机会。当线程在等待锁的过程中,操作系统可能会将其置于睡眠状态,并在锁可用时再次唤醒它。这种从睡眠到唤醒的过程涉及到上下文切换,这是一种相对耗时的操作。频繁的上下文切换会显著增加程序的执行时间。

通过定义局部的锁(优雅的解决)

        如下我们定义一个LockGuard.hpp的文件,该文件封装了一个可以通过构造以及析构完成对应的加锁以及解锁的操作:

#pragma once

#include <pthread.h>

// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock):_lock(lock)
    {}
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {}

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock): _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.Unlock();
    }
private:
    Mutex _mutex;
};

        主函数:

        可以看到我们在临界区中定义了上述LockGuard.hpp的lockguard变量,我们可以通过上述的构造以及析构进行加锁、解锁需要注意的是:临界区代码中可以发现我们使用了一个{}来括起来,这是表示代码块的意思,可以理解变量同在函数栈帧中一样。我们通过新定义的ThreadData类来传递给之前封装的多线程接口,优雅的实现了如下的代码:

#include <iostream>
#include <string>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include "Thread.hpp"
#include "LockGuard.hpp"

// 应用方的视角
std::string GetThreadName()
{
    static int number = 1;
    char name[64];
    snprintf(name, sizeof(name), "Thread-%d", number++);
    return name;
}

void Print(int num)
{
    while (num)
    {
        std::cout << "hello world: " << num-- << std::endl;
        sleep(1);
    }
}

class ThreadData
{
public:
    ThreadData(const std::string &name, pthread_mutex_t *lock)
    : threadname(name), pmutex(lock)
    {}
public:
    std::string threadname;
    pthread_mutex_t *pmutex;
};

int ticket = 10000; // 全局的共享资源

void GetTicket(ThreadData *td)
{
    while (true)
    {
        // 非临界区代码!
        // 2. 是由程序员自己保证的!规则都必须先申请锁
        // 3. 根据互斥的定义,任何时刻,只允许一个线程申请锁成功!多个线程申请锁失败,失败的线程怎么办?在mutex上进行阻塞,本质就是等待!
       
        {
             LockGuard lockguard(td->pmutex);
            if (ticket > 0) // 4. 一个线程在临界区中访问临界资源的时候,可不可能发生切换?可能,完全允许!!
            {
                // 充当抢票花费的时间
                usleep(1000);
                printf("%s get a ticket: %d\n", td->threadname.c_str(), ticket);
                ticket--;
           
            }
            else
            {
               
                break;
            }
        }
        // 非临界区代码!
        // 实际情况,还有后续的动作, TODO?
    }
}

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

    std::string name1 = GetThreadName();
    ThreadData *td = new ThreadData(name1, &mutex);
    Thread<ThreadData *> t1(name1, GetTicket, td);

    std::string name2 = GetThreadName();
    ThreadData *td2 = new ThreadData(name2, &mutex);
    Thread<ThreadData *> t2(name2, GetTicket, td2);

    std::string name3 = GetThreadName();
    ThreadData *td3 = new ThreadData(name3, &mutex);
    Thread<ThreadData *> t3(name3, GetTicket, td3);

    std::string name4 = GetThreadName();
    ThreadData *td4 = new ThreadData(name4, &mutex);
    Thread<ThreadData *> t4(name4, GetTicket, td4);

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

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

    pthread_mutex_destroy(&mutex);
    delete td;
    delete td2;
    delete td3;
    delete td4;

    return 0;
}

        一些知识点汇总:

// 加锁:
// 1. 我们要尽可能的给少的代码块加锁
// 2. 一般加锁,都是给临界区加锁
// 3. 个别系统,抢票代码会出现很多的票被同一个线程抢完了
// 4. 多线程运行,同一份资源,有线程长时间无法拥有,饥饿问题
// 5. 要解决饥饿问题,要让线程执行的时候,具备一定的顺序性 --- 同步

 


                         感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记一次Spring事务失效的发现与解决过程

一、事情起因是这样的 首先&#xff0c;我们是使用Spring mybatis 进行开发。 某功能在测试环境看到报错日志&#xff0c; 但是数据库里面的数据发生了变化&#xff0c;没有回滚。 执行数据库update 操作的方法上明确有 Transactional(rollbackFor Exception.class)的注解。…

【数学建模】熵权法

之前我们学了层次分析法和topsis法&#xff0c;但是主观性十分强&#xff0c;有没有科学的方法得出权重呢&#xff1f;今天&#xff0c;我们来学习熵权法&#xff01; 基本概念&#xff1a; 熵权法&#xff0c;物理学名词&#xff0c;按照信息论基本原理的解释&#xff0c;信息…

Spring状态机简单实现

一、什么是状态机 状态机&#xff0c;又称有限状态自动机&#xff0c;是表示有限个状态以及在这些状态之间的转移和动作等行为的计算模型。状态机的概念其实可以应用的各种领域&#xff0c;包括电子工程、语言学、哲学、生物学、数学和逻辑学等&#xff0c;例如日常生活中的电…

什么是MVC三层结构

1.MVC&#xff08;三层结构&#xff09; MVC&#xff08;Model-View-Controller&#xff09;是一种常见的软件设计模式&#xff0c;用于将应用程序的逻辑和界面分离成三个不同的组件。每个组件负责特定的任务&#xff0c;从而提高代码的可维护性和可扩展性。 以前的模式。 遇到…

数据集下载

一、数据集下载——谷歌Open images 谷歌Open-image-v6是由谷歌出资标注的一个超大型数据集&#xff0c;数据大小达到600多G&#xff0c;类别达到600多种分类&#xff0c;对于普通研究者而言&#xff0c;根本没办法全部下载下来做测试&#xff0c;也没必要。只需要下载与自己任…

苹果Vision Pro即将在中日韩等九国开卖 | 百能云芯

苹果公司近期透露&#xff0c;首款混合实境&#xff08;MR&#xff09;头盔「Vision Pro」即将在今年晚些时候推向更多国家销售。虽然苹果尚未公布具体的销售细节&#xff0c;但根据最新的外媒报道&#xff0c;这款高科技产品可能即将在中国、日本、韩国等九个国家开卖&#xf…

三翼鸟门店转型升级:首批260家线下店入驻天猫喵店

作者 | 曾响铃 文 | 响铃说 “资深玩家教你如何做全屋智能家居”、“一条视频给你讲清楚智能家居的设计思路”……在各大网站上搜索“智能家居”&#xff0c;就会出现类似的标题。区别于传统家居博主&#xff0c;他们主要通过分享智能家居体验&#xff0c;讲解智能家居设计等…

Hadoop大数据应用:Linux 部署 HDFS 分布式集群

目录 一、实验 1.环境 2.Linux 部署 HDFS 分布式集群 3.Linux 使用 HDFS 文件系统 二、问题 1.ssh-copy-id 报错 2. 如何禁用ssh key 检测 3.HDFS有哪些配置文件 4.hadoop查看版本报错 5.启动集群报错 6.hadoop 的启动和停止命令 7.上传文件报错 8.HDFS 使用命令 一…

【JetsonNano】onnxruntime-gpu 环境编译和安装,支持 Python 和 C++ 开发

1. 设备 2. 环境 sudo apt-get install protobuf-compiler libprotoc-devexport PATH/usr/local/cuda/bin:${PATH} export CUDA_PATH/usr/local/cuda export cuDNN_PATH/usr/lib/aarch64-linux-gnu export CMAKE_ARGS"-DONNX_CUSTOM_PROTOC_EXECUTABLE/usr/bin/protoc&qu…

SAT和SMT介绍及求解器使用

一、SAT 1、介绍 &#xff08;1&#xff09;定义 SAT即命题逻辑公式的可满足性问题/布尔可满足性问题。即给定一个与或非和变量组成的命题公式&#xff0c;判断是否存在一些结果使得这个公式成立 它是第一个被确认为NP完全的问题。 输入&#xff1a;析取范式&#xff08;C…

新站上线了

新站上线了 由于本人自身的向往&#xff0c;以及粉丝朋友的广大呼吁。我终于抽出时间给我的新站上线了。感谢各位粉丝好友的关注。欢迎大家前来踩站~。 新站地址&#xff1a;https://jhj-coding.top/ 今后会同时维护CSDN与jhj-coding哦&#xff01;期待新站可以给大家带来更好…

穿越半个世纪,探索中国数据库的前世今生

引言 在数字化潮流席卷全球的今天&#xff0c;数据库作为 IT 技术领域的“活化石”&#xff0c;已成为数字经济时代不可或缺的基础设施。那么&#xff0c;中国的数据库技术发展经历了怎样的历程&#xff1f;我们是如何在信息技术的洪流中逐步建立起自己的数据管理帝国的呢&…

Vue3基础笔记(1)模版语法 属性绑定 渲染

Vue全称Vue.js是一种渐进式的JavaScript框架&#xff0c;采用自底向上增量开发的设计&#xff0c;核心库只关注视图层。性能丰富&#xff0c;完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用&#xff0c;适用于场景丰富的web前端框架。灵活性和可逐步集成…

Modbus -tcp协议使用第二版

1.1 协议描述 1.1.1 总体通信结构 MODBUS TCP/IP 的通信系统可以包括不同类型的设备&#xff1a; &#xff08;1&#xff09;连接至 TCP/IP 网络的 MODBUS TCP/IP 客户机和服务器设备&#xff1b; &#xff08;2&#xff09;互连设备&#xff0c;例如&#xff1a;在 TCP/IP…

【消息队列开发】 实现内存加载

文章目录 &#x1f343;前言&#x1f333;实现思路&#x1f6a9;读取消息长度&#x1f6a9;读取相应长度的消息&#x1f6a9;进行反序列化&#x1f6a9;判定是否有效&#x1f6a9;加入有效消息&#x1f6a9;收尾工作&#x1f6a9;代码实现 ⭕总结 &#x1f343;前言 本次开发目…

微信小程序基础面试题

1、简述微信小程序原理 小程序本质就是一个单页面应用&#xff0c;所有的页面渲染和事件处理&#xff0c;都在一个页面内进行&#xff0c;但又可以通过微信客户端调用原生的各种接口&#xff1b;它的架构&#xff0c;是数据驱动的架构模式&#xff0c;它的UI和数据是分离的&am…

【UE5】动画混合空间的基本用法

项目资源文末百度网盘自取 什么是动画混合空间 混合空间分为两种: 通过一个数值控制通过两个数值控制 下面通过演示让大家更直观地了解 在Character文件夹中单击右键,选择动画(Animation),选择旧有的混合空间1D 然后选择骨骼&#xff08;动画是基于骨骼显示的,所以需要选择…

杂七杂八111

MQ 用处 一、异步。可提高性能和吞吐量 二、解耦 三、削峰 四、可靠。常用消息队列可以保证消息不丢失、不重复消费、消息顺序、消息幂等 选型 一Kafak:吞吐量最大&#xff0c;性能最好&#xff0c;集群高可用。缺点&#xff1a;会丢数据&#xff0c;功能较单一。 二Ra…

构建用户身份基础设施,推动新能源汽车高质量发展

随着市场进入智能电动汽车时代&#xff0c;车企们发现&#xff0c;在激烈竞争的市场中不断增长&#xff0c;并不是一件容易的事。《麻省理工科技评论》&#xff0c;前段时间写了一篇报道&#xff1a;中国是如何称霸电动汽车世界的&#xff1f;“过去两年&#xff0c;中国电动汽…

项目性能优化—性能优化的指标、目标

项目性能优化—性能优化的指标、目标 性能优化的终极目标是什么 性能优化的目标实际上是为了更好的用户体验&#xff1a; 一般我们认为用户体验是下面的公式&#xff1a; 用户体验 产品设计&#xff08;非技术&#xff09; 系统性能 ≈ 系统性能 快 那什么样的体验叫快呢…
最新文章