【C语言系统编程】【第二部分:并发编程】2.3 实践与案例分析
2.3 实践与案例分析
2.3.1 案例分析:多线程文件搜索程序
本文中,我们将通过一个多线程文件搜索程序的案例,展示如何在实际项目中应用多线程编程技术,并具体介绍任务分解、线程创建、结果汇总及锁机制的应用。
2.3.1.1 任务分解与线程创建
在一个多线程文件搜索程序中,我们首先需要将搜索任务分解为多个子任务,每个子任务由一个线程执行。
- 任务分解:将整个文件夹树的搜索任务分解成若干个独立的搜索路径,每个线程负责一个或多个搜索路径。这种方式能够充分利用多核CPU,提高搜索效率。
- 线程创建:利用POSIX线程库中的
pthread_create
函数创建多个线程,并让每个线程执行它们分配到的搜索任务。
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <dirent.h>#define MAX_THREADS 10// 线程函数声明
void* search_file(void *arg);// 任务结构体定义
typedef struct {char path[256]; // 搜索路径 [1]char target[256]; // 目标文件名 [2]
} SearchTask;int main() {pthread_t threads[MAX_THREADS]; // 线程数组 [3]SearchTask task[MAX_THREADS]; // 任务数组 [4]// 初始化任务,例如将 /home/user 作为搜索路径strcpy(task[0].path, "/home/user"); // 设置搜索路径 [5]strcpy(task[0].target, "target_file.txt"); // 设置目标文件 [6]// 创建线程if (pthread_create(&threads[0], NULL, search_file, (void*)&task[0])) { // 创建线程 [7]fprintf(stderr, "Error creating thread\n");return 1;}// 等待线程完成for (int i = 0; i < MAX_THREADS; i++) {pthread_join(threads[i], NULL); // 等待线程结束 [8]}return 0;
}void* search_file(void *arg) {SearchTask *task = (SearchTask*)arg; // 获取任务参数 [9]DIR *dir;struct dirent *entry; // 目录项结构指针// 打开目录if ((dir = opendir(task->path)) == NULL) { // 打开目录 [10]perror("opendir() error"); // 处理打开失败情况return NULL;}// 读取目录项while ((entry = readdir(dir)) != NULL) { // 遍历目录项 [11]if (strcmp(entry->d_name, task->target) == 0) { // 文件名匹配检查 [12]printf("Found file: %s/%s\n", task->path, task->target); // 找到目标文件 [13]break; // 找到文件后退出}}// 关闭目录closedir(dir);return NULL;
}
- [1] 搜索路径:
char path[256]
用于存储待搜索的目录路径。 - [2] 目标文件名:
char target[256]
保存待查找的文件名。 - [3] 线程数组:
pthread_t threads[MAX_THREADS]
用于存储线程ID。 - [4] 任务数组:
SearchTask task[MAX_THREADS]
存储每个线程的搜索任务。 - [5] 设置搜索路径:使用
strcpy()
函数初始化任务中路径。 - [6] 设置目标文件:使用
strcpy()
函数初始化任务中目标文件名。 - [7] 创建线程:使用
pthread_create()
函数创建线程并指定执行函数及其参数。 - [8] 等待线程结束:
pthread_join()
用于等待线程执行完毕,以确保所有线程在程序结束前完成任务。 - [9] 获取任务参数:将
void* arg
转换为SearchTask*
。 - [10] 打开目录:
opendir()
函数打开指定路径的目录,返回目录指针。 - [11] 遍历目录项:使用
readdir()
读取目录内容,将结果存入struct dirent* entry
。 - [12] 文件名匹配检查:
strcmp()
判断目录项名称是否与目标文件名相同。 - [13] 找到目标文件:匹配后打印出文件的完整路径。
2.3.1.2 线程间结果汇总
在一个多线程程序中,各个线程需要将它们的搜索结果汇总到一个共享的数据结构中。为了确保线程安全,汇总结果时需要使用同步机制。
- 共享数据结构:可以使用一个全局链表或数组来存储搜索到的目标文件路径。
- 线程安全:在访问共享数据结构时使用互斥锁 (
pthread_mutex_t
) 来防止数据竞态条件。
#include <pthread.h>// 定义一个互斥锁
pthread_mutex_t lock;// 定义一个二维字符数组用于存储搜索结果
char results[10][256]; // 假设最多可以存储10个结果 [1]// 函数 search_file:用于在文件中搜索
void* search_file(void *arg) {// ... 进行搜索的代码,与前代码段一致 ...// 加锁以保证线程安全pthread_mutex_lock(&lock);// 将结果存储到共享的 `results` 数组中strcpy(results[0], task->path); // 假设将结果存储到第一个位置 [2]// 解锁以允许其他线程访问pthread_mutex_unlock(&lock);// ... 继续进行其余的搜索逻辑 ...return NULL; // 返回空指针表示线程无返回值 [3]
}int main() {// 初始化互斥锁pthread_mutex_init(&lock, NULL); // 使用默认属性初始化锁 [4]// 创建线程并等待其完成,这部分代码在较早示例中提供// ... 线程创建及执行代码略 ...// 销毁互斥锁pthread_mutex_destroy(&lock); // 销毁锁以释放系统资源 [5]return 0;
}
- [1] 结果存储数组:
char results[10][256]
定义了一个二维数组,能够存储最多10个字符串结果,每个结果最长256字节。 - [2] 结果存储到共享数组:
strcpy(results[0], task->path);
此行示例中假设将结果存储在results
数组的第一个位置。实际存储位置需要根据逻辑进行动态选择。 - [3] 返回空指针:线程函数
search_file
不需要返回具体数据,直接返回NULL
。 - [4] 初始化互斥锁:
pthread_mutex_init(&lock, NULL);
用于初始化互斥锁,NULL
表示使用默认的锁属性。 - [5] 释放锁资源:
pthread_mutex_destroy(&lock);
销毁互斥锁,以释放占用的系统资源,这通常在主程序最后进行。
2.3.1.3 锁机制的应用
为了避免多个线程同时操作共享数据而导致竞态条件,需要使用互斥锁机制来确保线程安全。
- 互斥锁:在每次访问共享数据结构之前,先锁住互斥锁 (
pthread_mutex_lock
),操作完成后解锁 (pthread_mutex_unlock
)。 - 应用场景:在搜索结果的汇总过程中,各个线程需要安全地将找到的结果存储到共享的数据结构中,使用锁能确保各线程按照序列进行数据操作。
pthread_mutex_lock(&lock); // 锁住数据
// 更新共享数据
pthread_mutex_unlock(&lock); // 解锁数据
以上内容详细讲解了多线程编程中任务分解、线程创建、结果汇总及锁机制的应用。通过这个案例,您可以了解如何在实际项目中应用多线程技术,提高程序的并发处理能力和效率。
2.3.2 案例分析:生产者-消费者模型
2.3.2.1 问题描述与解决思路
生产者-消费者问题是一个经典的并发控制问题,描述了两个线程(或进程)之间的互斥使用共享资源的情况。通常,一个或多个生产者线程负责生产数据并放入一个共享的缓冲区,而一个或多个消费者线程负责从缓冲区中取出数据进行处理。
问题描述:
- 生产者不断地向缓冲区放入产品。
- 消费者不断地从缓冲区取出产品。
- 缓冲区有固定容量,当缓冲区满时,生产者必须等待;当缓冲区空时,消费者必须等待。
解决思路:
- 使用互斥锁(mutex)保护对共享缓冲区的访问,防止生产者和消费者同时操作缓冲区导致的数据不一致。
- 使用信号量(semaphore)或条件变量(condition variable)来管理缓冲区的状态,控制生产者和消费者的等待和唤醒。
2.3.2.2 信号量与互斥锁的结合使用
在生产者-消费者问题中,信号量常用于管理缓冲区中的空闲位置和已有数据的数量,而互斥锁则用于保护缓冲区的访问。
信号量:
- 空闲位置信号量:表示缓冲区中可放置产品的位置数量。
- 已有数据信号量:表示缓冲区中已有的产品数量。
互斥锁:
- 这段代码展示了经典的生产者-消费者问题,用缓冲区实现生产者生成产品,消费者消费产品。生产者和消费者之间通过信号量和互斥锁进行同步和互斥控制。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE]; // 缓冲区 [1]
int in = 0, out = 0; // 生产和消费的指针 [2]pthread_mutex_t mutex; // 互斥锁 [3]
sem_t empty, full; // 信号量:空槽和满槽 [4]// 生产者线程的函数
void *producer(void *param) {int item;while (1) {item = rand() % 100; // 生产一个随机数 [5]sem_wait(&empty); // 等待空闲位置 [6]pthread_mutex_lock(&mutex); // 获取互斥锁,保证对缓冲区操作的原子性 [7]buffer[in] = item; // 将产品放入缓冲区 [8]in = (in + 1) % BUFFER_SIZE; // 环形缓冲区,更新生产序号 [9]printf("Produced: %d\n", item);pthread_mutex_unlock(&mutex); // 释放互斥锁 [10]sem_post(&full); // 增加已有产品数量 [11]}
}// 消费者线程的函数
void *consumer(void *param) {int item;while (1) {sem_wait(&full); // 等待已有产品 [12]pthread_mutex_lock(&mutex); // 获取互斥锁,保证对缓冲区操作的原子性 [13]item = buffer[out]; // 从缓冲区取出产品 [14]out = (out + 1) % BUFFER_SIZE; // 环形缓冲区,更新消费序号 [15]printf("Consumed: %d\n", item);pthread_mutex_unlock(&mutex); // 释放互斥锁 [16]sem_post(&empty); // 增加空闲位置数量 [17]}
}// 主函数
int main() {pthread_t prod, cons;pthread_mutex_init(&mutex, NULL); // 初始化互斥锁 [18]sem_init(&empty, 0, BUFFER_SIZE); // 初始化信号量 `empty`,初始值为 BUFFER_SIZE,表示起始时所有位置都是空的 [19]sem_init(&full, 0, 0); // 初始化信号量 `full`,初始值为 0,表示起始时一个产品都没有 [20]pthread_create(&prod, NULL, producer, NULL); // 创建生产者线程 [21]pthread_create(&cons, NULL, consumer, NULL); // 创建消费者线程 [22]pthread_join(prod, NULL); // 阻塞主线程,等待生产者线程结束 [23]pthread_join(cons, NULL); // 阻塞主线程,等待消费者线程结束 [24]pthread_mutex_destroy(&mutex); // 销毁互斥锁 [25]sem_destroy(&empty); // 销毁信号量 `empty` [26]sem_destroy(&full); // 销毁信号量 `full` [27]return 0;
}
详细注释
- [1] 缓冲区:用于存储生产者产生而等待被消费者消费的产品。
- [2] 生产和消费的指针:
in
指向下一个将存入产品的位置,out
指向将被消费的产品位置。两者实现环形缓冲区的指针管理。 - [3] 互斥锁:保护对共享缓冲区的访问,确保同一时刻只有一个线程进行写入或读取操作。
- [4] 信号量
empty
和full
:empty
表示缓冲区中空余的槽位。full
表示缓冲区中已满的槽位,以便消费者知道是否有产品可以消费。
- [5] 生产随机产品:使用
rand()
函数生成随机数,模拟产品生产。 - [6] 等待空闲位置:通过
sem_wait(&empty)
若empty
信号量为0,则线程会被阻塞,直到有槽位可用。 - [7] 获取互斥锁:确保对共享资源操作的独占性。
- [8] 存放产品:将生产的产品放入缓冲区指定位置。
- [9] 环形缓冲区更新:生产器位置
in
的循环递增。 - [10] 释放互斥锁:操作完成后释放锁。
- [11] 增加产品数量:信号量
full
增加,表示多一个产品可消费。 - [12] 等待已有产品:通过
sem_wait(&full)
若full
信号量为0,则线程会被阻塞,直到有产品可用。 - [13] 获取互斥锁:确保对共享资源操作的独占性。
- [14] 消费产品:从缓冲区的指定位置取出产品。
- [15] 环行缓冲区更新:消费者位置
out
的循环递增。 - [16] 释放互斥锁:操作完成后释放锁。
- [17] 增加空闲位置:信号量
empty
增加,表示多一个槽位可用。 - [18] 初始化互斥锁:对
mutex
进行初始化,准备使用。 - [19] 初始化信号量
empty
:信号量empty
表示初始时所有位置都是空的。 - [20] 初始化信号量
full
:初始值为0,表示起始时没有产品可以消费。 - [21-22] 创建线程:分配并启动生产者和消费者线程。
- [23-24] 等待线程结束:
pthread_join
用于等待线程结束,确保主线程不会提前退出。 - [25-27] 资源销毁:销毁锁和信号量,释放资源。
2.3.2.3 条件变量的应用
条件变量提供了一种线程间通知的机制。一个线程可以等待特定的条件变量,另一个线程可以在条件变量上发出信号,通知等待的线程条件已经满足。
在生产者-消费者问题中,可以用条件变量实现对缓冲区状态的检查和通知。
代码示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h> // 添加stdlib.h用于rand()#define BUFFER_SIZE 5int buffer[BUFFER_SIZE]; // 循环缓冲区 [1]
int in = 0, out = 0; // 生产者和消费者指针 [2]pthread_mutex_t mutex; // 互斥锁 [3]
pthread_cond_t not_full, not_empty; // 条件变量 [4]// 生产者线程函数
void *producer(void *param) {int item;while (1) {item = rand() % 100; // 生成随机数 [5]pthread_mutex_lock(&mutex); // 获取互斥锁 [6]while ((in + 1) % BUFFER_SIZE == out) {pthread_cond_wait(¬_full, &mutex); // 缓冲区满时等待 [7]}buffer[in] = item; // 放入缓冲区 [8]in = (in + 1) % BUFFER_SIZE;printf("Produced: %d\n", item);pthread_cond_signal(¬_empty); // 通知消费者 [9]pthread_mutex_unlock(&mutex); // 释放互斥锁 [10]}
}// 消费者线程函数
void *consumer(void *param) {int item;while (1) {pthread_mutex_lock(&mutex); // 获取互斥锁 [11]while (in == out) {pthread_cond_wait(¬_empty, &mutex); // 缓冲区空时等待 [12]}item = buffer[out]; // 提取缓冲区产品 [13]out = (out + 1) % BUFFER_SIZE;printf("Consumed: %d\n", item);pthread_cond_signal(¬_full); // 通知生产者 [14]pthread_mutex_unlock(&mutex); // 释放互斥锁 [15]}
}int main() {pthread_t prod, cons;// 初始化互斥锁和条件变量 [16]pthread_mutex_init(&mutex, NULL);pthread_cond_init(¬_full, NULL);pthread_cond_init(¬_empty, NULL);// 创建生产者和消费者线程 [17]pthread_create(&prod, NULL, producer, NULL);pthread_create(&cons, NULL, consumer, NULL);// 等待线程结束(不会发生)[18]pthread_join(prod, NULL);pthread_join(cons, NULL);// 销毁互斥锁和条件变量 [19]pthread_mutex_destroy(&mutex);pthread_cond_destroy(¬_full);pthread_cond_destroy(¬_empty);return 0;
}
- [1] 循环缓冲区:定义一个大小为5的整数数组,用于存储生产者生成的数据。
- [2] 生产者和消费者指针:
in
和out
用于指示生产和消费的位置,基于环形缓冲区机制,确保指针在0到BUFFER_SIZE-1之间循环利用。 - [3] 互斥锁:
pthread_mutex_t mutex
用于确保对共享缓冲区的互斥访问,以避免竞争条件。 - [4] 条件变量:
not_full
和not_empty
用于生产者和消费者之间的同步,确保缓冲区状态改变时进行相应的阻塞或唤醒。 - [5] 生成随机数:生产者生成0-99之间的随机整数作为产品。
- [6] 获取互斥锁:生产者在向缓冲区中放入产品之前锁定互斥锁,确保独占访问。
- [7] 缓冲区满时等待:如果缓冲区满,则生产者在
not_full
条件变量上等待,直至消费者消耗产品。 - [8] 放入缓冲区:将生成的产品放入缓冲区,并更新生产者索引。
- [9] 通知消费者:生产者生产完产品后,通过
pthread_cond_signal()
通知可能在等待的消费者。 - [10] 释放互斥锁:对于共享数据操作完成后释放锁,允许其他线程访问。
- [11] 获取互斥锁:消费者在从缓冲区中消费产品之前锁定互斥锁。
- [12] 缓冲区空时等待:如果缓冲区为空,消费者在
not_empty
条件变量上等待,直至生产者提供产品。 - [13] 提取缓冲区产品:从缓冲区中取出产品,并更新消费者索引。
- [14] 通知生产者:消费者取完产品后,通过
pthread_cond_signal()
通知可能在等待的生产者。 - [15] 释放互斥锁:消费者完成操作后释放互斥锁。
- [16] 初始化互斥锁和条件变量:在程序开始时初始化全局同步资源。
- [17] 创建生产者和消费者线程:使用
pthread_create()
启动生产者和消费者线程。 - [18] 等待线程结束(不会发生):主线程等待生产者和消费者结束,但由于它们是无限循环,因此不会发生。
- [19] 销毁互斥锁和条件变量:程序结束时清理资源。不实际发生在这段代码中,因为程序永远运行。
总结
在生产者-消费者模型中,通过信号量和互斥锁可以有效地管理生产和消费的过程,避免竞争条件。而条件变量提供了更灵活的线程间通知机制,可以更精确地控制生产者和消费者的互相等待和唤醒。
2.3.3 案例分析:读写者问题
读写者(Reader-Writer)问题是经典的并发控制问题之一。在这个问题中,存在多个读者(Reader)和多个写者(Writer),读者可以同时读取数据,而写者在执行写操作时,需要保证没有其他读者或写者。
2.3.3.1 问题描述与解决方案
问题描述:
在共享数据的并发访问中,需要满足以下条件:
- 当一个写者正在写数据时,不允许任何读者或者写者访问数据。
- 当一个读者正在读数据时,允许其他读者继续读,但不允许写者写数据。
问题解决方案:
为了解决读写者问题,可以使用读写锁(Reader-Writer Lock)。读写锁允许多个读者同时读取数据,但在有写者进行写操作时,会锁定读访问和写访问。
2.3.3.2 读写锁的使用
使用POSIX线程库中的读写锁可以有效解决读写者问题,以下是一个基本使用示例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define NUM_READERS 5 // 定义读者线程数量 [1]
#define NUM_WRITERS 2 // 定义写者线程数量 [2]pthread_rwlock_t rwlock; // 声明读写锁 [3]
int shared_data = 0; // 共享数据资源 [4]// 读者线程函数
void* reader(void* arg) {int id = *((int*)arg);while (1) {pthread_rwlock_rdlock(&rwlock); // 获取读锁 [5]printf("Reader %d: read shared_data = %d\n", id, shared_data);pthread_rwlock_unlock(&rwlock); // 释放读锁 [6]sleep(1);}return NULL;
}// 写者线程函数
void* writer(void* arg) {int id = *((int*)arg);while (1) {pthread_rwlock_wrlock(&rwlock); // 获取写锁 [7]shared_data++;printf("Writer %d: updated shared_data to %d\n", id, shared_data);pthread_rwlock_unlock(&rwlock); // 释放写锁 [8]sleep(2);}return NULL;
}int main() {pthread_t readers[NUM_READERS], writers[NUM_WRITERS];int ids[NUM_READERS > NUM_WRITERS ? NUM_READERS : NUM_WRITERS];pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁 [9]// 创建读者线程for (int i = 0; i < NUM_READERS; i++) {ids[i] = i + 1;pthread_create(&readers[i], NULL, reader, &ids[i]); // 创建读者线程 [10]}// 创建写者线程for (int i = 0; i < NUM_WRITERS; i++) {ids[i] = i + 1;pthread_create(&writers[i], NULL, writer, &ids[i]); // 创建写者线程 [11]}// 等待所有线程完成for (int i = 0; i < NUM_READERS; i++) {pthread_join(readers[i], NULL); // 等待读者线程终止 [12]}for (int i = 0; i < NUM_WRITERS; i++) {pthread_join(writers[i], NULL); // 等待写者线程终止 [13]}pthread_rwlock_destroy(&rwlock); // 销毁读写锁 [14]return 0;
}
- [1] 读者线程数量:
#define NUM_READERS 5
表示创建5个读者线程。 - [2] 写者线程数量:
#define NUM_WRITERS 2
表示创建2个写者线程。 - [3] 读写锁声明:
pthread_rwlock_t rwlock
用于同步对共享数据的访问。 - [4] 共享数据资源:变量
shared_data
是所有线程共享访问的整数。 - [5] 获取读锁:
pthread_rwlock_rdlock
保证能安全地读共享数据。 - [6] 释放读锁:
pthread_rwlock_unlock
在读数据结束后释放锁,以允许其他线程访问。 - [7] 获取写锁:
pthread_rwlock_wrlock
为写者线程获取独占访问权。 - [8] 释放写锁:
pthread_rwlock_unlock
在写操作结束后释放锁。 - [9] 初始化读写锁:
pthread_rwlock_init
函数用于读写锁的初始化处理。 - [10] 创建读者线程:
pthread_create
启动指定数量的读者线程。 - [11] 创建写者线程:
pthread_create
启动指定数量的写者线程。 - [12] 等待读者线程终止:
pthread_join
用于等待对应的读者线程执行结束。 - [13] 等待写者线程终止:
pthread_join
用于等待对应的写者线程执行结束。 - [14] 销毁读写锁:
pthread_rwlock_destroy
清理和释放读写锁的资源。
这段代码示例了一个经典的读者-写者问题的同步解决方案,确保多个读者可以并发地读取资源,但只允许一个写者可以更新资源。
2.3.3.3 优化与性能分析
优化建议:
- 读优先 vs 写优先:根据应用场景,可以设置锁的策略,例如优先读还是优先写。可以通过调整锁的使用顺序,防止读者或写者饥饿。
- 线程设计:在实际程序中,可以根据读写操作频率调整读者和写者的数量。
- 调整锁策略:如果共享数据的访问频率较高,可以考虑减少锁的争用。例如通过分段锁定减少锁的冲突。
性能分析:
使用读写锁显著提高了并发性,因为读操作不相互干扰。但需要注意的是,如果写操作频繁,可能导致读操作的延迟。因此,对于高并发的读操作,读写锁是有效的,但如果有大量的写操作需求,可能需要重新审视锁策略或者使用其他并发控制机制。
通过本文中读写锁的基本介绍及实际代码示例,可以帮助解决经典的读写者问题,提升对并发编程的理解与应用能力。
上述解释包括了对读写者问题的描述,引入了读写锁的相关概念,并通过实际的C语言代码展示了如何使用读写锁,最后对优化措施和性能进行了简单分析。
相关文章:

【C语言系统编程】【第二部分:并发编程】2.3 实践与案例分析
2.3 实践与案例分析 2.3.1 案例分析:多线程文件搜索程序 本文中,我们将通过一个多线程文件搜索程序的案例,展示如何在实际项目中应用多线程编程技术,并具体介绍任务分解、线程创建、结果汇总及锁机制的应用。 2.3.1.1 任务分解…...

React -AppVarContext.Provider 提供者组件
AppVarContext.Provider 是一个 React 上下文提供者,通常用于在组件树中提供共享的状态或数据。下面将详细解释 AppVarContext.Provider 的作用和如何使用它。展示如何使用 AppVarContext.Provider 来管理全局状态 1. 什么是上下文(Context)…...

【Python】解密用户代理:使用 Python User Agents 库探索浏览器和设备信息
Python User Agents 是一个专为解析 User Agent 字符串而设计的 Python 库。它能够轻松识别访问设备的类型(如移动设备、桌面设备或平板),并获取设备、浏览器、操作系统等详细信息。借助它,开发者可以更好地了解访问用户的设备属性…...

以串口接口为例介绍关于BSP底层架构开发的迭代过程
以串口接口为例介绍关于BSP底层架构开发的迭代过程 文章目录 以串口接口为例介绍关于BSP底层架构开发的迭代过程架构概述初代BSP二代BSP:三代BSP:四代BSP:架构概述 单片机开发有四个阶段: 阶段一:单一单片机的功能实现阶段 此阶段你开始熟悉STM32F1系列的单片机,并利用…...

Label-Studio ML利用yolov8模型实现自动标注
引言 Label Studio ML 后端是一个 SDK,用于包装您的机器学习代码并将其转换为 Web 服务器。Web 服务器可以连接到正在运行的 Label Studio 实例,以自动执行标记任务。我们提供了一个示例模型库,您可以在自己的工作流程中使用这些模型&#x…...

【PostgreSQL】实战篇——用户管理、角色和权限控制的高级用法及技巧
数据库中用户管理、角色和权限控制不仅仅是基础的安全措施,更是实现复杂应用需求和优化数据库性能的重要手段。 通过深入理解这些概念,数据库管理员可以更有效地管理用户访问、确保数据安全,并优化系统性能。以下是对这些概念的详细介绍以及…...

Leetcode: 0011-0020题速览
Leetcode: 0011-0020题速览 本文材料来自于LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解 遵从开源协议为知识共享 版权归属-相同方式…...

Hive数仓操作(七)
一、 Hive动态分区表 1. 动态分区与静态分区的区别 分区定义: 静态分区:在插入数据时,需要手动指定分区字段的值。动态分区:分区字段的值是根据数据中的某个字段自动生成的,用户只需指定分区字段的类型。 数据加载方…...

Redis进阶篇 - 缓存穿透、缓存击穿、缓存雪崩问题及其解决方案
文章目录 1 文章概述2 缓存穿透2.1 什么是缓存穿透?2.2 缓存穿透的解决方法2.2.1 做好参数校验2.2.2 缓存无效Key2.2.3 使用布隆过滤器2.2.4 接口限流 3 缓存击穿3.1 什么是缓存击穿?3.2 缓存击穿的解决方法3.2.1 调整热点数据过期时间3.2.2 热点数据预热…...

一天认识一个硬件之电源
无论是台式机还是笔记本,都离不开电源,台式机和笔记本电脑的电源都承担着将交流电转换为直流电,并为电脑内部各个部件提供稳定电力供应的重要任务。今天就来分享一下台式机和笔记本的电源区别 设计和功率 台式机电源:设计为内置…...

关于BSV区块链覆盖网络的常见问题解答(上篇)
发表时间:2024年9月20日 在BSV区块链上的覆盖网络服务为寻求可扩展、安全、高效交易处理解决方案的开发者和企业家开辟了新的视野。 作为开创性的曼达拉升级的一部分,覆盖网络服务提供了一个强大的框架,用于管理特定类型的交易和数据访问…...

VUE 开发——Node.js学习(一)
一、认识Node.js Node.js是一个跨平台JavaScript运行环境,使开发者可以搭建服务器端的JavaScript应用程序 使用Node.js编写服务器端程序——编写数据接口、前端工程化; Node.js环境没有BOM和DOM; Node.js安装:下载node-v16.19…...

角膜移植难题现,传统方式缺陷显,创新水凝胶破局
大家好!今天来了解一篇天然聚合物衍生光固化生物粘附水凝胶研究——《Natural polymer-derived photocurable bioadhesive hydrogels for sutureless keratoplasty》发表于《Bioactive Materials》。本文介绍了一种用于无缝合角膜移植术的天然聚合物衍生光固化生物粘…...

探索Spring Boot:实现“衣依”服装电商平台
1系统概述 1.1 研究背景 如今互联网高速发展,网络遍布全球,通过互联网发布的消息能快而方便的传播到世界每个角落,并且互联网上能传播的信息也很广,比如文字、图片、声音、视频等。从而,这种种好处使得互联网成了信息传…...

使用 cron 来设置定时任务
使用 cron 来设置定时任务,以便定期运行这个脚本。 在 Linux 系统中设置定时任务的步骤: 1. 编辑 cron 任务 打开终端并输入以下命令以编辑 crontab 文件: crontab -e2. 设置每天 8 点运行脚本 在 crontab 文件的最后,添加以下…...

C# Blazor Server 调用海康H5Player播放摄像头画面
目标 调用海康综合安防平台api,通过摄像头的cameraIndexCode调用【获取监控点预览取流URLv2】api,得到websocket 的url,然后在blazor server中使用htplayer.js播放摄像头实时画面。 步骤 根据摄像头名字,调用【查询监控点列表v2…...

CSS实现服务卡片
CSS实现服务卡片 效果展示 CSS 知识点 回顾整体CSS知识点灵活运用CSS知识点 页面整体布局 <div class"container"><div class"card"><div class"box"><div class"icon"><ion-icon name"color-pal…...

问:如何判断系统环境是大端/小端存储?
大端存储(Big Endian)和小端存储(Little Endian)是两种不同的字节序(即字节顺序)规则,用于在计算机中存储和表示多字节数据类型(例如整数)。 概念解释 大端存储&#x…...

使用NumPy进行线性代数的快速指南
介绍 NumPy 是 Python 中用于数值计算的基础包。它提供了处理数组和矩阵的高效操作,这对于数据分析和科学计算至关重要。在本指南中,我们将探讨 NumPy 中可用的一些基本线性代数操作,展示如何通过运算符重载和内置函数执行这些操作。 元素级…...

uni-app之旅-day02-分类页面
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言创建cate分支4.1 渲染分类页面的基本结构4.2 获取分类数据4.3 动态渲染左侧的一级分类列表4.4 动态渲染右侧的二级分类列表4.5 动态渲染右侧的三级分类列表4.6 …...

鸿蒙harmonyos next flutter通信之BasicMessageChannel获取app版本号
本文将通过BasicMessageChannel获取app版本号,以此来演练BasicMessageChannel用法。 建立channel flutter代码: //建立通道 BasicMessageChannel basicMessageChannel BasicMessageChannel("com.xmg.basicMessageChannel",StringCodec());…...

【文件增量备份系统】MySQL百万量级数据量分页查询性能优化
🎯 导读:本文针对大数据量下的分页查询性能问题进行了深入探讨与优化,最初查询耗时长达12秒,通过避免全表计数及利用缓存保存总数的方式显著提升了浅分页查询速度。面对深分页时依然存在的延迟,采用先查询倒数第N条记录…...

音视频入门基础:FLV专题(12)——FFmpeg源码中,解析DOUBLE类型的ScriptDataValue的实现
一、引言 从《音视频入门基础:FLV专题(9)——Script Tag简介》中可以知道,根据《video_file_format_spec_v10_1.pdf》第80到81页,SCRIPTDATAVALUE类型由一个8位(1字节)的Type和一个ScriptDataV…...

【AI知识点】分层可导航小世界网络算法 HNSW(Hierarchical Navigable Small World)
HNSW(Hierarchical Navigable Small World)分层可导航小世界网络算法 是一种高效的近似最近邻搜索(Approximate Nearest Neighbor Search, ANN) 算法,特别适用于大规模、高维数据集的相似性检索。HNSW 基于小世界网络&…...

ubuntu图形界面右上角网络图标找回解决办法
问题现象: ubuntu图形界面右上角网络图标消失了,不方便联网: 正常应该是下图: 网络寻找解决方案,问题未解决,对于某些场景可能有用,引用过来: 参考方案 Ubuntu虚拟机没有网络图标或…...

maven安装本地jar包到本地仓库
有时候我们需要把本地的 jar 包 install 到本地的 maven 仓库,这时就需要手动install依赖项。例如,把下面的 zhdx-license-1.0.jar 安装到本地 maven 仓库的操作如下: <dependency><groupId>com.zhdx</groupId><artifa…...

1panel申请https/ssl证书自动续期
参考教程 https://hin.cool/posts/sslfor1panel.html #Acme 账户 #1panel.腾讯云dns账号 这里有一步不需要参考,腾讯云dns账号,就是子帐号授权 直接控制台搜索 访问管理 创建用户 授权搜索dns,选择第一个 点击用户名,去掉AdministratorAccess权限 5.点击api密钥生成即可…...

【C语言】指针篇 | 万字笔记
写在前面 在学习C语言过程,总有一个要点难点离不开,那就是大名鼎鼎的C语言指针,也是应为有指针的存在,使得C语言一直长盛不衰。因此不才把指针所学的所有功力都转换成这个笔记。希望对您有帮助🥰🥰 学习指…...

使用transformers调用owlv2实现开放目标检测
目录 安装Demo 安装 pip install transformersDemo from PIL import Image, ImageDraw, ImageFont import numpy as np import torch from transformers import AutoProcessor, Owlv2ForObjectDetection from transformers.utils.constants import OPENAI_CLIP_MEAN, OPENAI_…...

大数据技术:Hadoop、Spark与Flink的框架演进
大数据技术,特别是Hadoop、Spark与Flink的框架演进,是过去二十年中信息技术领域最引人注目的发展之一。这些技术不仅改变了数据处理的方式,而且还推动了对数据驱动决策和智能化的需求。在大数据处理领域,选择合适的大数据平台是确…...