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

linux并发服务器 —— 多线程并发(六)

线程概述

同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域;

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位;

Linux环境下,线程的本质就是进程;

ps -Lf pid:查指定进程LWP号(线程号)

线程和进程的区别

1. 进程间的信息难以共享,除只读代码段,父子进程并未共享内存;

        线程共享信息方便快速(进程、父进程、进程组、会话ID,文件描述符表,当前工作目录,文件权限掩码,虚拟地址空间(除栈、.text));但超线程ID、信号掩码、error变量、调度策略和优先级、栈、本地变量不共享;

2. fork创建进程代价较高

        创建线程比创建进程快一个数量级以上

线程操作

/*#include <pthread.h>一般情况下,main所在线程为主线程/main线程,其余都成为子线程pthread_t pthread_self(void);功能:获取当前线程IDint pthread equal(pthread_t tl,pthread_t t2);功能:比较两个线程号是否相等不同操作系统,pthread_t类型实现不一样,有可能是结构体int pthread_create(pthread t *thread, const pthread attr t *attr,void *(*start_routine) (void *), void *arg);功能:创建一个子线程(调度的基本单位)参数:thread - 传出参数:线程创建成功,子线程ID会写入该变量attr - 设置线程的属性,默认值 - NULLstart_rountine - 函数指针,子线程需要处理的逻辑代码arg - 给start_rountine使用,传参返回值:成功 - 0失败 - 错误号,与errno不同;获取错误号信息:char* strerror(int errnum);void pthread_exit(void *retval);功能:终止一个当前调用线程参数:retval - 传递一个指针,作为一个返回值,可以在pthread_join中获取返回值: 没有任何返回值int pthread_join(pthread_t thread,void **retval);功能:和一个已经终止的线程进行连接回收子线程的资源这个函数是阻塞函数,调用一次只能回收一个子线程一般在主线程中去使用参数:thread - 需要回收的子线程IDretval - 接收子线程退出的返回值返回值:成功 - 0失败 - !0int pthread_detach(pthread_t thread);功能:分离一个线程,将线程标记分离,线程终止时自动释放资源给系统1. 不能多次分离,不可预料2. 不能去连接一个已经分离的线程,会报错(join)参数:需要分离的线程ID返回值:成功 - 0失败 - errorint pthread_cancel(pthread_t thread);功能:取消线程(让线程终止),中途暂停!但并不是立马终止,而是当一个子线程执行到一个取消点,线程才会终止取消点:系统规定好的一些系统调用,可以粗略认为是用户去到内核区的切换这个位置
*/

创建线程

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void *arg){cout<<"子线程...."<<*((int*)arg)<<endl;return NULL;
}int main(){pthread_t tid;int num = 10;int ret = pthread_create(&tid , NULL , callback , (void*)&num);if(ret != 0){char* str = strerror(ret);cout<<"error: "<<str<<endl;}for(int i = 0 ; i<5 ; i++){cout<<i<<endl;}sleep(1);return 0;
}

终止线程

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void *arg){char buf[1024];sprintf(buf , "子线程....%ld" , pthread_self());cout<<buf<<endl;return NULL;
}int main(){// 创建子线程pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<str<<endl;}for(int i = 0 ; i<100 ; i++){cout<<i<<endl;}cout<<"子线程...."<<tid<<endl;cout<<"主线程...."<<pthread_self()<<endl;pthread_exit(NULL);// 主线程退出不会影响正常运行的线程return 0; // 进程退出 所有子线程立刻终止
}

链接已终止的线程

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;int val = 10;void* callback(void *arg){char buf[1024];sprintf(buf , "子线程....%ld" , pthread_self());cout<<buf<<endl;// sleep(3);// return NULL; // pthread_exit(NULL);// int val = 10; // 局部变量pthread_exit((void*)&val);
}int main(){// 创建子线程pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<str<<endl;}for(int i = 0 ; i<5 ; i++){cout<<i<<endl;}cout<<"子线程...."<<tid<<endl;cout<<"主线程...."<<pthread_self()<<endl;int* ptr;if(pthread_join(tid , (void **)&ptr) != 0){char* str = strerror(ret);cout<<str<<endl;}cout<<"回收子线程成功: "<<*(int *)ptr<<endl;pthread_exit(NULL);// 主线程退出不会影响正常运行的线程return 0; // 进程退出 所有子线程立刻终止
}

线程分离

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void* arg){cout<<"我的ID: "<<pthread_self()<<endl;return NULL;
}int main(){// 创建pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<"error1: "<<str<<endl;}cout<<"父线程:"<<pthread_self()<<"子线程:"<<tid<<endl;//子线程分离ret = pthread_detach(tid);if(ret != 0){char* str = strerror(ret);cout<<"error2: "<<str<<endl;}//对分离子线程进行连接ret = pthread_join(tid,NULL);if(ret != 0){char* str = strerror(ret);cout<<"error3: "<<str<<endl;}pthread_exit(NULL);return 0;
}

线程取消

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void* arg){cout<<"我的ID: "<<pthread_self()<<endl;for(int i = 0 ; i<5 ; i++){cout<<"子线程:"<<i<<endl;}return NULL;
}int main(){// 创建pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<"error1: "<<str<<endl;}// 取消线程pthread_cancel(tid);for(int i = 0 ; i<10 ; i++){cout<<i<<endl;}cout<<"父线程:"<<pthread_self()<<"子线程:"<<tid<<endl;pthread_exit(NULL);return 0;
}

线程属性

/*
int pthread_attr_init(pthread_attr_t *attr);初始化线程属性变量int pthread_attr_destroy(pthread_attr_t *attr);释放线程属性资源int pthread_attr_getdetachstate(const pthread_attq_t *attr, int* detachstate);获取线程分离的状态属性int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);设置线程分离的状态属性*/#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void* arg){cout<<"我的ID: "<<pthread_self()<<endl;return NULL;
}int main(){// 创建线程属性变量pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_DETACHED);// 获取线程栈的大小size_t size;pthread_attr_getstacksize(&attr , &size);cout<<"子线程占空间大小:"<<size<<endl;// 创建pthread_t tid;int ret = pthread_create(&tid , &attr , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<"error1: "<<str<<endl;}cout<<"父线程:"<<pthread_self()<<"子线程:"<<tid<<endl;pthread_attr_destroy(&attr);ret = pthread_join(tid,NULL);if(ret != 0){char* str = strerror(ret);cout<<"error3: "<<str<<endl;}pthread_exit(NULL);return 0;
}

线程同步

必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量;

临界区是指访问某一共享资源的代码片段,这段代码的执行应该为原子操作(不能分割);

互斥锁

使用互斥锁来确保仅有一个线程可以访问某项共享资源,保证原子访问;

互斥锁由两种状态:锁定/未锁定,试图对锁定的互斥锁再加锁会导致线程阻塞/报错,取决于加锁使用的方法;

线程加锁成为互斥锁的所有者,只有所有者才能解锁;

/*
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutexconst pthread_mutexattr_t *restrict attr);功能:初始化互斥锁参数:mutex - 需要初始化的互斥锁attr - 互斥锁相关属性 NULLrestric - C语言修饰符,被修饰的指针不能由另外的指针进行操作
int pthread_mutex_destroy(pthread_mutex_t *mutex);释放互斥量的资源
int pthread_mutex_lock(pthread_mutex_t *mutex);加锁 , 如果有线程已经加锁,只能阻塞等待
int pthread_mutex_trylock(pthread_mutex_t *mutex);尝试加锁,加锁失败不会阻塞,会直接返回
int pthread_mutex_unlock(pthread_mutex_t *mutex);释放锁
*/

死锁

多个进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象;

导致死锁的三个主要原因:

1. 加锁忘记释放

2. 重复枷锁

3. 线程之间对于锁循环等待

读写锁

读写锁允许多个读出,但只允许一个写入:

1. 如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作;

2. 有其他线程写数据,则其他线程不允许读/写;

3. 写是独占的,写的优先级高;

/*读写锁的类型 pthread_rwlock_tint pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
*/
// 案例:创建8个线程,操作同一个全局变量;
//      3个线程不定时写一个全局变量,其余5个线程不定时读全局变量
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <unistd.h>
using namespace std;int num = 1;
// pthread_mutex_t mutex;
pthread_rwlock_t rwlock;void* wnum(void* arg){while(1){// pthread_mutex_lock(&mutex);pthread_rwlock_wrlock(&rwlock);num++;printf("++write , tid: %ld , num : %d\n" , pthread_self() , num);// pthread_mutex_unlock(&mutex);pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}void* rnum(void* arg){while(1){// pthread_mutex_lock(&mutex);pthread_rwlock_rdlock(&rwlock);printf("read , tid: %ld , num : %d\n" , pthread_self() , num);// pthread_mutex_unlock(&mutex);pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}int main(){// pthread_mutex_init(&mutex , NULL);pthread_rwlock_init(&rwlock , NULL);// 创建3个写线程 5个读线程pthread_t wtids[3] , rtids[5];for(int i = 0 ; i<3 ; i++){pthread_create(&wtids[i] , NULL , wnum , NULL);}for(int i = 0 ; i<5 ; i++){pthread_create(&rtids[i] , NULL , rnum , NULL);}// 设置线程分离for(int i = 0 ; i<3 ; i++){pthread_detach(wtids[i]);}for(int i = 0 ; i<5 ; i++){pthread_detach(rtids[i]);}pthread_exit(NULL);// pthread_mutex_destroy(&mutex);pthread_rwlock_destroy(&rwlock);return 0;
}

生产者消费者模式

多生产者 - 容器 - 多消费者

阻塞 - 通知机制,需要条件变量和信号量来进行实现;

条件变量 - 通过条件变量来唤醒阻塞进程

信号量 - 一定程度上表示资源的多少

/*信号量的类型 sem_tint sem_init(sem_t *sem, int pshared,unsigned int value);初始化信号量参数:sem - 信号量变量的地址pshared - 0用在线程,非0用在进程value - 信号量中的值int sem_destroy(sem_t *sem);释放资源int sem_wait(sem_t *sem);加锁 对信号量的值减1,如果值为0则阻塞int sem_trywait(sem_t *sem);尝试int sem_timedwait(sem_t *sem, const struct timespec *abs timeout);等待多少时间int sem_post(sem_t *sem);解锁 对信号量的值加1int sem_getvalue(sem_t *sem, int *sval);获取值
*/
// 生产者消费者模型 粗略版本
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <semaphore.h>
using namespace std;// 创建一个互斥锁
pthread_mutex_t mutex;
// 创建两个信号量
sem_t p , c;class Node{
public:int num;Node* next;
};Node* head = NULL;void* pro(void* arg){// 不断创建节点添加到链表while(1){sem_wait(&p);pthread_mutex_lock(&mutex);Node* newNode = new Node();newNode->next = head;head = newNode;newNode->num = rand()%100;printf("add node , num: %d , tid: %ld\n" , newNode->num , pthread_self());   pthread_mutex_unlock(&mutex);sem_post(&c);usleep(1000);}return NULL;
}void* cus(void* arg){while(1){sem_wait(&c);pthread_mutex_lock(&mutex);Node* cur = head;head = head->next;printf("del node : %d , tid : %ld\n" , cur->num , pthread_self());delete(cur);cur = NULL;pthread_mutex_unlock(&mutex);sem_post(&p);usleep(1000);}return NULL;
}int main(){pthread_mutex_init(&mutex , NULL);sem_init(&p , 0 , 5);sem_init(&c , 0 , 0);// 5个生产者,5个消费者pthread_t ptids[5] , ctids[5];for(int i = 0 ; i<5; i++){pthread_create(&ptids[i] , NULL , pro , NULL);pthread_create(&ctids[i] , NULL , cus , NULL);}for(int i = 0 ; i<5 ; i++){pthread_detach(ptids[i]);pthread_detach(ctids[i]);}while(1){sleep(10);}pthread_mutex_destroy(&mutex);pthread_exit(NULL);return 0;
}

相关文章:

linux并发服务器 —— 多线程并发(六)

线程概述 同一个程序中的所有线程均会独立执行相同程序&#xff0c;且共享同一份全局内存区域&#xff1b; 进程是CPU分配资源的最小单位&#xff0c;线程是操作系统调度执行的最小单位&#xff1b; Linux环境下&#xff0c;线程的本质就是进程&#xff1b; ps -Lf pid&…...

Nginx 部署 配置

一.概述 什么是nginx? Nginx (engine x) 是一款轻量级的Web 服务器 、反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。 什么是反向代理&#xff1f; 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服务器来接受internet上的连接请求…...

数据结构:时间复杂度和空间复杂度计算

1.什么是时间复杂度和空间复杂度&#xff1f; 1.1算法效率 算法效率分析分为两种&#xff1a;第一种是时间效率&#xff0c;第二种是空间效率。时间效率被称为时间复杂度&#xff0c; 而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空间…...

云原生Kubernetes:二进制部署K8S单Master架构(一)

目录 一、理论 1.K8S单Master架构 2. etcd 集群 3.flannel网络 4.K8S单Master架构环境部署 5.部署 etcd 集群 6.部署 docker 引擎 7.flannel网络配置 二、实验 1.二进制部署K8S单Master架构 2. 环境部署 3.部署 etcd 集群 4.部署 docker 引擎 5.flannel网络配置…...

ICCV 2023 | 利用双重聚合的Transformer进行图像超分辨率

导读 本文提出一种同时利用图像空间和通道特征的 Transformer 模型&#xff0c;DAT&#xff08;Dual Aggregation Transformer&#xff09;&#xff0c;用于图像超分辨&#xff08;Super-Resolution&#xff0c;SR&#xff09;任务。DAT 以块间和块内的双重方式&#xff0c;在空…...

经纬恒润预期功能安全(SOTIF)解决方案为自动驾驶安全保驾护航

近年来&#xff0c;“安全”被普遍认为是智能驾驶汽车被用户接受或者得到商业应用最大的问题&#xff0c;ISO26262功能安全旨在避免由E/E系统功能失效导致的不可接受的风险&#xff0c;主要是针对系统性失效/随机硬件失效导致的风险进行分析和控制&#xff0c;然而传感器和感知…...

java从入门到起飞(七)——面向对象

文章目录 一、什么是面向对象1.1 定义1.2 特点 二、面向对象的基础2.1 面向对象的基础是抽象2.2 抽象的作用2.3 类和对象2.4 属性和方法2.5 构造方法 三、面向对象的三大特征3.1 封装3.1.1 封装的意义3.1.2 封装的实现 3.2 继承3.2.1 继承的意义3.2.2 继承的实现 3.3 多态3.3.1…...

题集-三路划分和三数取中(快排优化)

快排排序是非常快的&#xff0c;但是有一种情况快排是无法进行的。 912. 排序数组 - 力扣&#xff08;LeetCode&#xff09; 这道题看上去没什么问题&#xff0c;但是如果我们用快排去提交的话&#xff0c;发现快排其实是被针对了的。 有一个样例是这样的。如果我们按照快排的…...

设计模式-迭代器

文章目录 1. 引言1.1 概述1.2 设计模式1.3 迭代器模式的应用场景1.4 迭代器模式的作用 2. 基本概念2.1 迭代器 Iterator2.2 聚合 Aggregate2.3 具体聚合 ConcreteAggregate 3. Java 实现迭代器模式3.1 Java 集合框架3.2 Java 迭代器接口3.3 Java 迭代器模式实现示例 4. 迭代器模…...

Hive学习(12)Hive常用日期函数

1、hive返回当天三种方式 select current_date; --返回年月日 --2017-06-15 select current_timestamp; --返回年月日时分秒 --2017-06-15 19:54:44 SELECT from_unixtime(unix_timestamp()); --2017-06-15 19:55:042、from_unixtime&#xff1a;转化unix时间戳到当前时区的时…...

PowerQuery动态加载M公式

Power Query 是Excel中的强大数据处理与转换工具&#xff0c;如果需要“动态”处理数据&#xff0c;大家第一时间想到的是可以使用VBA&#xff0c;利用代码创建M公式&#xff0c;进而创建PQ查询&#xff0c;但是复杂的M公式可能有很多行&#xff0c; 使用VBA处理起来并不是很方…...

2分钟搭建FastGPT训练企业知识库AI助理(Docker部署)

我们使用宝塔面板来进行搭建&#xff0c;更方便快捷灵活&#xff0c;争取操作时间只需两分钟 宝塔面板下安装Docker 在【软件商店中】安装【docker管理器】【docker模块】即可 通过Docker安装FastGPT 通过【Docker】【添加容器】【容器编排】创建里新增docker-compose.yaml以下…...

TDengine函数大全-字符串函数

以下内容来自 TDengine 官方文档 及 GitHub 内容 。 以下所有示例基于 TDengine 3.1.0.3 TDengine函数大全 1.数学函数 2.字符串函数 3.转换函数 4.时间和日期函数 5.聚合函数 6.选择函数 7.时序数据库特有函数 8.系统函数 字符串函数 TDengine函数大全CHAR_LENGTHCONCATCONCA…...

part-02 C++知识总结(类型转换)

一.C常用的类型转换函数 在C中&#xff0c;有几种自带的类型转换函数可以用于不同类型之间的转换。以下是其中一些常用的自带类型转换函数&#xff1a; 1.隐式转换&#xff08;Implicit Conversion&#xff09; 数字类型之间的隐式转换&#xff0c;例如将int转换为float、do…...

stable diffusion实践操作-图生图

本文专门开一节写图生图相关的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 正文...

Jtti:Ubuntu18.04如何修改远程ssh端口号

要在Ubuntu 18.04上修改SSH的远程端口号&#xff0c;您需要编辑SSH服务器配置文件并指定新的端口号。以下是具体的步骤&#xff1a; 以root或具有sudo权限的用户登录到您的Ubuntu服务器。 备份SSH配置文件&#xff08;可选&#xff09;&#xff1a; 在进行任何更改之前&…...

微软表示Visual Studio的IDE即日起开启“退休”倒计时

据了解&#xff0c;日前有消息透露称&#xff0c;适用于 Mac平台的Visual Studio集成开发环境(IDE)于8月31日启动“退休”进程。 而这意味着Visual Studio for Mac 17.6将继续支持12个月&#xff0c;一直到2024年8月31日。    微软表示后续不再为Visual Studio for Mac开发…...

好马配好鞍:Linux Kernel 4.12 正式发布

Linus Torvalds 在内核邮件列表上宣布释出 Linux 4.12&#xff0c;Linux 4.12 的主要特性包括&#xff1a; BFQ 和 Kyber block I/O 调度器&#xff0c;livepatch 改用混合一致性模型&#xff0c;信任的执行环境框架&#xff0c;epoll 加入 busy poll 支持等等&#xff0c;其它…...

element——switch接口成功后赋值打开开关

应用场景 基本用法使用v-model双向绑定值&#xff0c;进行开关控制 例子1:需求&#xff1a; **点击switch&#xff0c;出弹窗&#xff0c;点击弹窗保存按钮调接口成功后再赋值&#xff08;row.orderButtonValue“1”&#xff09;打开switch开的状态变颜色。 在vue 中使用 :va…...

WPF Border设置渐变色

背景色渐变 <Border> <Border.Resources> <Style TargetType"Border"> <Setter Property"Background"> …...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...