【Linux】生产者消费者模型{基于BlockingQueue的PC模型/RAII风格的加锁方式/串行,并行,并发}

news2024/11/26 4:49:55

文章目录

  • 1.认识PC模型
  • 2.基于BlockingQueue的PC模型
    • 2.1串行,并行,并发
    • 2.2理解linux下的并发
    • 2.2RAII风格的加锁方式
    • 2.3阻塞队列
    • 2.4深入理解pthread_cond_wait
    • 2.5整体代码
      • 1.Task.hpp
      • 2.lockGuard.hpp
      • 3.BlockQueue.hpp
      • 4.pcModel.cc
  • 3.总结PC模型

1.认识PC模型

知乎好文

「生产者 - 消费者」模型

图解PC模型

在这里插入图片描述

通常所说的条件满足时去唤醒线程,如何知道条件满足

生产者产生数据,所以能确定“有”数据
消费者消费数据,所以能确定“没有”数据

3种关系:生产者和生产者【互斥】,消费者和消费者【互斥】,生产者和消费者【互斥/同步】

生产者和生产者:竞争和互斥关系,多个生产者不能同时进行生产操作。
消费者和消费者:竞争和互斥关系,多个消费者不能同时进行消费操作。
生产者和消费者:互斥和同步调关系,生产者和消费者不能同时进行生产和消费操作;生产者和消费者要能够互相通知,来确定资源是否就绪。

2种角色:生产者/消费者

生产者线程和消费者线程

1个交易场所:超市

数据结构实现的缓冲区

目的/意义

  1. 完成对生产者和消费者的解耦,提高整个过程的效率。
  2. 生产者和消费者模型实现了线程的角色化,使不同角色的线程执行特定的工作。
  3. 生产者能够确定容器中有数据,而消费者能够确定容器中没有数据。以此来为条件变量发送信号唤醒线程提供依据。
  4. 生产者消费者模型中的容器实际上就是以一定形式组织的(数据结构)一段内存空间(缓冲区)
    生产者消费者模型的优点
  5. 解耦:通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列;消费者不找生产者要数据,而是直接从阻塞队列里取。阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
  6. 提高效率:缓冲区的存在,实现了生产者和消费者的并发执行。多生产多消费模型,实现了生产者和生产者,消费者和消费者的并发执行。

2.基于BlockingQueue的PC模型

2.1串行,并行,并发

串行、并行和并发是计算机科学中常用来描述任务执行方式的重要概念。它们各自有着独特的特性和应用场景。以下是这三个概念的简要解释:

串行(Serial):
定义:在串行执行中,任务按照严格的顺序一个接一个地执行,前一个任务完成之前,下一个任务不会开始。
特性:没有两个任务会同时执行;资源利用率较低,因为处理器或其他资源在大部分时间可能处于空闲状态。
应用:在单核心处理器上,或者当任务之间存在严格的依赖关系时,通常会使用串行执行。
并行(Parallel):
定义:并行执行指的是多个任务同时执行,没有相互等待的情况。每个任务都在其自己的执行单元(如处理器核心)上独立运行。
特性:可以显著提高整体执行速度,因为多个任务可以同时进行;需要更多的硬件资源(如多核心处理器)来支持。
应用:适用于可以独立执行且没有相互依赖关系的任务。在科学计算、图形处理、大数据分析等领域广泛应用。
并发(Concurrency):
定义:并发执行指的是多个任务看起来是同时进行的,但实际上可能由于任务切换、资源争用等原因,并不是真正的同时执行。
特性:通过任务切换来模拟并行执行,使得多个任务能够在单个处理器核心上交替执行;并发控制机制(如锁、信号量等)用于管理对共享资源的访问,以避免数据不一致。
应用:在操作系统、多线程编程、网络编程等领域广泛应用。它允许程序在等待I/O操作(如网络请求或磁盘读写)时继续执行其他任务,从而提高整体性能。
区别与联系:

并行是真正的同时执行多个任务,而并发则更侧重于多个任务在逻辑上的同时执行,实际上可能由于任务切换等原因并不是真正的同时执行。
串行是最基本的执行方式,而并行和并发则是为了提高性能而采用的技术。
在多核心处理器上,可以实现真正的并行执行,而在单核心处理器上,则通常通过并发技术来模拟并行执行。
需要注意的是,虽然并行和并发都能提高性能,但它们也带来了复杂性,如同步问题、资源争用等,需要开发者仔细考虑和处理。

2.2理解linux下的并发

Linux下的并发主要涉及到多任务、多线程和进程间通信等多个方面。要理解Linux下的并发,我们可以从以下几个关键点出发:

多任务处理:Linux是一个多任务操作系统,这意味着它可以同时处理多个任务或进程。这些任务可以是用户级别的程序,也可以是系统级别的服务。操作系统通过时间片轮转或优先级调度等方式,使得这些任务看起来是同时执行的。

进程与线程:

进程:在Linux中,进程是资源分配的基本单位。每个进程都有自己的地址空间、文件描述符、信号处理器等。进程之间的通信和同步通常通过管道、消息队列、共享内存等方式实现。
线程:线程是CPU调度的基本单位。与进程相比,线程共享进程的地址空间和资源,因此线程间的通信和同步相对简单,但也需要小心处理共享数据的访问冲突。Linux通过POSIX线程(pthread)库支持多线程编程。
并发控制:当多个任务或线程需要访问共享资源时,就需要进行并发控制。这主要通过互斥锁(mutex)、读写锁、条件变量、信号量等机制实现。这些机制可以确保同一时间只有一个任务或线程能够访问共享资源,从而避免数据竞争和不一致。

异步编程:异步编程是处理并发的一种常见方法。在Linux中,可以使用异步I/O(如epoll)、事件驱动编程等方式实现异步处理。这种方式允许程序在等待某些操作(如网络请求或文件读写)完成时,继续执行其他任务,从而提高整体性能。

进程间通信(IPC):在Linux中,进程间通信是并发编程中不可或缺的一部分。常见的IPC机制包括管道、命名管道(FIFO)、消息队列、共享内存、信号和套接字等。这些机制允许不同进程之间交换数据和协调行动。

调度器与内核:Linux内核负责管理和调度进程和线程的执行。调度器根据进程的优先级、状态和资源需求等因素,决定哪个进程或线程应该获得CPU的使用权。内核还提供了各种系统调用和接口,支持并发编程的各种需求。

综上所述,Linux下的并发是一个复杂而丰富的领域,涉及到多任务处理、进程与线程、并发控制、异步编程、进程间通信以及内核调度等多个方面。理解这些概念和机制,对于编写高效、稳定的并发程序至关重要。

2.2RAII风格的加锁方式

RAII(Resource Acquisition Is Initialization)风格的加锁方式是一种在C++等编程语言中常见的资源管理技术,其核心思想是将资源的获取(如加锁)与对象的初始化绑定在一起,资源的释放(如解锁)则与对象的析构绑定在一起。通过这种方式,可以确保在对象的生命周期内,资源始终保持有效状态,并且无需显式地调用释放资源的函数。

在加锁的场景中,RAII风格的加锁方式通常是通过创建一个锁对象来实现的。当这个锁对象被创建时,它会自动地获取锁,而当对象离开其作用域或被销毁时,它会自动地释放锁。这样,程序员就无需在每个可能的退出路径上都显式地调用解锁函数,从而减少了出错的可能性,并提高了代码的可读性和可维护性。

例如,在C++中,可以使用智能指针或自定义的锁类来实现RAII风格的加锁。当智能指针或锁类的对象被创建时,它们会自动地获取锁;当这些对象离开其作用域或被销毁时(例如,在函数返回或异常发生时),它们的析构函数会自动地释放锁。

这种加锁方式不仅简化了代码,还提高了程序的健壮性。它可以帮助程序员避免忘记解锁或多次解锁等常见的并发问题,从而减少了死锁和竞态条件的风险。同时,由于锁的获取和释放是自动进行的,因此也可以提高程序的性能。

需要注意的是,虽然RAII风格的加锁方式可以简化并发编程,但并不能完全解决所有的并发问题。在编写并发程序时,还需要考虑其他因素,如线程间的通信、同步机制的选择等。因此,在使用RAII风格的加锁方式时,应结合具体的应用场景和需求来选择合适的解决方案。

2.3阻塞队列

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

2.4深入理解pthread_cond_wait

  1. 一个要访问临界资源的线程,在访问前先检测当前的临界资源是否满足访问条件。如果不满足,则去等待,在临界区中调用pthread_cond_wait时是持有锁的状态,当成功调用wait之后,会自动释放所持有的锁,这也是设计这个接口的设计者考虑周到的一方面:持有锁的线程检测到资源未就绪而去等待,他去等待了需要保证其他线程可以去访问临界资源,而访问临界资源就需要锁,所以调用wait的线程等待前会先释放所持有的锁,不影响其他线程获取锁
  2. 等待的线程被唤醒时是从被阻塞的地方唤醒,即被唤醒时仍处于临界区,wait函数的另一功能就是被唤醒的时候,会自动帮助线程获取锁,但是只要是一个函数,就可能调用失败,如果调用失败,该线程在应该等待的情况下没有执行等待,即发生了不合理的情况:队列已满还去执行push。也可能存在 伪唤醒 的情况,即对方线程在不应该唤醒的情况下唤醒了当前线程。为了防止上述两种情况,我们把if ⇒ while,即如果函数调用失败此时while条件仍满足,那么他就再次调用。如果被伪唤醒,那么它在被唤醒后,再调用while如果条件满足,再次去等待,直到被真正唤醒。

伪唤醒

条件变量的伪唤醒是指在没有满足特定条件的情况下,条件变量的等待操作被唤醒。这种情况可能是由于操作系统的调度策略或其他因素导致的,而不是由于满足了条件。
伪唤醒可能会导致程序的错误行为或不正确的结果。当一个线程被伪唤醒时,它会重新获得互斥锁并从等待操作的地方继续执行,但实际上条件并没有满足。这可能会导致线程在不正确的状态下执行,或者导致竞争条件的发生。
为了防止条件变量的伪唤醒,通常需要将条件的检查与等待操作放在一个while循环中。在等待操作被唤醒后,线程会再次检查条件是否满足,如果条件不满足,则继续等待。这样可以确保线程只在满足条件时才继续执行,避免了伪唤醒带来的问题。

signa放在解锁前和解锁后的问题:

放在解锁前,向目标进程发送唤醒信号,【此时未执行解锁】,目标线程被唤醒后,等待锁,即这个情况没有影响,目标线程从等待条件变量变成了等待锁。如果在向目标进程发送唤醒信号前,有其他线程处于被唤醒且等锁状态,那么目标线程被唤醒后,也没有影响:和其他线程竞争锁即可。
放在解锁后,在锁已经被释放的前提下,目标线程被唤醒,获取对应的锁即可。

2.5整体代码

1.Task.hpp

#pragma once

#include <iostream>
#include <functional>

typedef std::function<int(int, int)> func_t;

class Task
{
public:
    Task() {}

    Task(int x, int y, func_t func)
        : _x(x),
          _y(y),
          _func(func)
    {
    }

    int operator()()
    {
        return _func(_x, _y);
    }

public:
    int _x;
    int _y;
    func_t _func;
};

2.lockGuard.hpp

#pragma once

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

class Mutex
{
public:
    Mutex(pthread_mutex_t *mtx)
        : pmtx_(mtx)
    {
    }

    void lock()
    {
        //std::cout << "加锁中..." << std::endl;
        pthread_mutex_lock(pmtx_);
    }

    void unlock()
    {
        //std::cout << "解锁中..." << std::endl;
        pthread_mutex_unlock(pmtx_);
    }

    ~Mutex()
    {
    }

private:
    pthread_mutex_t *pmtx_;
};

// RAII风格的加锁方式
class lockGuard
{
public:
    lockGuard(pthread_mutex_t *mtx)
        : _mtx(mtx)
    {
        _mtx.lock();
    }

    ~lockGuard()
    {
        _mtx.unlock();
    }

private:
    Mutex _mtx;
};

3.BlockQueue.hpp

#pragma once

#include <iostream>
#include <queue>
#include <mutex>
#include <pthread.h>
#include "lockGuard.hpp"

// #define INI_MTX(mtx) pthread_mutex_init(&mtx, nullptr)
// #define INI_COND(cond) pthread_cond_init(&_Empty, nullptr);
// #define INI_COND(cond) pthread_cond_init(&_Full, nullptr);

const int g_DefaultCap = 5;

template <class T>
class BlockQueue
{
private:
    bool isQueueEmpty()
    {
        return _bq.size() == 0;
    }

    bool isQueueFull()
    {
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = g_DefaultCap)
        : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);

        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
    }

    void push(const T &in)
    {
        lockGuard lockguard(&_mtx); // 自动调用构造函数申请锁

        while (isQueueFull()) // 用while而非if==>访问临界资源100%确定资源就绪
            pthread_cond_wait(&_Full, &_mtx);

        _bq.push(in);

        pthread_cond_signal(&_Empty);
    } // 自动调用lockguard析构函数解锁

    void pop(T *out)
    {
        lockGuard lockguard(&_mtx);

        while (isQueueEmpty())
            pthread_cond_wait(&_Empty, &_mtx);

        *out = _bq.front();
        _bq.pop();

        pthread_cond_signal(&_Full);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }

private:
    std::queue<T> _bq;     // 阻塞队列
    int _capacity;         // 队列容量
    pthread_mutex_t _mtx;  // 互斥锁
    pthread_cond_t _Empty; // 用它来表示bq 是否空的条件
    pthread_cond_t _Full;  // 用它来表示bq 是否满的条件
};

4.pcModel.cc

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

#include "Task.hpp"
#include "BlockQueue.hpp"

int myAdd(int x, int y)
{
    return x + y;
}

// 生产者启动例程
void *producer(void *args)
{
    BlockQueue<Task> *bq = (BlockQueue<Task> *)args;

    while (true)
    {
        //生产数据(自己生产/网络获取/用户输入)
        int x = rand() % 10 + 1;
        usleep(rand() % 1000);
        int y = rand() % 5 + 1;

        Task t(x, y, myAdd);

        //发送数据
        bq->push(t);

        //for debug:输出消息
        std::cout << pthread_self() << " productor: " << t._x << "+" << t._y << "= ?" << std::endl;
        sleep(1);
    }
    return nullptr;
}

// 消费者启动例程
void *consumer(void *args)
{
    BlockQueue<Task> *bq = (BlockQueue<Task> *)args;

    while (true)
    {
        //接收数据
        Task t;
        bq->pop(&t);

        //处理数据
        std::cout << pthread_self() << " consumer : " << t._x << "+" << t._y << "=" << t() << std::endl;
    }

    return nullptr;
}



int main()
{
    srand((uint64_t)time(nullptr) ^ getpid() ^ 0x13254);

    BlockQueue<Task> *bq = new BlockQueue<Task>();

    pthread_t consmr[2], prodcr[2];
    pthread_create(consmr, nullptr, consumer, bq);
    pthread_create(consmr + 1, nullptr, consumer, bq);

    pthread_create(prodcr, nullptr, producer, bq);
    pthread_create(prodcr + 1, nullptr, producer, bq);

    pthread_join(consmr[0], nullptr);
    pthread_join(consmr[1], nullptr);

    pthread_join(prodcr[0], nullptr);
    pthread_join(prodcr[1], nullptr);

    delete bq;

    return 0;
}

在这里插入图片描述

单线程生产/单线程消费PC模型生产/消费速度对结果影响

  1. 速度相当
    在这里插入图片描述
    在这里插入图片描述
  2. 生产速度快
    在这里插入图片描述
  3. 生产者通过用户输入的信息获取数据
    在这里插入图片描述

3.总结PC模型

  1. 生产者获取数据(发送数据数据前即他生产数据/创造数据)需要花时间,例如访问网络获取数据,读取磁盘数据;消费者处理数据(消费数据后)也需要花时间,例如进行数据计算,或者IO操作。
  2. 发送数据和接收数据必须串行执行(互斥),但是生产数据和处理数据可以并行执行。当消费者处理数据时,生产者可以向缓冲区发送数据,也可以生产数据;当生产者生产数据时,消费者可以从缓冲区接收数据,也可以处理数据。
  3. 缓冲区是通过实现生产者线程和消费者线程的并发执行来提高效率的
  4. 多个生产者线程在发送数据时必须串行执行(互斥),但在生产数据时(生产前)可以并发执行:当一个生产者正在生产数据时,其他生产者可以生产数据,也可以发送数据。
  5. 多个消费者线程在接收数据时必须串行执行(互斥),但在处理数据时(消费后)可以并发执行:当一个消费者正在处理数据时,其他消费者可以接收数据,也可以处理数据。
  6. 多生产多消费模型是通过实现生产者和生产者,消费者和消费者的并发执行来提高效率的。
  7. 多生产多消费模型适用于生产数据或处理数据比较花时间的场景,如IO操作,访问网络等。如果只是简单的处理过程就没有必要使用多生产多消费,因为线程切换反而成为效率的影响方。
  8. 生产者消费者模型的并发性:通过线程互斥使得临界资源被合法地访问(即临界资源同一时刻只能有一个线程访问),线程同步使得临界资源被合理地访问(即一个线程准备访问时检测到资源不就绪他会释放锁去等待而非轮询访问),互斥与同步使得【生产者发送数据和消费者获取数据】这一行为合理又合法,而做到这一步只是PC模型不值一提但十分重要的一个“点”,PC模型的并发体现在生产者在生产数据时,消费者可以获取数据获处理数据;消费者在处理数据时,生产者可以生产数据或发送数据。毕竟与发送和获取这样的拷贝动作来比,真正耗时的是“生产”和“处理”这两个动作。
  9. 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,
    直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
  10. 生产者消费者模型优点:解耦;支持并发;支持忙闲不均

PC模型的例子:

  1. 之前学的管道
  2. 内核级/用户级缓冲区:数据到外设(磁盘/显示器)的过程要经历缓冲区,数据到缓冲区和从缓冲区到外设的过程即为PC模型。
  3. 网络:服务端和客户端通信

在计算机层面,如何理解忙闲不均

在计算机层面,忙闲不均通常指的是计算机系统中各个组件、处理器或线程之间的负载不均衡现象。这种现象可能发生在各种计算环境中,包括多核处理器、分布式系统或云计算环境等。

具体来说,忙闲不均可能表现为以下几个方面:

处理器利用率不均衡:在多核处理器系统中,某些核心可能处于高负载状态,而另一些核心则相对空闲。这可能是由于任务分配不当、线程优先级设置不合理或系统资源争用等原因导致的。

内存访问不均衡:在内存访问过程中,某些内存区域可能频繁被访问,而其他区域则较少被访问。这可能导致内存带宽利用率不均衡,影响整体性能。

网络通信不均衡:在分布式系统或云计算环境中,某些节点可能承担大量的网络通信负载,而其他节点则相对空闲。这可能是由于网络拓扑结构、路由策略或节点性能差异等原因造成的。

I/O操作不均衡:对于涉及大量输入/输出操作的应用,如数据库系统或文件服务器,忙闲不均可能表现为某些存储设备的负载过高,而其他设备则相对空闲。

忙闲不均现象对计算机系统的影响是显著的。首先,它可能导致系统整体性能下降,因为高负载的组件可能成为性能瓶颈,限制整体吞吐量。其次,忙闲不均可能增加系统的能耗,因为空闲的组件仍然需要消耗一定的能量。最后,长期的忙闲不均可能导致硬件组件的过早老化或损坏。

为了解决忙闲不均问题,可以采取以下策略:

优化任务分配:通过合理的任务调度和分配算法,确保各个组件或处理器之间的负载均衡。这可以通过使用负载均衡器、任务队列或优先级调度等机制来实现。

提高资源利用率:通过采用虚拟化、容器化或云计算等技术,提高系统资源的利用率。这些技术可以实现资源的动态分配和共享,从而缓解忙闲不均问题。

监控与调优:通过实时监控系统的负载情况,及时发现并解决忙闲不均问题。同时,可以根据实际运行情况进行系统调优,优化性能瓶颈和资源利用率。

综上所述,理解并解决计算机层面的忙闲不均问题对于提高系统性能和可靠性具有重要意义。

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

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

相关文章

蓝桥杯第十三届电子类单片机组程序设计

目录 前言 单片机资源数据包_2023 一、第十三届比赛省赛 1.比赛题目 2.赛题解读 二、部分功能实现 1.继电器的开启与关闭 2.长按切换显示状态功能的实现 3.对于温度传感器小数部分的处理 4.其他处理 1&#xff09;关于数码管显示小数的处理 2&#xff09;关于5s后继…

(完结)Java项目实战笔记--基于SpringBoot3.0开发仿12306高并发售票系统--(三)项目优化

本文参考自 Springboot3微服务实战12306高性能售票系统 - 慕课网 (imooc.com) 本文是仿12306项目实战第&#xff08;三&#xff09;章——项目优化&#xff0c;本篇将讲解该项目最后的优化部分以及一些压测知识点 本章目录 一、压力测试-高并发优化前后的性能对比1.压力测试相关…

系统需求分析报告(原件获取)

第1章 序言 第2章 引言 2.1 项目概述 2.2 编写目的 2.3 文档约定 2.4 预期读者及阅读建议 第3章 技术要求 3.1 软件开发要求 第4章 项目建设内容 第5章 系统安全需求 5.1 物理设计安全 5.2 系统安全设计 5.3 网络安全设计 5.4 应用安全设计 5.5 对用户安全管理 …

Android 自定义EditText

文章目录 Android 自定义EditText概述源码可清空内容的EditText可显示密码的EditText 使用源码下载 Android 自定义EditText 概述 定义一款可清空内容的 ClearEditText 和可显示密码的 PasswordEditText&#xff0c;支持修改提示图标和大小、背景图片等。 源码 基类&#xf…

相机标定学习记录

相机标定是计算机视觉和机器视觉领域中的一项基本技术&#xff0c;它的主要目的是通过获取相机的内部参数&#xff08;内参&#xff09;和外部参数&#xff08;外参&#xff09;&#xff0c;以及镜头畸变参数&#xff0c;建立起现实世界中的点与相机成像平面上对应像素点之间准…

枚举--enum和动态内存管理(malloc和free)

枚举---enum&#xff1a;它的本意就是列举事物&#xff0c;比如&#xff0c;颜色和性别&#xff0c;则代码为&#xff1a; #include<stdio.h> //枚举的示例&#xff1a;性别&#xff0c;颜色 enum sex//性别 {MALE,FEMALE,SECRTY }; enum clore//颜色 {ROW,BLUS,GREEN …

查找某数据在单链表中出现的次数

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct LinkNode {ElemType data;LinkNode* next; }LinkNode, * LinkList; //尾插法建立单链表 void creatLinkList(LinkList& L) {L (LinkNode*)mallo…

Vue系列——数据对象

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>el:挂载点</title> </head> <body&g…

本地项目上传到GitHub

本文档因使用实际项目提交做为案例&#xff0c;故使用xxx等字符进行脱敏&#xff0c;同时隐藏了部分输出&#xff0c;已实际项目和命令行输出为准 0、 Git 安装与GitHub注册 1&#xff09; 在下述地址下载Git&#xff0c;安装一路默认下一步即可。安装完成后&#xff0c;随便…

【面试经典150 | 动态规划】零钱兑换

文章目录 Tag题目来源解题思路方法一&#xff1a;动态规划 写在最后 Tag 【动态规划】【数组】 题目来源 322. 零钱兑换 解题思路 方法一&#xff1a;动态规划 定义状态 dp[i] 表示凑成总金额的最少硬币个数。 状态转移 从小到大枚举要凑成的金额 i&#xff0c;如果当前…

电动车“锂”改“铅”屡被查?新国标或疏于考虑用户真实需求

最近几个月&#xff0c;电动自行车又走到了舆论中心。 “315期间”&#xff0c;不少媒体集中报道了超标电动自行车改装上牌事件。3月18日至20日&#xff0c;新华社推出电动自行车安全隐患系列调查&#xff0c;聚焦点之一就是改装超标问题。而在近段时间&#xff0c;综合媒体报…

MarTech调研总结整理

整体介绍 概念解释&#xff1a; Martech是一种智慧营销的概念&#xff0c;将割裂的营销&#xff08;Marketing&#xff09;、技术&#xff08;Technology&#xff09;与管理&#xff08;Management&#xff09;联系在一起&#xff0c;Martech将技术溶于全营销流程中&#xff0…

基于Java实验室预约管理系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

《操作系统导论》第14章读书笔记:插叙:内存操作API

《操作系统导论》第14章读书笔记&#xff1a;插叙&#xff1a;内存操作API —— 杭州 2024-03-30 夜 文章目录 《操作系统导论》第14章读书笔记&#xff1a;插叙&#xff1a;内存操作API1.内存类型1.1.栈内存&#xff1a;它的申请和释放操作是编译器来隐式管理的&#xff0c;所…

FebHost:意大利.IT域名一张意大利网络名片

.IT域名是意大利的国家顶级域名&#xff0c;对于意大利企业和个人而言,拥有一个属于自己的”.IT”域名无疑是件令人自豪的事。这个被誉为意大利互联网标志性代表的域名,不仅隐含着浓厚的意大利文化特色,还为使用者在当地市场的推广铺平了道路。 对于那些希望在意大利市场建立强…

2核4G服务器可以承载多少用户?卡不卡?

腾讯云轻量应用服务器2核4G5M配置性能测评&#xff0c;腾讯云轻量2核4G5M带宽服务器支持多少人在线访问&#xff1f;并发数10&#xff0c;支持每天5000IP人数访问&#xff0c;腾讯云百科txybk.com整理2核4G服务器支持多少人同时在线&#xff1f;并发数测试、CPU性能、内存性能、…

手把手在K210上部署自己在线训练的YOLO模型

小白花了两天时间学习了一下K210&#xff0c;将在线训练的模型部署在K210&#xff08;代码后面给出&#xff09;上&#xff0c;能够识别卡皮巴拉水杯&#xff08;没错&#xff0c;卡皮巴拉&#xff0c;情绪稳定&#xff0c;真的可爱&#xff01;&#xff09;。数据集是用K210拍…

C语言例1-11:语句 while(!a); 中的表达式 !a 可以替换为

A. a!1 B. a!0 C. a0 D. a1 答案&#xff1a;C while()成真才执行&#xff0c;所以!a1 &#xff0c;也就是 a0 原代码如下&#xff1a; #include<stdio.h> int main(void) {int a0;while(!a){a;printf("a\n");} return 0; } 结果如…

数字化转型导师坚鹏:新质生产力发展解读、方法与案例

新质生产力发展解读、方法与案例 课程背景&#xff1a; 很多学员存在以下问题&#xff1a; 不知道如何理解新质生产力&#xff1f; 不清楚如何发展新质生产力&#xff1f; 不知道新质生产力发展案例&#xff1f; 课程特色&#xff1a; 有实战案例 有原创观点 有…

Linux课程____selinux模式

一、是什么 它叫做“安全增强型 Linux&#xff08;Security-Enhanced Linux&#xff09;”&#xff0c;简称 SELinux&#xff0c;它是 Linux 的一个安全子系统 二、有啥用 就是最大限度地减小系统中服务进程可访问的资源&#xff08;根据的是最小权限原则&#xff09;。避免…