Linux----线程

一、基础概念对比

特性进程 (Process)线程 (Thread)
资源分配资源分配的基本单位(独立地址空间)共享进程资源
调度单位操作系统调度单位CPU调度的最小单位
创建开销高(需复制父进程资源)低(共享进程资源)
通信方式管道、共享内存、消息队列等IPC共享全局变量(需同步机制)
隔离性内存隔离,安全性高共享内存,需处理竞争条件
典型组成代码段+数据段+堆栈段+PCB线程ID+寄存器组+栈+线程控制块TCB

二、线程组成详解

1. 核心组件

struct thread_struct {pthread_t tid;           // 线程ID (8字节)void* stack_base;        // 栈基地址 (8字节)size_t stack_size;       // 栈大小 (Linux默认8MB)void* (*start_routine)(void*); // 入口函数指针void* arg;               // 入口函数参数// 寄存器组保存区 (约52个寄存器,约416字节)// 包括:PC、SP、通用寄存器、浮点寄存器等
};

2. 关键特征

  • 线程IDpthread_t 类型,进程内唯一
  • 独立栈空间:每个线程拥有独立调用栈
  • 共享资源:全局变量、堆内存、文件描述符等

三、线程创建与管理

1. 创建函数原型

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数详解表
参数类型作用说明
threadpthread_t*输出参数,存储新线程ID
attrpthread_attr_t*线程属性(NULL使用默认属性):<br>▪ 栈大小<br>▪ 调度策略<br>▪ 分离状态
start_routinevoid* (*)(void*)线程入口函数(返回值为线程退出状态)
argvoid*传递给入口函数的参数
返回值
  • 成功返回0
  • 失败返回错误码(非errno值,需用strerror转换)

2. 编译指令

gcc program.c -lpthread -o program  # 必须链接pthread库

四、线程生命周期管理

1. 线程终止方式

(1) 显式调用退出函数
void* worker(void* arg) {int* heap_result = malloc(sizeof(int));*heap_result = 100;pthread_exit((void*)heap_result);  // 正确方式:堆内存传递// static int static_result = 200;  // 替代方案:静态变量// pthread_exit((void*)&static_result);
}

关键特性

  • 状态值通过pthread_join()获取
  • 返回值必须使用堆内存或全局/静态变量
  • 主线程调用时仅结束自身执行流
(2) 入口函数返回
void* worker(void* arg) {static int result = 200;  // 必须使用静态存储期变量return (void*)&result;     // 等效于pthread_exit()
}

禁止行为

int local_var = 300;
return (void*)&local_var;  // 错误!栈空间失效
(3) 被其他线程取消
// 取消请求端
pthread_cancel(target_tid);// 被取消线程端
void* worker(void* arg) {pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);while(1) {pthread_testcancel();  // 设置取消点/* 长时间操作 */}
}

取消类型对比

类型行为特征设置函数
PTHREAD_CANCEL_DEFERRED延迟到下一个取消点pthread_setcanceltype()
PTHREAD_CANCEL_ASYNCHRONOUS立即终止(可能破坏数据)pthread_setcanceltype()
(4) 进程级终止
int main() {pthread_t tid;pthread_create(&tid, NULL, worker, NULL);// return 0;        // 错误!触发exit()终止所有线程pthread_exit(NULL); // 正确:仅结束主线程
}

进程终止规则

  • exit()立即终止整个进程
  • 主线程return会隐式调用exit()
  • 建议主线程始终使用pthread_exit()

2. 状态回收机制

(1) 阻塞回收(Joinable模式)
void* status;
int ret = pthread_join(tid, &status);
if(ret == 0) {printf("退出码: %d\n", *(int*)status);free(status);  // 必须释放堆内存
}

限制条件

  • 每个线程只能被join一次
  • 已分离线程无法join
(2) 自动回收(Detached模式)
// 创建时设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);// 运行时分离
pthread_detach(existing_tid);

特性

  • 线程终止后自动回收资源
  • 无法获取返回值
  • 适用于后台任务线程

五、线程资源管理

1. 清理函数机制

void cleanup(void* arg) {printf("清理资源: %p\n", arg);free(arg);
}void* worker(void* arg) {void* res = malloc(1024);pthread_cleanup_push(cleanup, res);  // 注册清理函数// 可能被取消的代码段while(1) {pthread_testcancel();/* 临界操作 */}pthread_cleanup_pop(1);  // 执行清理并出栈return NULL;
}

触发条件

  1. pthread_cleanup_pop(非零值)
  2. 线程通过pthread_exit()退出
  3. 被其他线程取消

编码规范

  • push/pop必须成对出现
  • 建议在可能被取消的代码段前注册
  • 栈式管理(后进先出)

2. 属性管理

(1) 完整属性设置流程
pthread_attr_t attr;
pthread_attr_init(&attr);  // 初始化// 设置分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 设置栈大小(2MB示例)
size_t stack_size = 2 * 1024 * 1024;
void* stack_addr = malloc(stack_size);
pthread_attr_setstack(&attr, stack_addr, stack_size);pthread_create(&tid, &attr, worker, NULL);pthread_attr_destroy(&attr);  // 销毁属性
(2) 常用属性API
函数功能描述
pthread_attr_setdetachstate设置分离/结合状态
pthread_attr_setstacksize设置线程栈大小
pthread_attr_setguardsize设置栈溢出保护区大小
pthread_attr_setschedpolicy设置调度策略(FIFO/RR等)

六、线程同步机制

1. 互斥锁完整实现

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* thread_func(void* arg) {pthread_mutex_lock(&mutex);/* 临界区操作 */pthread_mutex_unlock(&mutex);return NULL;
}// 动态初始化方式
pthread_mutex_init(&mutex, NULL);
/* ... */
pthread_mutex_destroy(&mutex);

2. 同步机制对比

机制适用场景优点缺点
互斥锁共享资源访问控制简单高效可能产生死锁
读写锁读多写少场景提高读并发性能写线程可能饿死
条件变量线程间状态通知精确唤醒机制需配合互斥锁使用
信号量资源数量控制跨进程可用功能相对基础

3. 全局变量竞争解决方案

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
int global_counter = 0;void* counter_thread(void* arg) {for(int i=0; i<100000; ++i) {pthread_mutex_lock(&mutex);global_counter++;  // 原子操作pthread_mutex_unlock(&mutex);}return NULL;
}

优化建议

  • 尽量减小临界区范围
  • 避免嵌套锁
  • 使用trylock避免死锁

七、高级主题与最佳实践

1. 返回值处理规范

(1) 简单状态码
// 传递整型值
pthread_exit((void*)(intptr_t)error_code);// 接收端
int code = (int)(intptr_t)status;
(2) 复杂数据结构
struct Result {int code;char message[256];
};void* worker(void* arg) {struct Result* res = malloc(sizeof(struct Result));/* 填充数据 */pthread_exit(res);
}// 接收端
struct Result* res = (struct Result*)status;
free(res);

2. 线程设计准则

  1. 资源管理三原则

    • 谁分配谁释放
    • 退出前释放非共享资源
    • 使用RAII模式管理资源
  2. 锁使用规范

    // 推荐加锁方式
    pthread_mutex_lock(&mutex);
    do {/* 临界区操作 */
    } while(0);
    pthread_mutex_unlock(&mutex);// 避免的写法
    if(condition) pthread_mutex_unlock(&mutex);  // 易漏解锁
    
  3. 错误处理模板

    int ret = pthread_create(&tid, NULL, worker, NULL);
    if(ret != 0) {fprintf(stderr, "线程创建失败: %s\n", strerror(ret));exit(EXIT_FAILURE);
    }
    

3. 调试技巧

  1. 死锁检测

    • 使用pthread_mutex_trylock()测试锁状态
    • 记录加锁顺序
    • 使用Valgrind的Helgrind工具
  2. 性能分析

    # 使用perf分析锁竞争
    perf record -g -- ./program
    perf report
    

     八、完整生命周期图示

graph TDA[线程创建] --> B{执行阶段}B -->|正常完成| C[资源回收]B -->|被取消| D[清理处理]C --> E[线程终止]D --> EE --> F[系统回收TID]style B fill:#f9f,stroke:#333,stroke-width:2pxstyle C fill:#9f9,stroke:#333style D fill:#f99,stroke:#333subgraph 关键状态BCDend

九、常见问题解决方案

1. 僵尸线程问题

现象:线程终止但未回收,占用系统资源
解决方案

// 方案1:及时join
void* retval;
pthread_join(tid, &retval);
free(retval);// 方案2:设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);

2. 返回值内存泄漏

错误示例

void* worker() {int result = 42;pthread_exit(&result);  // 栈内存泄露!
}

正确实践

void* worker() {int* result = malloc(sizeof(int));  // 堆内存*result = 42;pthread_exit(result);
}

3. 取消点设置不足

问题表现:取消请求长期不响应
优化方案

void* worker(void* arg) {pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);while(1) {pthread_testcancel();  // 每循环添加取消点/* 长时间操作 */}return NULL;
}

十、扩展知识

1. 线程与进程对比

特性进程线程
资源开销高(独立地址空间)低(共享地址空间)
通信方式管道、共享内存、信号等全局变量、互斥锁、条件变量
容错性一个进程崩溃不影响其他线程崩溃导致整个进程终止
上下文切换成本

2. 可重入函数设计

安全函数特征

  • 不使用静态变量
  • 不调用非可重入函数
  • 所有数据通过参数传递

示例对比

// 不安全版本
char* strtok(char* str, const char* delim) {static char* buffer;  // 静态变量/* ... */
}// 可重入版本
char* strtok_r(char* str, const char* delim, char** saveptr) {/* 使用传入的saveptr保存状态 */
}

十一、信号量

一、信号量概述

  1. 本质:计数器+等待队列,用于控制多线程/进程对共享资源的访问
  2. 分类:
    • 无名信号量:内存中匿名对象,用于线程间同步
    • 有名信号量:文件系统可见,用于进程间同步

二、核心API详解

1. 无名信号量操作
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • pshared:0表示线程间共享,非0表示进程间共享
  • value:信号量初始值(可用资源数)
2. 有名信号量操作
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_unlink(const char *name);  // 删除系统残留信号量
3. 通用操作函数
// P操作(申请资源)
int sem_wait(sem_t *sem);       // 阻塞等待
int sem_trywait(sem_t *sem);    // 非阻塞尝试// V操作(释放资源)
int sem_post(sem_t *sem);// 销毁信号量
int sem_destroy(sem_t *sem);    // 无名信号量
int sem_close(sem_t *sem);      // 有名信号量

三、使用框架

// 无名信号量使用流程
sem_t sem;
sem_init(&sem, 0, 1);  // 初始化
sem_wait(&sem);         // P操作
// 临界区操作...
sem_post(&sem);         // V操作
sem_destroy(&sem);      // 销毁// 有名信号量使用流程
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);
sem_wait(sem);
// 临界区操作...
sem_post(sem);
sem_close(sem);
sem_unlink("/mysem");  // 最后使用后删除

四、线程同步示例

#include <semaphore.h>
#include <pthread.h>sem_t sem;
int shared_data;void* thread_func(void* arg) {sem_wait(&sem);// 操作共享资源shared_data++;sem_post(&sem);return NULL;
}int main() {sem_init(&sem, 0, 1);pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);sem_wait(&sem);printf("Shared data: %d\n", shared_data);sem_post(&sem);pthread_join(tid, NULL);sem_destroy(&sem);return 0;
}

五、线程属性管理

// 设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 或在线程创建后设置
pthread_detach(thread_id);// 主动让出CPU
pthread_yield();  // 或使用 usleep()

六、死锁专题

必要条件(需同时满足):
  1. 互斥访问
  2. 请求并保持
  3. 不可剥夺
  4. 循环等待
避免策略:
  • 资源有序分配法
  • 银行家算法
  • 设置超时机制(使用sem_timedwait)
  • 避免嵌套锁申请
  • 使用valgrind等工具检测

七、注意事项

  1. 信号量初始值决定行为:
    • 二进制信号量:初始值为1
    • 计数信号量:初始值>1
  2. 必须检查API返回值:
    if(sem_post(&sem) == -1) {perror("sem_post failed");
    }
    
  3. 销毁前确保:
    • 没有线程阻塞在信号量上
    • 不能销毁已销毁的信号量
  4. 有名信号量的命名:
    • 必须以斜杠开头(如"/mysem")
    • 长度限制(通常NAME_MAX-4)

八、补充说明

  1. POSIX vs System V信号量:

    • POSIX更轻量,接口更简洁
    • System V支持更复杂的控制
  2. 信号量与互斥锁的区别:

    特性信号量互斥锁
    所有者有(锁定线程)
    可递增值
    跨进程使用支持(有名)一般不支持

练习、创建两个线程,线程1 打印 hello,线程2 打印 world,预期效果在屏幕上严格打印hello world

#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
#include<semaphore.h>char buf[1024];
sem_t sem_w;
sem_t sem_r;void *do_something(void *arg)
{while(1){sem_wait(&sem_w);printf("hello ");sem_post(&sem_r);sleep(1);}return NULL;
}void *do_something1(void *arg)
{while(1){sem_wait(&sem_r);printf("world!\n");sem_post(&sem_w);sleep(1);}return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid1, tid2;int ret, ret1;sem_init(&sem_w,0,1);sem_init(&sem_r,0,0);if ((ret = pthread_create(&tid1, NULL, do_something, NULL)) != 0){errno = ret;perror("pthread_create for do_something fail");return -1;}if ((ret1 = pthread_create(&tid2, NULL, do_something1, NULL)) != 0){errno = ret1;perror("pthread_create for do_something1 fail");return -1;}pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

十二、练习

    练习1:创建一个线程
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>void * do_something(void *arg)
{printf("do copy file---\n");return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid;int ret;if((ret = pthread_create(&tid,NULL,do_something,NULL)) != 0){errno = ret;perror("pthread_create fail");return -1;}printf("-----main-------\n");sleep(1);return 0;return 0;
}
  练习2:创建多个线程
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>void * do_one(void *arg)
{printf("pthread 1 pid = %d\n",getpid());return NULL;
}void * do_two(void *arg)
{printf("pthread 2 pid = %d\n",getpid());return NULL;
}void * do_three(void *arg)
{printf("pthread 3 pid = %d\n",getpid());return NULL;
}typedef void *(*thread_cb_t)(void*);int main(int argc, const char *argv[])
{printf("---main---  pid = %d\n",getpid());pthread_t tid[3];int ret;thread_cb_t func[3] = {do_one,do_two,do_three};int i = 0;for(i = 0;i < 3;i++){if((ret = pthread_create(&tid[i],NULL,func[i],NULL)) != 0){errno = ret;perror("pthread1_create fail");return -1;}}sleep(1);return 0;return 0;
}
 练习3:线程的关闭
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>void * do_something(void *arg)
{static int ret = 100;printf("do copy file---\n");//pthread_exit("i am dead\n");pthread_exit(&ret);//return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid;int ret;if((ret = pthread_create(&tid,NULL,do_something,NULL)) != 0){errno = ret;perror("pthread_create fail");return -1;}printf("-----main-------\n");int *retval;//char *retval;pthread_join(tid,(void **)&retval);//printf("*retval = %s\n",retval);printf("*retval = %d\n",*retval);sleep(1);return 0;return 0;
}

练习4:多线程拷贝文件(缺陷当文件过大,会导致偏移量出错)
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>typedef struct
{int fd_s;int fd_d;int size;int len;int id;
}msg_t;void * do_copy (void *arg)
{msg_t p = *(msg_t*)arg;lseek(p.fd_s,p.size*p.id,SEEK_SET);lseek(p.fd_d,p.size*p.id,SEEK_SET);//	printf("tid = %ld id = %d fd_s = %d fd_d = %d size = %d len = %d\n",pthread_self(),p.id,p.fd_s,p.fd_d,p.size,p.len);//调试代码char buf[p.len];int ret = read(p.fd_s,buf,p.len);write(p.fd_d,buf,ret);return NULL; 
}//cp src dest 
int main(int argc, const char *argv[])
{if (argc!=3){printf("Usage: %s <src> <dest>\n",argv[0]);return -1;}int fd_s = open(argv[1],O_RDONLY);int fd_d = open(argv[2],O_WRONLY|O_TRUNC|O_CREAT,0666);if (fd_s < 0 || fd_d < 0){perror("open fail");return -1;}int n = 0;printf("Input threads num: ");scanf("%d",&n);int i = 0;int ret = 0;pthread_t tid[n];msg_t msg[n];struct stat st;if (stat(argv[1],&st) < 0){perror("stat fail");return -1;}int f_len = st.st_size;for (i = 0; i < n; ++i){msg[i].fd_s = fd_s;msg[i].fd_d = fd_d;msg[i].size = f_len / n;msg[i].id = i;#if 1if (i == n-1){ msg[i].len  = f_len - (f_len/n)*(n-1);}else {msg[i].len  = f_len/n;}
#endifret = pthread_create(&tid[i],NULL,do_copy,&msg[i]);if (ret != 0){errno = ret;perror("pthread_create fail");return -1;}}printf("----main-----\n");for (i = 0; i < n; ++i)pthread_join(tid[i],NULL);close(fd_s);close(fd_d);return 0;
}


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Missing required prop: “maxlength“

背景&#xff1a; 封装一个使用功能相同使用频率较高的input公共组件作为子组件&#xff0c;大多数长度要求为200&#xff0c;且实时显示统计子数&#xff0c;部分input有输入提示。 代码实现如下&#xff1a; <template><el-input v-model"inputValue" t…

DeepSeek引领目标检测新趋势:如何通过知识蒸馏优化模型性能

目录 一、知识蒸馏是什么&#xff1f; 二、知识蒸馏在目标检测中的重要性 提升实时性 跨任务迁移学习 三、如何使用知识蒸馏优化目标检测&#xff1f; 训练教师模型 生成软标签 训练学生模型 调节温度参数 多教师蒸馏&#xff08;可选&#xff09; 四、案例分享 定…

给老系统做个安全检查——Burp SqlMap扫描注入漏洞

背景 在AI技术突飞猛进的今天&#xff0c;类似Cursor之类的工具已经能写出堪比大部分程序员水平的代码了。然而&#xff0c;在我们的代码世界里&#xff0c;仍然有不少"老骥伏枥"的系统在兢兢业业地发光发热。这些祖传系统的代码可能早已过时&#xff0c;架构可能岌…

Pytorch实现论文:基于多尺度融合生成对抗网络的水下图像增强

简介 简介:提出了一种新型的水下图像增强算法,基于多尺度融合生成对抗网络,名为UMSGAN,以解决低对比度和颜色失真的问题。首先经过亮度的处理,将处理后的图像输入设计的MFFEM模块和RM模块生成图像。该算法旨在适应各种水下场景,提供颜色校正和细节增强。 论文题目:Und…

C++ DAY4

作业 代码 class Data { private:int a;int b; public://构造函数Data(int a0,int b0):a(a),b(b){}//set接口void setA(int index0){aindex;}void setB(int index0){bindex;}//get接口int getA(){return a;}int getB(){return b;}void show(){ cout <<"a " &…

Flutter 实现抖音风格底部导航栏

在移动应用开发中&#xff0c;良好的导航设计对用户体验至关重要。抖音作为一款现象级应用&#xff0c;其底部导航设计简洁直观&#xff0c;极具吸引力。本文将详细介绍如何使用 Flutter 开发一个类似抖音风格的底部导航栏&#xff0c;帮助开发者打造兼具美观与实用的导航界面。…

深入剖析:基于红黑树实现自定义 map 和 set 容器

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; 在 C 标准模板库&#xff08;STL&#xff09;的大家庭里&#xff0c;map和set可是超级重要的关联容器成员呢&#x1f60e;&#x…

前端面试题之HTML篇

1.src和href的区别 src用于替换当前元素&#xff0c;href用于在当前文档和引用资源之间确立联系。 src可用于img、input、style、script、iframe---同步加载执行 href可用于link、a---异步 1.用途不同 src 用于引入外部资源&#xff0c;通常是图像、视频、JavaScript 文件等&am…

硬件工程师入门教程

1.欧姆定律 测电压并联使用万用表测电流串联使用万用表&#xff0c;红入黑出 2.电阻的阻值识别 直插电阻 贴片电阻 3.电阻的功率 4.电阻的限流作用 限流电阻阻值的计算 单位换算关系 5.电阻的分流功能 6.电阻的分压功能 7.电容 电容简单来说是两块不连通的导体加上中间的绝…

01背包之---应用篇

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、01背包之---背包是否能被装满&#xff1f;例题1.分析题意例题2.分析题意 二、01背包之---装满背包有多少种组合?例题1.分析题意 三、01背包之---容量为N的…

DeepSeek赋能智慧文旅:新一代解决方案,重构文旅发展的底层逻辑

DeepSeek作为一款前沿的人工智能大模型&#xff0c;凭借其强大的多模态理解、知识推理和内容生成能力&#xff0c;正在重构文旅产业的发展逻辑&#xff0c;推动行业从传统的经验驱动向数据驱动、从人力密集型向智能协同型转变。 一、智能服务重构&#xff1a;打造全域感知的智…

uniapp修改picker-view样式

解决问题&#xff1a; 1.选中文案样式&#xff0c;比如字体颜色 2.修改分割线颜色 3.多列时&#xff0c;修改两边间距让其平分 展示效果&#xff1a; 代码如下 <template><u-popup :show"showPicker" :safeAreaInsetBottom"false" close&quo…

开源嵌入式实时操作系统uC/OS-II介绍

一、uC/OS-II的诞生&#xff1a;从开源实验到行业标杆 背景与起源 uC/OS-II&#xff08;Micro-Controller Operating System Version II&#xff09;诞生于1992年&#xff0c;由嵌入式系统先驱Jean J. Labrosse开发。其前身uC/OS&#xff08;1991年&#xff09;最初作为教学工…

8.spring对logback的支持

文章目录 一、入口二、源码解析LoggingApplicationListener 三、其它支持四、总结 本节以logback为背景介绍的 一、入口 gav: org.springframework.boot:spring-boot:3.3.4 spring.factories文件中有如下两个配置 org.springframework.boot.logging.LoggingSystemFactory\ …

OpenHarmony分布式数据管理子系统

OpenHarmony分布式数据管理子系统 简介 目录 组件说明 分布式数据对象数据共享分布式数据服务Key-Value数据库首选项关系型数据库标准数据化通路 相关仓 简介 子系统介绍 分布式数据管理子系统支持单设备的各种结构化数据的持久化&#xff0c;以及跨设备之间数据的同步、…

智慧后勤的消防管理:豪越科技为安全护航

智慧后勤消防管理难题大揭秘&#xff01; 在智慧后勤发展得如火如荼的当下&#xff0c;消防管理却暗藏诸多难题。传统模式下&#xff0c;消防设施分布得那叫一个散&#xff0c;就像一盘散沙&#xff0c;管理起来超费劲。人工巡检不仅效率低&#xff0c;还容易遗漏&#xff0c;不…

python轻量级框架-flask

flask简述 Flask 是 Python 生态圈中一个基于 Python 的Web 框架。其轻量、模块化和易于扩展的特点导致其被广泛使用&#xff0c;适合快速开发 Web 应用以及构建小型到中型项目。它提供了开发 Web 应用最基础的工具和组件。之所以称为微框架&#xff0c;是因为它与一些大型 We…

政安晨【零基础玩转各类开源AI项目】DeepSeek 多模态大模型Janus-Pro-7B,本地部署!支持图像识别和图像生成

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 目录 下载项目 创建虚拟环境 安装项目依赖 安装 Gradio&#xff08;UI&#xff09; 运…

在 Mac mini M2 上本地部署 DeepSeek-R1:14B:使用 Ollama 和 Chatbox 的完整指南

随着人工智能技术的飞速发展&#xff0c;本地部署大型语言模型&#xff08;LLM&#xff09;已成为许多技术爱好者的热门选择。本地部署不仅能够保护隐私&#xff0c;还能提供更灵活的使用体验。本文将详细介绍如何在 Mac mini M2&#xff08;24GB 内存&#xff09;上部署 DeepS…

【Godot4.3】基于绘图函数的矢量蒙版效果与UV换算

概述 在设计圆角容器时突发奇想&#xff1a; 将圆角矩形的每个顶点坐标除以对应圆角矩形所在Rect2的size&#xff0c;就得到了顶点对应的UV坐标。然后使用draw_colored_polygon&#xff0c;便可以做到用图片填充圆角矩形的效果。而且这种计算的效果就是图片随着其填充的图像缩…