Go基础学习08-并发安全型类型-通道(chan)深入研究
文章目录
- chan基础使用和理解
- 通道模型:单通道、双通道
- 双向通道
- 单向通道
- 单向通道的作用
- 缓冲通道和非缓冲通道数据发送和接收过程
- 缓冲通道
- 非缓冲通道
- 通道基本特性
- 通道何时触发panic
- Channel和Select结合使用
- Select语句和通道的关系
- Select语句的分支选择规则有那些
- Select和Channel结合使用案例一
- Select和Channel结合使用案例二
- Select和for联合使用时如何退出最外层循环
在前面学习中了解到对于单值变量,如:int、string;多值变量,如:map存在多协程对资源竞争的并发问题,为了解决并发性通常需要引入sync.Mutex解决。 通道类型本身就是并发安全的,是Go语言自带的、唯一一个可以满足并发安全性的类型。
chan基础使用和理解
package mainimport ("fmt"
)func main() {// 使用make声明并初始化一个int型的带缓冲的通道,并将其容量设置为3ch1 := make(chan int, 3)// 使用make声明并初始化一个int型的不带缓冲的通道,其容量为0ch2 := make(chan int)// 声明一个int型的通道var ch3 chan int// 使用接送操作符 <- 向通道ch1中发送int型数据1go func(){ch1 <- 1ch1 <- 2ch1 <- 3}()// 使用接送操作符 <- 从通道ch1中读取数据,下面的短变量表达式左边有两个变量num, ok:其中ok用于判断通道ch1是否关闭,// 当ok == ture时表示通道没有关闭,可以读取数据并将其保存到变量nums中,当ok == false时表示通道关闭,不能读取数据。// 此外读取通道中的数据,可以直接使用num := <- ch1进行读取,不添加第二个判断通道是否关闭的条件,此时有风险存在num, ok := <-ch1if ok {fmt.Println(num)}close(ch1)close(ch2)// close(ch3)
}
对于通道的基本声明方式有三种:声明并初始化带缓冲的通道(ch1);声明并初始化一个不带缓冲的通道(ch2);仅仅声明一个通道(ch3)
什么是通道:一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符<-。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。
上述代码:对于通道3由于仅仅声明没有进行初始化,所以不能执行关闭操作。 对于未初始化的通道执行close操作报panic:
panic: close of nil channel
// go语言中通道类型为引用类型,故只声明而未初始化时通道值为nil。
通道模型:单通道、双通道
双向通道
channel两端的goroutine既可以发送数据也可以接受数据。
默认情况下使用创建的通道即为双向通道。
单向通道
创建的channel规定了数据流向,对于channel双端,分别一端作为发送者(producer)、一端作为接收者(consumer),只能发送或者接收。
创建单向通道:对于单向通道而言,发送和接收均是站在数据的角度:
- 如果向通道中发送数据,则为发送通道。
- 如果从通道中接收数据,则为接收通道。
// 单向发送通道ch1 := make(chan<-, 2)var ch2 chan<-// 单向接收通道ch3 := make(<-chan, 2)var ch4 <-chan
单向通道的作用
- 单向通道的主要约束其他代码的行为。 看下面示例代码:
package mainimport "fmt"/*
*
创建单向通道
*/
func main() {// 定义带缓冲通道ch := make(chan int, 3)producer(ch, 7)ans := consumer(ch)fmt.Printf("main function get num is %v\n", ans)
}
func producer(ch chan<- int, num int) {ch <- num
}
func consumer(ch <-chan int) (ans int) {ans, ok := <-chif ok {fmt.Printf("consumer get data is %v\n", ans)return ans}return 0
}
在Go语言中把元素类型匹配的双向通道传递给单向通道,会自动把双向通道转换为函数需要的单向通道(发送 or 接收)
上述代码简单定义一个生产者函数,向通道中写入数据;定义一个消费者函数,从特定通道中消费数据。借助于通道实现了消息的特定方向移动。
更普适应的使用——对接口的行为进行约束:从而对接口的所有实现者都进行约束的目的。
type Notifier interface{SendInt(ch chan<- int)
}
上述代码中我们对Notidier接口的SendInt函数使用了单向通道约束,在所有Notifier接口的所有实现类型中,SendInt函数都会受到单向通道的约束。
- 在函数声明的结果列表中使用单向通道:
package mainimport "fmt"func main() {getChan := getIntChan()for elem := range getChan {fmt.Println(elem)}
}// 获取一个单向发送通道
func getIntChan() <-chan int {num := 5ch := make(chan int, num)for i := 0; i < num; i++ {ch <- i}close(ch)return ch
}
调用getIntChan()函数获取一个单向接收通道getChan,随后使用带有range的for遍历接收通道中的每一个元素并对其打印。
结合通道的特性:上述代码在getChan没有元素或者,为nil时会阻塞在for的那行代码处,上述只是展示单向通道的用于,不具有实际意义。
缓冲通道和非缓冲通道数据发送和接收过程
缓冲通道
ch := make(chan int, 5)
上述代码创建一个容量为5的双向通道。在Go语言中对于通道的初始化不像切片初始化可以指定切片的长度,在通道初始化时只需要设置一个缓冲容量,通道中的长度含义就是通道中的元素个数。
带缓冲通道数据发送:元素进行复制,将副本放入通道中,同时将通道中的原值删除。
如果缓冲容量已经满了,此时新来的goroutine会被放到一个FIFO队列中,等到缓冲区有容量,此时最前面的goroutine执行元素放置操作。
带缓冲通道数据接收:生成在通道中的元素值的副本,将副本给到接收方。
接收操作类似,如果缓冲区中没有元素,此时所有接收者会阻塞,并进入一个FIFO队列中,当缓冲区有数据后,在队列最前面的goroutine会获取channel最前端的元素值。
缓冲通道何时阻塞:
- 缓冲区满时,所有发送goroutine阻塞。
- 缓冲区没有数据时,所有接收goroutine阻塞。
- 对与nil的通道,无论何时一直阻塞。
在一般情况下缓冲通道起到数据传输的中间件作用,需要将数据暂时存储到缓冲区中,但是当缓冲通道执行发送操作时发现空的通道中正好有等待的接收者,此时发送者会直接将数据拷贝给接收者,减少数据在缓冲区的临时拷贝。(类似与非缓冲通道)
非缓冲通道
ch := make(chan int) // 创建非缓冲通道
非缓冲通道必须等待发送方和接收方均就绪,才会进行数据发送以及接收,否则对应的goroutine处于组塞。并且数据发送以及接收过程,在通道中不会产生数据副本,发送方数据拷贝后直接通过通道传递给接收方。
非缓冲通道何时阻塞:
- 发送方或接收方任何一方没有准备好,都会阻塞。
- 对于nil的通道,无论何时一直阻塞。
通道基本特性
- 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的:在同一时刻,Go 语言的运行时系统只会执行对同一个通道的任意个发送操作中的某一个。直到这个元素值被完全复制进该通道之后,其他针对该通道的发送操作才可能被执行。类似的,在同一时刻,运行时系统也只会执行,对同一个通道的任意个接收操作中的某一个。直到这个元素值完全被移出该通道之后,其他针对该通道的接收操作才可能被执行。即使这些操作是并发执行的也是如此。
- 发送操作和接收操作中对元素值的处理都是不可分割的。:保证通道中元素值的完整性,同时保证通道操作的唯一性
- 发送操作要么还没复制元素值,要么已经复制完毕,绝不会出现只复制了一部分的情况。
- 接收操作在准备好元素值的副本之后,一定会删除掉通道中的原值,绝不会出现通道中仍有残留的情况。
- 发送操作在完全完成之前会被阻塞。接收操作也是如此。
通道何时触发panic
- 对于值为nil(未初始化)的通道执行close()操作。
- 对于已经执行了close()操作的通道再次执行close()操作。
- 对于执行了close()操作的通道执行收发操作。
Channel和Select结合使用
Select语句和通道的关系
- select语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。
- select语句的分支分为两种,一种叫做候选分支,另一种叫做默认分支。候选分支总是以关键字case开头,后跟一个case表达式和一个冒号,然后我们可以从下一行开始写入当分支被选中时需要执行的语句。
- 默认分支其实就是 default case,因为,当且仅当没有候选分支被选中时它才会被执行,所以它以关键字default开头并直接后跟一个冒号。同样的,我们可以在default:的下一行写入要执行的语句。
- 由于select语句是专为通道而设计的,所以每个case表达式中都只能包含操作通道的表达式,比如接收表达式。
Select语句的分支选择规则有那些
-
对于每一个case表达式,都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式,同时也可能会包含其他的表达式。比如,如果case表达式是包含了接收表达式的短变量声明时,那么在赋值符号左边的就可以是一个或两个表达式,不过此处的表达式的结果必须是可以被赋值的。当这样的case表达式被求值时,它包含的多个表达式总会以从左到右的顺序被求值。
-
select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值,并且求值的顺序是依从代码编写的顺序从上到下的。结合上一条规则,在select语句开始执行时,排在最上边的候选分支中最左边的表达式会最先被求值,然后是它右边的表达式。仅当最上边的候选分支中的所有表达式都被求值完毕后,从上边数第二个候选分支中的表达式才会被求值,顺序同样是从左到右,然后是第三个候选分支、第四个候选分支,以此类推。
-
对于每一个case表达式,如果其中的发送表达式或者接收表达式在被求值时,相应的操作正处于阻塞状态,那么对该case表达式的求值就是不成功的。在这种情况下,我们可以说,这个case表达式所在的候选分支是不满足选择条件的。
-
仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足选择条件,那么默认分支就会被执行。如果这时没有默认分支,那么select语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select语句(或者说它所在的 goroutine)就会被唤醒,这个候选分支就会被执行。
-
如果select语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行。注意,即使select语句是在被唤醒时发现的这种情况,也会这样做。
-
一条select语句中只能够有一个默认分支。并且,默认分支只在无候选分支可选时才会被执行,这与它的编写位置无关。
-
select语句的每次执行,包括case表达式求值和分支选择,都是独立的。不过,至于它的执行是否是并发安全的,就要看其中的case表达式以及分支中,是否包含并发不安全的代码了。
Select和Channel结合使用案例一
package mainimport ("fmt""math/rand"
)/*
*
channel和select的联合使用
*/
func main() {// 创建一个多维通道chs := []chan int{make(chan int, 1),make(chan int, 1),make(chan int, 1),}// 创建随机数index := rand.Intn(3)fmt.Printf("index is: %v\n", index)chs[index] <- 1select {case elem := <-chs[0]:fmt.Printf("通道0被选中,元素为:%v\n", elem)case elem := <-chs[1]:fmt.Printf("通道1被选中,元素为:%v\n", elem)case elem := <-chs[2]:fmt.Printf("通道2被选中,元素为:%v\n", elem)default:fmt.Println("error.")}
}
- 如果像上述示例那样加入了默认分支,那么无论涉及通道操作的表达式是否有阻塞,select语句都不会被阻塞。如果那几个表达式都阻塞了,或者说都没有满足求值的条件,那么默认分支就会被选中并执行。
- 如果没有加入默认分支,那么一旦所有的case表达式都没有满足求值条件,那么select语句就会被阻塞。直到至少有一个case表达式满足条件为止。
- 可能会因为通道关闭了,而直接从通道接收到一个其元素类型的零值。所以,在很多时候,我们需要通过接收表达式的第二个结果值来判断通道是否已经关闭。一旦发现某个通道关闭了,我们就应该及时地屏蔽掉对应的分支或者采取其他措施。
- select语句只能对其中的每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道的话,就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句
Select和Channel结合使用案例二
package mainimport ("fmt""time"
)func main() {ch := make(chan int, 1)time.AfterFunc(time.Second*2, func() {close(ch)})select {case _, ok := <-ch:if !ok {fmt.Println("The candidate case is closed.")break}fmt.Println("The candidate case is selected.")}
}
声明并初始化了一个叫做intChan的通道,然后通过time包中的AfterFunc函数约定在二秒钟之后关闭该通道。后面的select语句只有一个候选分支,我在其中利用接收表达式的第二个结果值对intChan通道是否已关闭做了判断,并在得到肯定结果后,通过break语句立即结束当前select语句的执行。
Select和for联合使用时如何退出最外层循环
- break和标签配合使用,直接break处出指定的循环体。
- goto语句跳转到指定标签。
代码示例:
package mainimport ("fmt""time"
)func main() {go func() {ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3time.AfterFunc(time.Second*3, func() {close(ch)})// 方式一:使用break配合标签实现loop:for {select {case _, ok := <-ch:if !ok {fmt.Println("The ch is closed.")break loop // 使用return配合标签退出for循环}fmt.Println("The ch case is selected.")}}fmt.Println("The end of ch.")}()go func() {ch1 := make(chan int, 3)ch1 <- 5ch1 <- 6ch1 <- 7time.AfterFunc(3*time.Second, func() {close(ch1)})for {select {case _, ok := <-ch1:if !ok {fmt.Println("The ch1 is closed.")goto loop1 // 使用goto配合标签实现退出select和for的结合使用}fmt.Println("The ch1 case is selected.")}}loop1:fmt.Println("The end of ch1.")}()// 等待10防止主协程退出后所有子协程死亡time.Sleep(time.Second * 10)fmt.Println("The end of main.")
}
上述代码执行结果:
The ch1 case is selected.
The ch1 case is selected.
The ch1 case is selected.
The ch case is selected.
The ch case is selected.
The ch case is selected.
The ch is closed.
The end of ch.
The ch1 is closed.
The end of ch1.
The end of main.
上述代码创建两个协程,一个协程中验证break配合标签实现退出外层for循环,另一个协程中验证goto配合标签实现退出外层for循环。同时为了防止main协程退出导致两个子协程退出,在主协程中调用time.Sleep()函数休眠10秒钟。
如果在select语句中发现某个通道已关闭:为了防止再次进入这个分支,可以把这个channel重新赋值成为一个长度为0的非缓冲通道,这样这个case就一直被阻塞了
相关文章:
Go基础学习08-并发安全型类型-通道(chan)深入研究
文章目录 chan基础使用和理解通道模型:单通道、双通道双向通道单向通道单向通道的作用 缓冲通道和非缓冲通道数据发送和接收过程缓冲通道非缓冲通道 通道基本特性通道何时触发panicChannel和Select结合使用Select语句和通道的关系Select语句的分支选择规则有那些Sel…...
some 蓝桥杯题
12.反异或01串 - 蓝桥云课 (lanqiao.cn) #include "bits/stdc.h" #define int long long using namespace std; char c[10000000]; char s[10000000]; int cnt,Ans,mr,mid; int maxi; int p[10000000],pre[10000000]; signed main() {ios::sync_with_stdio(0);cin.t…...
[linux 驱动]input输入子系统详解与实战
目录 1 描述 2 结构体 2.1 input_class 2.2 input_dev 2.4 input_event 2.4 input_dev_type 3 input接口 3.1 input_allocate_device 3.2 input_free_device 3.3 input_register_device 3.4 input_unregister_device 3.5 input_event 3.6 input_sync 3.7 input_se…...
2023_Spark_实验十:Centos_Spark Local模式部署
参考这篇博客:【Centos8_配置单节点伪分布式Spark环境】_centos8伪分布式环境搭建-CSDN博客...
pyecharts-快速入门
pyecharts文档:渲染图表 - pyecharts - A Python Echarts Plotting Library built with love. pyecharts-gallery文档:中文简介 - Document (pyecharts.org) 一、快速入门案例 from pyecharts.charts import Barbar Bar() bar.add_xaxis(["衬衫…...
vue3打包疯狂报错
打包的时候报错很多Cannot find name ‘xxx‘ 。 但是npm run dev 是运行正常的。 解决方法:package.json中的vue-tsc --noEmit 删掉就可以了。 例如: 这是原来的 {"scripts": {"dev": "vite","build": &quo…...
STM32 软件触发ADC采集
0.91寸OLED屏幕大小的音频频谱,炫酷! STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系(深度理解) STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能,几乎任何一款单片机都会包含这个功能&a…...
Android SystemUI组件(08)睡眠灭屏 锁屏处理流程
该系列文章总纲链接:专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明: 说明:本章节持续迭代之前章节的思维导图,主要关注左侧上方锁屏分析部分 睡眠灭屏 即可。 Power按键的处理逻辑最终是由PhoneWindowManager来完…...
C# 表达式与运算符
本课要点: 1、表达式的基本概念 2、常用的几种运算符 3、运算符的优先级 4、常见问题 一 表达式 表达式是由运算符和操作数组成的。、-、*和/等都是运算符,操作数包括文本、常量、变量和表达式等。 二 算术运算符 2.1 算术运算符的使用 三 常见错误 …...
SpringBoot--最大连接数和最大并发数
原文网址:SpringBoot--最大连接数和最大并发数-CSDN博客 简介 本文介绍SpringBoot的最大连接数和最大并发数。 配置 SpringBoot默认使用tomcat处理请求。tomcat可以指定连接数、线程数等配置。 server:tomcat:# 请求处理线程都在使用中时,新连接请求…...
CF687D Dividing Kingdom II 题解
Description 给定一个 n n n 个点、 m m m 条边的图,有 q q q 次询问,每次询问一个 [ l , r ] [l,r] [l,r] 的区间,求将 n n n 个点分为两个部分后,编号在 [ l , r ] [l,r] [l,r] 内的边中,两端点属于同一部分的…...
高空抛物AI检测算法:精准防控,技术革新守护城市安全
近年来,随着城市化进程的加速,高楼大厦如雨后春笋般涌现,但随之而来的高空抛物问题却成为城市管理的一大难题。高空抛物不仅严重威胁行人的安全,还可能引发法律纠纷和社会问题。为了有效预防和减少高空抛物事件的发生,…...
html+css+js实现Collapse 折叠面板
实现效果: HTML部分 <div class"collapse"><ul><li><div class"header"><h4>一致性 Consistency</h4><span class"iconfont icon-jiantou"></span></div><div class"…...
RM服务器研究(一)
客户端默认端口是10100: MultiPort.dll BOOL sub_10001070() { UINT v0; // esi BOOL result; // eax CHAR KeyName; // [espCh] [ebp-10Ch] DWORD flOldProtect; // [esp10h] [ebp-108h] CHAR Buffer; // [esp14h] [ebp-104h] char v5; // [esp15h] [e…...
云岚到家xxl job 配置
调度中心: 负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码; 主要职责为执行器管理、任务管理、监控运维、日志管理等 任务执行器: 负责接收调度请求并执行任务逻辑; 主要职责是执行任…...
国内动态短效sk5
HTTP爬虫代理,软件测试, 动态转发IP方案,全高匿名,私密IP,固定网关将您每次请求的HTTP重定向到不同的后端IP,支持API;指路小熊IP https://www.xiaoxiongip.com?fromqkJWgD可测...
【路径规划】路径平滑算法,A星算法拐点的圆弧化处理
摘要 A算法广泛应用于路径规划中,但其生成的路径通常在拐点处呈现不平滑的折线。为了提升路径的平滑性,本文提出了一种基于圆弧的平滑处理方法,用于对A算法产生的路径拐点进行优化。通过在MATLAB中进行仿真验证,该方法能够有效减…...
【寻找one piece的算法之路】——双指针算法!他与她是否会相遇呢?
💐个人主页:初晴~ 📚相关专栏:寻找one piece的刷题之路 什么是双指针算法 双指针算法是一种常用的编程技巧,尤其在处理数组和字符串问题时非常有效。这种方法的核心思想是使用两个指针来遍历数据结构,这两…...
UFS 3.1架构简介
整个UFS协议栈可以分为三层:应用层(UFS Application Layer(UAP)),传输层(UFS Transport Layer(UTP)),链路层(UIC InterConnect Layer(UIC))。应用层发出SCSI命令(UFS没有自己的命令使用的是简化的SCSI命令),在传输层将SCSI分装为UPIU,再经过链路层将命令发送给Devices。下…...
注册安全分析报告:科研诚信查询平台无验证方式导致安全隐患
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…...
04.useTitle
在 React 应用中,动态更新页面标题是提升用户体验的一个重要方面。它可以让用户更清楚地知道当前页面的内容或状态,特别是在单页应用(SPA)中。useTitle 钩子提供了一种简单而有效的方式来管理文档标题。以下是如何实现和使用这个自定义钩子: const useTitle = title =>…...
ROS2中的srv、action、发布订阅三种方式
ROS2中的srv、action、发布订阅三种方式 以下是ROS2中srv、action、发布订阅三种方式的差异和使用场景的表格形式呈现: 特性/方式srv(服务)action(动作)发布订阅(Publish-Subscribe)通信模式请…...
HarmonyOS/OpenHarmony 自定义弹窗页面级层级控制解决方案
关键词:CuntomDialog自定义弹窗、SubWindow子窗口、页面级、弹窗层级控制、鸿蒙、弹窗展示层级异常 问题存在API版本:API10 - API12(该问题已反馈,期望后续官方能增加页面级控制能力) 在正常的鸿蒙app开发过程中&…...
C/C++进阶(一)--内存管理
更多精彩内容..... 🎉❤️播主の主页✨😘 Stark、-CSDN博客 本文所在专栏: 学习专栏C语言_Stark、的博客-CSDN博客 其它专栏: 数据结构与算法_Stark、的博客-CSDN博客 项目实战C系列_Stark、的博客-CSDN博客 座右铭&a…...
docker-compose 快速部署clickhouse集群
在本教程中,我们将学习如何使用 Docker Compose 部署一个带有三节点的 ClickHouse 集群,并使用 ZooKeeper 作为分布式协调服务。 前提条件 注意事项: 镜像版本号注意保持一致 [zookeeper:3.7, clickhouse/clickhouse-server:22.5.4]config…...
闯关训练三:Git 基础知识
任务1: 破冰活动:自我介绍 点击Fork目标项目,创建一个新的Fork 获取仓库链接 在连接好开发机的vscode终端中逐行执行以下代码: git clone https://github.com/KelvinIII/Tutorial.git # 修改为自己frok的仓库 cd Tutorial/ git branch -a g…...
Java--IO基本流
IO流 概述 生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrls ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘…...
结合大语言模型的机械臂抓取操作简单介绍
一、大语言模型与机械臂抓取的基本操作 1. 大语言模型简介 大语言模型是基于深度学习技术构建的自然语言处理模型,能够生成、理解和处理文本信息。这些模型通过训练大量的文本数据,学习语法、上下文和常识,能够执行多种任务,如文…...
Vivado - BD(差分时钟、简单分频、RESET、KEY)
目录 1. 简介 1.1 要点 1.2 buffer 介绍 2. vivado 工程 2.1 Block Design 2.2 IBUFDS 2.3 BUFGCE_DIV 2.4 Processor System Reset 2.5 key_mod 2.6 led_drv 3. 编译与调试 3.1 XDC 3.2 Debug 4. 总结 1. 简介 1.1 要点 了解 Utility Buffer v2.2 中的 Buffer…...
7--苍穹外卖-SpringBoot项目中套餐管理 详解(一)
前言 目录 新增套餐 需求分析和设计 代码开发 根据分类id查询菜品 Controller层 Service层 ServiceImpl层 Mapper层 DishMapper.xml 新增套餐 实体类 mapper层 Service层 ServiceImpl层 Mapper层 SetmealMapper.xml setmealDishMapper.xml 套餐分页查询 需求分…...
网站没有做404页面/软文发布平台有哪些
有小学弟问:如何在3个月内学会自动化测试? 老实说如果你现在上班,之前主要在做功能测试,或者编程基础比较弱的话,三个月够呛。 如果你是脱产学习,每天能保持6~8小时学习时间的话,可…...
网站建设论坛/seo百度站长工具
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群:1040082875 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 今天有小伙伴在我这篇文章【虚拟仿…...
百度快照 查看指定网站/百度统计代码
java-如何在适配器中启动Activity?我的自定义适配器具有ListActivity。 并且在每个视图内部,它可能都有一些按钮,在这些按钮中我需要实现OnClickListener。 我需要在适配器中实现OnClickListener。 但是,我不知道如何调用诸如star…...
前端静态网站模板/济南网站优化公司哪家好
1.SPring Security 教程入门 ——几个小demo,从认证处理(xml配置和数据库存储方式),授权管理再到常见问题(入门推荐) 2.spring security 概述& 配置文件详解 ——了解配置元素作用 3.春天的故事&…...
b2c电子商务网站的收益模式主要有/西安竞价推广托管
对于任何一个模型,如果需要使用软删除功能,需要在模型中使用 Illuminate\Database\Eloquent\SoftDeletes这个 trait 。软删除功能需要实现的功能有以下几点:1.模型执行删除操作,只标记删除,不执行真正的数据删除2.查询的时候自动…...
西安招聘网最新招聘/aso关键词优化计划
1. \u0039\u0031\u0031\u67E5\u8BE2\u00B7\u82F1\u8BED\u5355\u8BCD1. 对计算机硬件我略有特长,并通过了国家认证的计算机三级A类等级考试。I slightly on the computer hardware, and through the country special authentication of A computer grade examination…...