func Test59(t *testing.T) { runtime.GOMAXPROCS(1) intChan := make(chan int, 1) stringChan := make(chan string, 1) intChan <- 1 stringChan <- "hello" select { case value := <-intChan: fmt.Println(value) case value := <-stringChan: panic(value) } }
答案
不一定,当两个chan同时有值时,select 会随机选择一个可用通道做收发操作
A. 给一个 nil channel 发送数据,造成永远阻塞
B. 从一个 nil channel 接收数据,造成永远阻塞
C. 给一个已经关闭的 channel 发送数据,引起 panic
D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
答案
A B C D
const i = 100 var j = 123
func main() { fmt.Println(&j, j) fmt.Println(&i, i) }
答案
Go语言中,常量无法寻址, 是不能进行取指针操作的
func Test62(t *testing.T) { x := []string{"a", "b", "c"} for v := range x { fmt.Print(v) } }
答案
012
range 一个返回值时,这个值是下标,两个值时,第一个是下标,第二个是值,当 x 为 map时,第一个是key,第二个是value
A. 无缓冲的channel是默认的缓冲为1的channel;
B. 无缓冲的channel和有缓冲的channel都是同步的;
C. 无缓冲的channel和有缓冲的channel都是非同步的;
D. 无缓冲的channel是同步的,而有缓冲的channel是非同步的;
答案
D
func Foo(x interface{}) { if x == nil { fmt.Println("empty interface") return } fmt.Println("non-empty interface") } func Test64(t *testing.T) { var x *int = nil Foo(x) }
答案
non-empty interface
接口除了有静态类型,还有动态类型和动态值,
当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。
这里的 x 的动态类型是 *int,所以 x 不为 nil
A. select机制用来处理异步IO问题;
B. select机制最大的一条限制就是每个case语句里必须是一个IO操作;
C. golang在语言级别支持select关键字;
D. select关键字的用法与switch语句非常类似,后面要带判断条件;
答案
A B C
包(pkg)是 Go 工作区中包含 Go 源文件或其他包的目录。源文件中的每个函数、变量和类型都存储在链接包中。每个 Go 源文件都属于一个包,该包在文件顶部使用以下命令声明:
package <packagename>
您可以使用以下方法导入和导出包以重用导出的函数或类型:
import <packagename>
Golang 的标准包是 fmt,其中包含格式化和打印功能,如 Println().
A. str := 'abc' + '123'
B. str := "abc" + "123"
C. str := '123' + "abc"
D. fmt.Sprintf("abc%d", 123)
答案
B D 双引号用来表示字符串 string,其实质是一个 byte 类型的数组,单引号表示 rune 类型。
package main import "fmt" func main() { type A struct { a int } type B struct { a int } a := A{1} //b := A{1} b := B{1} if a == b { fmt.Println("a == b") }else{ fmt.Println("a != b") } } // output // command-line-arguments [command-line-arguments.test] // ./.go:14:7: invalid operation: a == b (mismatched types A and B)
Go 支持显式类型转换以满足其严格的类型要求。
i := 55 //int
j := 67.8 //float64
sum := i + int(j)//j is converted to int
Golang的标准库提供了log的机制,但是该模块的功能较为简单(看似简单,其实他有他的设计思路)。在输出的位置做了线程安全的保护。
从调度上看,goroutine的调度开销远远小于线程调度开销。
OS的线程由OS内核调度,每隔几毫秒,一个硬件时钟中断发到CPU,CPU调用一个调度器内核函数。这个函数暂停当前正在运行的线程,把他的寄存器信息保存到内存中,查看线程列表并决定接下来运行哪一个线程,再从内存中恢复线程的注册表信息,最后继续执行选中的线程。这种线程切换需要一个完整的上下文切换:即保存一个线程的状态到内存,再恢复另外一个线程的状态,最后更新调度器的数据结构。某种意义上,这种操作还是很慢的。
Go运行的时候包涵一个自己的调度器,这个调度器使用一个称为一个M:N调度技术,m个goroutine到n个os线程(可以用GOMAXPROCS来控制n的数量),Go的调度器不是由硬件时钟来定期触发的,而是由特定的go语言结构来触发的,他不需要切换到内核语境,所以调度一个goroutine比调度一个线程的成本低很多。
从栈空间上,goroutine的栈空间更加动态灵活。
每个OS的线程都有一个固定大小的栈内存,通常是2MB,栈内存用于保存在其他函数调用期间哪些正在执行或者临时暂停的函数的局部变量。这个固定的栈大小,如果对于goroutine来说,可能是一种巨大的浪费。作为对比goroutine在生命周期开始只有一个很小的栈,典型情况是2KB, 在go程序中,一次创建十万左右的goroutine也不罕见(2KB*100,000=200MB)。而且goroutine的栈不是固定大小,它可以按需增大和缩小,最大限制可以到1GB。
goroutine没有一个特定的标识。
在大部分支持多线程的操作系统和编程语言中,线程有一个独特的标识,通常是一个整数或者指针,这个特性可以让我们构建一个线程的局部存储,本质是一个全局的map,以线程的标识作为键,这样每个线程可以独立使用这个map存储和获取值,不受其他线程干扰。
goroutine中没有可供程序员访问的标识,原因是一种纯函数的理念,不希望滥用线程局部存储导致一个不健康的超距作用,即函数的行为不仅取决于它的参数,还取决于运行它的线程标识。
比如:
import ( "log" . "github.com/alimy/mir/v4/core" "github.com/gin-gonic/gin" _ "github.com/rocboss/paopao-ce/mirc/admin/v1" _ "github.com/rocboss/paopao-ce/mirc/bot/v1"
)
在 Go 语言中,. 和 _ 都是用于导入包的特殊符号。
. 导入包后可以直接使用其中的符号,无需包名前缀。
. 的作用是将导入的包中的符号(函数、类型、常量等)直接在当前文件中可见,而不需要通过包名来访问。这意味着你可以直接使用导入包中的符号,而无需在代码中添加包名前缀。例如,使用 gin 包时,可以直接使用 gin 中的函数和类型,而不需要写成 gin.XXX 的形式。
_ 导入包但不直接使用其中的符号,通常用于执行包的初始化或注册操作。
_ 的作用是导入包,但不直接使用该包中的任何符号。这通常用于执行包的初始化操作或注册操作。
比如:当你导入一个包但不使用其中的任何符号时,Go 编译器会产生一个编译错误。为了避免这种错误,可以使用 _ 来导入包,告诉编译器你只是希望执行包的初始化或注册操作,并不需要使用该包中的符号。
又比如:你服务启动时依赖某个包的init()方法里的逻辑,通过_ 导入时,会去执行对应包的init()方法。
在 Go 语言中,select 语句用于处理多个通道(channel)的操作。它类似于 switch 语句,但用于处理通道通信。
select 语句会阻塞直到其中的一个 case 分支可以执行,如果有多个 case 分支都可以执行,那么 Go 语言会随机选择一个执行。 以下是 select 语句的一些常见特性和用法:
基本语法:
goselect {
case <-chan1:
// 当从 chan1 成功接收值时执行
case chan2 <- value:
// 当成功向 chan2 发送值时执行
default:
// 当所有的 case 都无法立即执行时执行
}
多个可执行的 case 分支:如果有多个 case 分支同时满足执行条件(即通道操作就绪),那么 Go 语言会随机选择其中一个执行。
超时处理:可以使用 time 包设置超时,避免 select 永远阻塞。
gotimeout := time.After(5 * time.Second)
select {
case <-chan1:
// 从 chan1 接收
case <-timeout:
// 超时
}
非阻塞操作:如果 select 语句中没有任何一个 case 分支就绪,并且没有 default 分支,那么 select 语句会阻塞,直到至少有一个 case 分支可以执行。如果存在 default 分支,那么当所有的通道操作都没有准备好时,会立即执行 default 分支。
select 机制在处理多个并发的通道操作时非常有用,可以方便地实现并发任务之间的协调和通信。
在 Go 语言中,sync.Mutex是用于提供互斥锁的同步原语,它可以确保在同一时刻只有一个 goroutine 能够访问被保护的资源。
一、功能
sync.Mutex的主要功能是对共享资源进行同步访问控制,防止多个 goroutine 同时读写共享资源导致数据不一致或出现竞态条件。
二、用法
创建互斥锁:
go var mu sync.Mutex
加锁:
go mu.Lock()
// 访问共享资源
解锁:
gomu.Unlock()
三、Mutex 的四种状态
sync.Mutex实际上并没有严格意义上的明确四种状态,但可以从不同角度来理解其状态:
四、正常模式和饥饿模式
在正常模式下,等待锁的 goroutine 以先进先出(FIFO)的顺序排队等待锁的释放。新唤醒的 goroutine 和已经在等待队列中的 goroutine 竞争锁的机会是平等的,但是刚被唤醒的 goroutine 有一定的优势,因为它可能已经在 CPU 上运行,而已经在等待队列中的 goroutine 可能需要重新被调度。
正常模式的目的是为了在没有严重竞争的情况下提供较好的性能,因为它允许新唤醒的 goroutine 快速获取锁,减少了上下文切换的开销。
五、自旋
在 Go 语言中,sync.Mutex在某些情况下会使用自旋(spinning)策略。自旋是指当一个 goroutine 尝试获取锁时,如果发现锁已经被其他 goroutine 持有,它不会立即进入阻塞状态,而是在一个循环中不断地尝试获取锁,直到一定次数后才进入阻塞状态。
自旋的目的是为了在锁被短暂持有的情况下,减少上下文切换的开销。如果锁很快被释放,那么自旋的 goroutine 可以快速获取锁,而不需要进行上下文切换进入阻塞状态,然后再被唤醒。但是,如果锁被长时间持有,自旋会浪费 CPU 资源,因此自旋只会进行一定次数的尝试。
总的来说,sync.Mutex通过正常模式和饥饿模式以及自旋策略来平衡性能和公平性,以适应不同的竞争情况。
CHAR
和 VARCHAR
是 MySQL 中两种常见的字符数据类型,它们主要用于存储文本数据。尽管它们的功能相似,但在存储和使用上有一些关键的区别。以下是它们的主要区别:
CHAR:
CHAR
是定长字符类型,表示存储固定长度的字符串。如果存储的字符串长度小于定义的长度,MySQL会自动在字符串后面填充空格,直到达到指定的长度。例如,定义 CHAR(10)
,即使存储的是 "abc"(长度为3),它会将 "abc" 填充为 "abc "(共10个字符,末尾有7个空格)。
VARCHAR:
VARCHAR
是变长字符类型,表示存储可变长度的字符串。VARCHAR
只会存储实际字符的长度,并且会在存储时记录字符串的实际长度。因此,VARCHAR
不会像 CHAR
一样填充空格。如果存储的字符串长度是 3 个字符,VARCHAR(10)
只存储 3 个字符。
CHAR:
CHAR
总是占用固定的空间,即使存储的字符串长度小于定义的长度,也会占用完整的空间。这可能导致存储空间浪费。例如,如果定义 CHAR(100)
,即使只存储了一个字符,它也会占用 100 字节(如果字符集是 UTF-8)。
VARCHAR:
VARCHAR
只会占用实际存储的字符空间,再加上一个字节用于记录字符串的长度(如果最大长度小于 255,则占用 1 字节;如果最大长度大于等于 255,则占用 2 字节)。因此,VARCHAR
更加节省空间,尤其在存储长度较短的字符串时。
CHAR:
由于 CHAR
存储的是固定长度的字符串,它在存取时的操作较为简单且速度较快,尤其适用于存储长度固定的字段(如身份证号码、邮政编码等)。因为每次读取时,MySQL 已经知道每个字符串的长度,不需要再计算实际的存储长度。
VARCHAR:
由于 VARCHAR
存储的是变长字符串,MySQL 需要存储和读取额外的长度信息,因此在某些情况下,访问 VARCHAR
的速度可能比 CHAR
慢。不过,实际性能差异通常非常小,只有在大量数据需要频繁查询时,才可能有显著影响。
CHAR:
适用于存储长度固定的字符串,比如国家代码、性别、邮政编码、电话号码等。这些字段通常具有固定的长度,因此使用 CHAR
可以提高性能。
VARCHAR:
适用于存储长度不固定的字符串,如用户名、地址、描述等。这些字段的长度经常变化,因此使用 VARCHAR
可以节省存储空间。
CHAR:
由于是定长字段,查询时不需要额外计算字段的实际长度,因此通常可以提供较快的查询速度,尤其是对于长度固定的字段。
VARCHAR:
对于变长字段,查询时需要额外考虑存储长度信息,因此在某些情况下可能稍微影响性能,尤其是在涉及大量变长字段的查询时。
CHAR:
因为是定长的,CHAR
的索引在性能上通常比较稳定,尤其是在排序和查找时。
VARCHAR:
由于变长,VARCHAR
的索引可能会稍微影响性能,尤其是对于较长的字符串列。然而,如果该列的数据非常多样化,VARCHAR
的空间利用效率会更高。
CHAR:
CHAR
的最大长度为 255 字符。
VARCHAR:
VARCHAR
的最大长度为 65,535 字符,但实际长度受到行的大小限制。在 MySQL 中,一行的最大大小为 65,535 字节,这意味着存储一个 VARCHAR
字段的最大长度将受到字符集(如 UTF-8)每个字符所占字节数的影响。
特性 | CHAR | VARCHAR |
---|---|---|
存储方式 | 固定长度(填充空格) | 变动长度(不填充空格) |
存储空间 | 总是占用固定空间 | 根据实际长度占用空间 |
性能 | 查询速度较快(适用于定长字段) | 查询时略慢(适用于变长字段) |
适用场景 | 长度固定的字符串(如邮政编码、身份证) | 长度不固定的字符串(如用户名、地址) |
最大长度 | 最大255字符 | 最大65,535字符 |
CHAR
或 VARCHAR
CHAR
可以提高性能并节省存储空间。VARCHAR
是更好的选择,可以节省存储空间。本文作者:han
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!