当前位置: 首页 > article >正文

Android学习19 -- NDK4--共享内存(TODO)

在安卓的NDK(Native Development Kit)中,C++共享内存通常用于不同进程间的通信,或者在同一进程中多线程之间共享数据。这种方法相较于其他形式的IPC(进程间通信)来说,具有更高的性能和低延迟。共享内存提供了一块可以被多个进程访问的内存区域,但它的使用需要小心,以避免并发问题和内存访问冲突。

以下是如何在安卓NDK下使用C++进行共享内存的一些基本概念和实现方法:

1. 使用 ashmem(Android共享内存)

Android提供了ashmem(Android Shared Memory)用于跨进程共享内存。它是安卓平台上特定于共享内存的一种机制,可以通过文件描述符来实现共享内存区域。

基本步骤:
  1. 创建共享内存: 使用ashmem_create_region()函数创建共享内存区域。

    #include <sys/mman.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <ashmem.h>
    
    int create_shared_memory(size_t size) {
        int fd = ashmem_create_region("my_shared_memory", size);
        if (fd < 0) {
            perror("Failed to create shared memory");
            return -1;
        }
        return fd;
    }
    
  2. 映射共享内存: 使用mmap函数将共享内存映射到进程的虚拟地址空间。

    void* map_shared_memory(int fd, size_t size) {
        void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (addr == MAP_FAILED) {
            perror("Failed to map shared memory");
            return nullptr;
        }
        return addr;
    }
    
  3. 写入/读取共享内存: 映射后,可以直接通过指针访问共享内存。

    void write_to_shared_memory(void* addr, const char* data) {
        memcpy(addr, data, strlen(data) + 1);
    }
    
    void read_from_shared_memory(void* addr) {
        printf("Data from shared memory: %s\n", (char*)addr);
    }
    
  4. 关闭共享内存: 使用munmapclose来卸载和关闭共享内存。

    void unmap_shared_memory(void* addr, size_t size) {
        munmap(addr, size);
    }
    
    void close_shared_memory(int fd) {
        close(fd);
    }
    

2. 使用 mmap 和普通文件

如果需要跨进程共享内存,可以使用mmap映射一个物理文件作为共享内存。文件在磁盘上存在,但多个进程可以通过映射来共享这个文件的内存区域。

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int create_shared_memory_file(const char* path, size_t size) {
    int fd = open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("Failed to open file");
        return -1;
    }
    if (ftruncate(fd, size) == -1) {
        perror("Failed to set file size");
        close(fd);
        return -1;
    }
    return fd;
}

void* map_shared_memory_file(int fd, size_t size) {
    void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("Failed to map shared memory");
        return nullptr;
    }
    return addr;
}

void unmap_shared_memory(void* addr, size_t size) {
    munmap(addr, size);
}

3. 线程间共享内存

在同一进程中的多个线程也可以通过共享内存来交换数据。共享内存在多线程场景下的使用是非常常见的,因为它允许线程直接访问共享数据,而无需进行复杂的序列化。

#include <pthread.h>
#include <atomic>

std::atomic<int> shared_counter(0);

void* thread_func(void* arg) {
    shared_counter.fetch_add(1, std::memory_order_relaxed);
    return nullptr;
}

int main() {
    pthread_t threads[10];
    for (int i = 0; i < 10; ++i) {
        pthread_create(&threads[i], nullptr, thread_func, nullptr);
    }
    for (int i = 0; i < 10; ++i) {
        pthread_join(threads[i], nullptr);
    }
    printf("Shared counter: %d\n", shared_counter.load());
    return 0;
}

4. 注意事项

  • 同步问题:共享内存的访问需要确保线程或进程间的同步。可以使用mutexsemaphore或其他同步机制来防止竞态条件。

  • 资源管理:共享内存资源需要适当管理,避免内存泄漏。确保共享内存被正确地映射、使用和释放。

  • 权限控制:对于跨进程的共享内存,必须确保各个进程具有访问该内存区域的权限,否则可能会导致访问错误。

总结

安卓NDK中使用C++进行共享内存操作主要通过ashmemmmap来实现。ashmem是专为Android设计的共享内存机制,适用于跨进程通信;而mmap可以用于映射普通文件作为共享内存。共享内存在多进程和多线程之间的数据交换中非常有用,但需要注意同步和资源管理的问题。

在安卓NDK下,多个进程共享一个共享内存的例子通常可以通过ashmem(Android共享内存)来实现。ashmem允许不同的进程通过文件描述符访问同一块内存区域,从而实现跨进程的共享内存通信。

下面是一个简单的例子,展示了如何使用ashmem在两个进程间共享内存。进程A写入共享内存,进程B读取共享内存。

步骤概述:

  1. 进程A:创建共享内存并将数据写入内存。
  2. 进程B:访问同一共享内存并读取数据。

1. 进程A:创建共享内存并写入数据

进程A代码 (process_a.cpp)
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ashmem.h>

int create_shared_memory(size_t size) {
    int fd = ashmem_create_region("my_shared_memory", size);
    if (fd < 0) {
        perror("Failed to create shared memory");
        return -1;
    }
    return fd;
}

void* map_shared_memory(int fd, size_t size) {
    void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("Failed to map shared memory");
        return nullptr;
    }
    return addr;
}

int main() {
    const size_t size = 1024;  // 定义共享内存大小
    const char* message = "Hello from Process A!";

    // 创建共享内存
    int fd = create_shared_memory(size);
    if (fd < 0) {
        return 1;
    }

    // 映射共享内存
    void* addr = map_shared_memory(fd, size);
    if (addr == nullptr) {
        close(fd);
        return 1;
    }

    // 将数据写入共享内存
    strncpy((char*)addr, message, size);

    printf("Process A: Written to shared memory: %s\n", message);

    // 关闭共享内存
    munmap(addr, size);
    close(fd);

    return 0;
}

2. 进程B:访问共享内存并读取数据

进程B代码 (process_b.cpp)
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ashmem.h>

void* map_shared_memory(int fd, size_t size) {
    void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("Failed to map shared memory");
        return nullptr;
    }
    return addr;
}

int main() {
    const size_t size = 1024;  // 定义共享内存大小

    // 打开已有的共享内存文件描述符
    int fd = open("/dev/ashmem/my_shared_memory", O_RDONLY);
    if (fd < 0) {
        perror("Failed to open shared memory");
        return 1;
    }

    // 映射共享内存
    void* addr = map_shared_memory(fd, size);
    if (addr == nullptr) {
        close(fd);
        return 1;
    }

    // 读取共享内存中的数据
    printf("Process B: Read from shared memory: %s\n", (char*)addr);

    // 关闭共享内存
    munmap(addr, size);
    close(fd);

    return 0;
}

3. 编译和运行

  1. 编译代码: 你需要将process_a.cppprocess_b.cpp分别编译成独立的可执行文件。

    在终端中使用NDK编译工具(如ndk-buildCMake)编译代码。确保设置正确的NDK环境。

    例如,使用CMake时,创建一个CMakeLists.txt文件来设置编译参数:

    cmake_minimum_required(VERSION 3.4.1)
    
    add_executable(process_a process_a.cpp)
    add_executable(process_b process_b.cpp)
    

    使用CMake进行构建:

    mkdir build
    cd build
    cmake ..
    make
    
  2. 运行进程A: 启动进程A,它会创建共享内存并写入数据。

    ./process_a
    
  3. 运行进程B: 启动进程B,它会读取进程A写入共享内存中的数据。

    ./process_b
    

4. 说明

  • 创建共享内存:进程A通过调用ashmem_create_region()函数来创建共享内存。这个函数返回一个文件描述符,可以通过该文件描述符在其他进程中访问共享内存。

  • 映射共享内存:进程A和进程B通过mmap()函数将共享内存映射到各自的地址空间,读取或写入共享内存的数据。

  • 数据传输:进程A将数据写入共享内存,进程B从共享内存读取数据。这两者通过共享内存实现了数据传输。

  • 进程间共享内存访问:共享内存的访问方式是基于文件描述符的,通过文件描述符,可以在不同的进程之间共享这块内存。ashmem是一种轻量级的共享内存方式,适用于安卓中的跨进程数据共享。

5. 注意事项

  • 由于共享内存没有内建的同步机制,多个进程同时访问时需要通过锁(例如mutex、semaphore)来保证数据一致性。
  • 在实际开发中,可能还需要设置共享内存的权限(如只读或读写),以保证数据的安全性。

通过这个例子,你可以看到如何在安卓NDK中使用C++进行共享内存的创建和访问,实现多个进程间的数据共享。

在安卓NDK下,使用共享内存实现生产者消费者模型,涉及多个进程通过共享内存进行数据交换。一个进程作为生产者写入数据,多个消费者进程从共享内存中读取数据。

关键概念:

  1. 共享内存:生产者和消费者进程使用共享内存区域进行数据交换。ashmem是一个适用于安卓的共享内存机制。
  2. 同步机制:由于多个进程访问共享内存,必须确保数据一致性和避免竞争条件。因此,使用同步机制(如信号量)来控制数据的读写。

示例描述:

  • 生产者进程:产生数据并写入共享内存。
  • 消费者进程:从共享内存中读取数据并消费。

为了实现这一模型,我们将使用ashmem来创建共享内存区域,并使用简单的信号量(例如通过sem_t)来同步进程之间的读写。

1. 数据结构和共享内存设计

首先定义一个简单的共享内存结构,其中包括一个缓冲区、读写指针和信号量,用于同步。

// shared_memory.h
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H

#include <semaphore.h>

#define BUFFER_SIZE 10  // 缓冲区大小

// 共享内存结构
struct shared_memory {
    int buffer[BUFFER_SIZE];   // 数据缓冲区
    int read_index;            // 读取位置
    int write_index;           // 写入位置
    sem_t full;                // 缓冲区中的数据数量
    sem_t empty;               // 缓冲区中的空位数量
    sem_t mutex;               // 互斥锁,保护缓冲区的读写
};

#endif

2. 生产者进程(写入共享内存)

生产者进程不断生成数据,并将数据写入共享内存。在每次写入后,生产者会通知消费者进程。

生产者代码 (producer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <string.h>
#include "shared_memory.h"
#include <ashmem.h>

int main() {
    // 打开共享内存
    int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);
    if (fd == -1) {
        perror("Failed to open shared memory");
        return -1;
    }

    // 映射共享内存
    struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("Failed to map shared memory");
        close(fd);
        return -1;
    }

    // 写入数据的生产者循环
    for (int i = 0; i < 100; ++i) {
        // 等待空槽位
        sem_wait(&shm->empty);
        sem_wait(&shm->mutex);

        // 生成数据并写入缓冲区
        shm->buffer[shm->write_index] = i;
        printf("Producer: produced %d\n", i);

        // 更新写入位置
        shm->write_index = (shm->write_index + 1) % BUFFER_SIZE;

        // 释放互斥锁,通知消费者
        sem_post(&shm->mutex);
        sem_post(&shm->full);

        // 模拟生产者的工作时间
        sleep(1);
    }

    // 解除共享内存映射
    munmap(shm, sizeof(struct shared_memory));
    close(fd);

    return 0;
}

3. 消费者进程(读取共享内存)

消费者进程从共享内存中读取数据,并进行消费。在每次读取后,消费者会通知生产者进程。

消费者代码 (consumer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <string.h>
#include "shared_memory.h"
#include <ashmem.h>

int main() {
    // 打开共享内存
    int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);
    if (fd == -1) {
        perror("Failed to open shared memory");
        return -1;
    }

    // 映射共享内存
    struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("Failed to map shared memory");
        close(fd);
        return -1;
    }

    // 消费者循环
    for (int i = 0; i < 100; ++i) {
        // 等待缓冲区中有数据
        sem_wait(&shm->full);
        sem_wait(&shm->mutex);

        // 从缓冲区读取数据
        int item = shm->buffer[shm->read_index];
        printf("Consumer: consumed %d\n", item);

        // 更新读取位置
        shm->read_index = (shm->read_index + 1) % BUFFER_SIZE;

        // 释放互斥锁,通知生产者
        sem_post(&shm->mutex);
        sem_post(&shm->empty);

        // 模拟消费者的工作时间
        sleep(1);
    }

    // 解除共享内存映射
    munmap(shm, sizeof(struct shared_memory));
    close(fd);

    return 0;
}

4. 创建和初始化共享内存

共享内存需要在生产者和消费者进程启动前创建,并初始化必要的信号量。这里我们需要一个初始化共享内存的代码。

初始化共享内存 (init_shared_memory.cpp)
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <ashmem.h>
#include "shared_memory.h"

int main() {
    // 创建共享内存
    int fd = open("/dev/ashmem/my_shared_memory", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("Failed to create shared memory");
        return -1;
    }

    // 设置共享内存大小
    if (ftruncate(fd, sizeof(struct shared_memory)) == -1) {
        perror("Failed to set memory size");
        close(fd);
        return -1;
    }

    // 映射共享内存
    struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("Failed to map shared memory");
        close(fd);
        return -1;
    }

    // 初始化缓冲区
    shm->read_index = 0;
    shm->write_index = 0;

    // 初始化信号量
    sem_init(&shm->full, 1, 0);       // full: initially 0
    sem_init(&shm->empty, 1, BUFFER_SIZE);  // empty: initially BUFFER_SIZE
    sem_init(&shm->mutex, 1, 1);      // mutex: initially unlocked (1)

    // 解除共享内存映射
    munmap(shm, sizeof(struct shared_memory));
    close(fd);

    printf("Shared memory initialized.\n");
    return 0;
}

5. 编译和运行

首先编译这三个程序:init_shared_memory.cppproducer.cppconsumer.cpp

g++ init_shared_memory.cpp -o init_shared_memory -lstdc++ -lpthread
g++ producer.cpp -o producer -lstdc++ -lpthread
g++ consumer.cpp -o consumer -lstdc++ -lpthread

然后,按照以下顺序执行:

  1. 初始化共享内存: 在任何生产者或消费者进程运行之前,先运行初始化共享内存的程序。

    ./init_shared_memory
    
  2. 启动生产者进程: 启动生产者进程,它会开始生成数据并写入共享内存。

    ./producer
    
  3. 启动消费者进程: 启动多个消费者进程,读取共享内存中的数据。

    ./consumer
    

    可以启动多个消费者进程,如:

    ./consumer &
    ./consumer &
    

总结

在这个例子中,我们通过共享内存和信号量实现了生产者-消费者模式。生产者通过共享内存写入数据,多个消费者读取并消费数据。信号量确保了在多进程环境中对共享内存的同步访问。

在安卓 NDK 中使用 C++ 和共享内存实现生产者消费者模型,且采用 条件变量 进行同步,需要确保生产者和消费者在共享内存的读写操作中正确同步。

关键步骤:

  1. 共享内存:使用 ashmem 共享内存来传递数据。
  2. 互斥锁和条件变量:使用 pthread_mutex_tpthread_cond_t 来实现生产者和消费者的同步。

示例设计:

  • 生产者进程:生成数据并写入共享内存。
  • 消费者进程:从共享内存中读取数据并进行消费。
  • 通过条件变量来保证数据写入和读取的同步:
    • 生产者在缓冲区满时等待。
    • 消费者在缓冲区空时等待。

1. 数据结构和共享内存设计

// shared_memory.h
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H

#include <pthread.h>

#define BUFFER_SIZE 10  // 缓冲区大小

// 共享内存结构
struct shared_memory {
    int buffer[BUFFER_SIZE];   // 数据缓冲区
    int read_index;            // 读取位置
    int write_index;           // 写入位置
    pthread_mutex_t mutex;     // 互斥锁,保护缓冲区的读写
    pthread_cond_t full_cond;  // 条件变量,缓冲区满时等待
    pthread_cond_t empty_cond; // 条件变量,缓冲区空时等待
};

#endif

2. 初始化共享内存

初始化共享内存区域,包括互斥锁和条件变量。

初始化共享内存 (init_shared_memory.cpp)
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <ashmem.h>
#include "shared_memory.h"

int main() {
    // 创建共享内存
    int fd = open("/dev/ashmem/my_shared_memory", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("Failed to create shared memory");
        return -1;
    }

    // 设置共享内存大小
    if (ftruncate(fd, sizeof(struct shared_memory)) == -1) {
        perror("Failed to set memory size");
        close(fd);
        return -1;
    }

    // 映射共享内存
    struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("Failed to map shared memory");
        close(fd);
        return -1;
    }

    // 初始化缓冲区
    shm->read_index = 0;
    shm->write_index = 0;

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&shm->mutex, NULL);
    pthread_cond_init(&shm->full_cond, NULL);
    pthread_cond_init(&shm->empty_cond, NULL);

    // 解除共享内存映射
    munmap(shm, sizeof(struct shared_memory));
    close(fd);

    printf("Shared memory initialized.\n");
    return 0;
}

3. 生产者进程(写入共享内存)

生产者进程会不断生成数据并将其写入共享内存。当缓冲区满时,生产者会等待,直到消费者消费一些数据。

生产者代码 (producer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <string.h>
#include <ashmem.h>
#include "shared_memory.h"

int main() {
    // 打开共享内存
    int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);
    if (fd == -1) {
        perror("Failed to open shared memory");
        return -1;
    }

    // 映射共享内存
    struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("Failed to map shared memory");
        close(fd);
        return -1;
    }

    // 写入数据的生产者循环
    for (int i = 0; i < 100; ++i) {
        pthread_mutex_lock(&shm->mutex);

        // 如果缓冲区满,则等待
        while ((shm->write_index + 1) % BUFFER_SIZE == shm->read_index) {
            pthread_cond_wait(&shm->full_cond, &shm->mutex);
        }

        // 生成数据并写入缓冲区
        shm->buffer[shm->write_index] = i;
        printf("Producer: produced %d\n", i);

        // 更新写入位置
        shm->write_index = (shm->write_index + 1) % BUFFER_SIZE;

        // 通知消费者数据已准备好
        pthread_cond_signal(&shm->empty_cond);

        pthread_mutex_unlock(&shm->mutex);

        // 模拟生产者的工作时间
        sleep(1);
    }

    // 解除共享内存映射
    munmap(shm, sizeof(struct shared_memory));
    close(fd);

    return 0;
}

4. 消费者进程(读取共享内存)

消费者进程会从共享内存中读取数据并进行消费。当缓冲区为空时,消费者会等待,直到生产者写入数据。

消费者代码 (consumer.cpp)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <string.h>
#include <ashmem.h>
#include "shared_memory.h"

int main() {
    // 打开共享内存
    int fd = open("/dev/ashmem/my_shared_memory", O_RDWR);
    if (fd == -1) {
        perror("Failed to open shared memory");
        return -1;
    }

    // 映射共享内存
    struct shared_memory *shm = (struct shared_memory *)mmap(NULL, sizeof(struct shared_memory),
            PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("Failed to map shared memory");
        close(fd);
        return -1;
    }

    // 消费者循环
    for (int i = 0; i < 100; ++i) {
        pthread_mutex_lock(&shm->mutex);

        // 如果缓冲区空,则等待
        while (shm->read_index == shm->write_index) {
            pthread_cond_wait(&shm->empty_cond, &shm->mutex);
        }

        // 从缓冲区读取数据
        int item = shm->buffer[shm->read_index];
        printf("Consumer: consumed %d\n", item);

        // 更新读取位置
        shm->read_index = (shm->read_index + 1) % BUFFER_SIZE;

        // 通知生产者可以继续写入
        pthread_cond_signal(&shm->full_cond);

        pthread_mutex_unlock(&shm->mutex);

        // 模拟消费者的工作时间
        sleep(1);
    }

    // 解除共享内存映射
    munmap(shm, sizeof(struct shared_memory));
    close(fd);

    return 0;
}

5. 编译和运行

首先编译这三个程序:init_shared_memory.cppproducer.cppconsumer.cpp

g++ init_shared_memory.cpp -o init_shared_memory -lstdc++ -lpthread
g++ producer.cpp -o producer -lstdc++ -lpthread
g++ consumer.cpp -o consumer -lstdc++ -lpthread

然后,按照以下顺序执行:

  1. 初始化共享内存: 在任何生产者或消费者进程运行之前,先运行初始化共享内存的程序。

    ./init_shared_memory
    
  2. 启动生产者进程: 启动生产者进程,它会开始生成数据并写入共享内存。

    ./producer
    
  3. 启动消费者进程: 启动多个消费者进程,读取共享内存中的数据。

    ./consumer
    

    可以启动多个消费者进程,如:

    ./consumer &
    ./consumer &
    

总结

在这个例子中,我们使用了 条件变量 来实现生产者消费者模型。在每次写入和读取数据时,生产者和消费者会根据缓冲区的状态进行等待和通知,从而保证了数据的一致性。信号量可以用来控制资源的访问,而条件变量则提供了更灵活的同步机制,尤其是在等待和通知特定条件时。


http://www.kler.cn/a/454062.html

相关文章:

  • javaweb 04 springmvc
  • 自学记录HarmonyOS Next的HMS AI API 13:语音合成与语音识别
  • Speckly:基于Speckle文档的RAG智能问答机器人
  • 大模型的实践应用33-关于大模型中的Qwen2与Llama3具体架构的差异全解析
  • C++ 泛编程 —— 嵌套使用模板类
  • MATLAB符号计算-符号表达式基础运算操作
  • 机器学习常用评估Metric(ACC、AUC、ROC)
  • 自動提取API爬蟲代理怎麼實現?
  • Docker环境下数据库持久化与多实例扩展实践指南
  • 再谈ChatGPT降智:已蔓延到全端,附解决方案!
  • docker怎么复制容器的文件到宿主机
  • 基于Spring Boot的电影售票系统
  • OCR(三)windows 环境基于c++的 paddle ocr 编译【CPU版本】
  • flask后端开发(6):模板继承
  • 【C++boost::asio网络编程】有关服务端退出方法的笔记
  • 华为OD E卷(100分)39-最长子字符串的长度(二)
  • SpringBoot + HttpSession 自定义生成sessionId
  • 数据中台从centos升级为国产操作系统后,资源增加字段时,提交报500错误
  • 网页中字体图标Fontawesome的使用
  • linux-22 目录管理(二)rmdir命令,删除目录
  • 白牛招投标数据库介绍
  • 什么是Web应用防火墙,简称:WAF(Web Application Firewall)
  • 前端请求跨域问题
  • Docker部署GitLab服务器
  • UDP的报文结构和特点
  • leetcode1110删点成林