Go最新最新golang语言面试题总结(一)_golang面试题(3),2024Golang大厂面试真题

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

//打印奇数
func PrintOddNumber(wg *sync.WaitGroup, ch chan int, num int) {
defer wg.Done()
for i := 0; i <= num; i++ {
ch <- i
if i%2 != 0 {
fmt.Println(“奇数:”, i)
}
}
}

//打印偶数
func PrintEvenNumber(wg *sync.WaitGroup, ch chan int, num int) {
defer wg.Done()
for i := 1; i <= num; i++ {
<-ch
if i%2 == 0 {
fmt.Println(“偶数:”, i)
}
}
}
func main() {
wg := sync.WaitGroup{}
ch := make(chan int)
wg.Add(1)
go PrintOddNumber(&wg, ch, 10)
go PrintEvenNumber(&wg, ch, 10)
wg.Wait()
}
结果:
奇数: 1
偶数: 2
奇数: 3
偶数: 4
奇数: 5
偶数: 6
奇数: 7
偶数: 8
奇数: 9
偶数: 10



**6、go中slice和数组的区别**



> 
>  1. 数组定长,定义的时候就需要确定。切片长度不定,append时会自动扩容
> 
> 
> 2. 相同大小数组可以赋值,会拷贝全部内容。slice赋值和指针一样。数组和slice之间不能相互赋值。当然slice有自己的copy函数
> 
> 
> 3. 数组也可以进行切片,返回值是一个slice,改变slice时会同步修改数组内容,相当于取得了这个数组的指针
> 
> 
> 


**7、go中channel在你们项目中使用的场景举例**



> 
> 1、消息传递、消息过滤
> 
> 
> 2、信号广播
> 
> 
> 3、事件订阅与广播
> 
> 
> 4、请求、响应转发
> 
> 
> 5、任务分发
> 
> 
> 


**8、go中使用chan要注意什么**



> 
> 1、当需要不断从channel读取数据时,最好使用`for-range`读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值。 
> 
> 
> 
> ```
> 例如:for x := range ch {
> 		//
> 	}
> ```
> 
> 2、读已关闭的channel会造成零值 ,如果不确定channel,需要使用`ok`进行检测。
> 
> 
> 
> ```
> if v, ok := <-chan;ok{
> 		///
> 	}
> ```
> 
> 3、s`elect`可以同时监控多个通道的情况,只处理未阻塞的case。当通道为nil时,对应的case永远为阻塞,无论读写。特殊关注:普通情况下,对nil的通道写操作是要panic的
> 
> 
> 
> ```
> select {
> 		case <-ch:
> 		//
> 		default:
> 		//
> 	}
> ```
> 
> 4、使代码更易读、更易维护,防止只读协程对通道进行写数据,但通道已关闭,造成panic。
> 
> 
> 5、有缓冲通道是异步的,无缓冲通道是同步的,有缓冲通道可供多个协程同时处理,在一定程度可提高并发性。
> 
> 
> 6、使用`select`和`time.After`,看操作和定时器哪个先返回,处理先完成的,就达到了超时控制的效果
> 
> 
> 
> ```
> func WorkWithOutTime(t time.Duration) (int, error) {
> 	select {
> 	case ret := <-Work():
> 		return ret, nil
> 	case <-time.After(t):
> 		return 0, errors.New("timeout")
> 	}
> }
> func Work() <-chan int {
> 	ch := make(chan int)
> 	go func() {
> 		//
> 	}()
> 	return ch
> }
> ```
> 
> 7、是为操作加上超时的扩展,这里的操作是channel的读或写
> 
> 
> 
> ```
> func OnlyRead(ch chan int) {
> 	select {
> 	case <-ch:
> 	//
> 	default:
> 		//
> 	}
> }
> ```
> 
> 8、channel本质上传递的是数据的拷贝,拷贝的数据越小传输效率越高,传递结构体指针,比传递结构体更高效
> 
> 
> 


**9、说一下go中的CSP**



> 
> CSP(communicating sequential processes)并发模型:Go语言特有且推荐使用的。
> 
> 
> **Go的CSP并发模型**,是通过goroutine和channel来实现的。
> 
> 
> 不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。
> 
> 
> 普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。
> 
> 
> Go的CSP并发模型,是通过goroutine和channel来实现的。
> 
> 
> goroutine 是Go语言中并发的执行单位。可以理解为用户空间的线程。  
>  channel是Go语言中不同goroutine之间的通信机制,即各个goroutine之间通信的”管道“,有点类似于Linux中的管道。 
> 
> 
> ![](https://img-blog.csdnimg.cn/7540a973d6534d9e90595f4479326ea1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRfemlsaWFuZw==,size_20,color_FFFFFF,t_70,g_se,x_16)
> 
> 
>  ![](https://img-blog.csdnimg.cn/b3df6ac26a8f4a61a2ce3ef10edc9ebc.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRfemlsaWFuZw==,size_11,color_FFFFFF,t_70,g_se,x_16)
> 
> 
> M:是内核线程
> 
> 
> P : 是调度协调,用于协调M和G的执行,内核线程只有拿到了 P才能对goroutine继续调度执行,一般都是通过限定P的个数来控制golang的并发度
> 
> 
> G : 是待执行的goroutine,包含这个goroutine的栈空间
> 
> 
> Gn : 灰色背景的Gn 是已经挂起的goroutine,它们被添加到了执行队列中,然后需要等待网络IO的goroutine,当P通过 epoll查询到特定的fd的时候,会重新调度起对应的,正在挂起的goroutine。
> 
> 
> Golang为了调度的公平性,在调度器加入了steal working 算法 ,在一个P自己的执行队列,处理完之后,它会先到全局的执行队列中偷G进行处理,如果没有的话,再会到其他P的执行队列中抢G来进行处理![](https://img-blog.csdnimg.cn/e509d3c81eda4acdb7716cea60390502.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRfemlsaWFuZw==,size_20,color_FFFFFF,t_70,g_se,x_16)  
>  csp很好的例子:生产者和消费者。
> 
> 
> 
> 


**10、说一下go中channel是不是线程安全的,什么情况下会变成线程不安全**



> 
>  Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication),Channel也可以理解是一个先进先出的队列,通过管道进行通信。
> 
> 
> Golang的Channel,发送一个数据到Channel 和 从Channel接收一个数据 都是 原子性的。而且Go的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。也就是说,设计Channel的主要目的就是在多任务间传递数据的,这当然是安全的
> 
> 
> 


**11、说一下map是有序的吗?如何实现有序**



> 
> 1、由于golang map内部存储机制是以key为hash的结构来实现,所以顺序是混乱的
> 
> 
> 2、如果希望是有顺序的,可以把 key 转移至 slice,将slice 进行排序,然后输出。
> 
> 
> 
> ```
> var keys []string
> 
> for key := range maps {
> 
>     keys = append(keys, key)
> 
> }
> 
> sort.Strings(keys) //内值排序
> 
> 
> for _, key := range keys {
> 
>     fmt.Printf("%s:%v\n", key, maps[key])
> 
> }
> ```
> 
> 


**12、如下程序添加一段代码让程序不报panic异常。**



func test() {
//添加一段程序捕获panic异常

}
func main() {
defer test()
panic(1)
}

答案:
func test() {
//添加一段程序捕获panic异常
recover()
}
func main() {
defer test()
panic(1)
}


**13、使用go语言将数组中12300045变成12345000**



func MobileNnm(arr []int) []int {
arr2 := make([]int, 0)
oneCount := 0
for _, v := range arr {
if v == 0 {
oneCount++
} else {
arr2 = append(arr2, v)
}
}
for i := 0; i < oneCount; i++ {
arr2 = append(arr2, 0)
}
return arr2
}
func main() {
//使用go语言将12300045变成12345000
val1 := []int{1, 2, 3, 0, 0, 0, 4, 5}
val2 := MobileNnm(val1)
fmt.Println(val2)
}
结果:[1 2 3 4 5 0 0 0]


**14、算法题**



题目:1、(( 结果是false
2、(())() 是true
3、((()))())是false 用go实现

思路:压栈找到匹配的出栈,找不到还放入栈中,直到栈为空,代表都匹配上了。
代码如下:(仅供参考)
func main() {
//题目:1、(( 结果是false
//2、(())() true
//3、((()))())false 用go实现
s0 := []string{“(”, “(”}
fmt.Println(IsMatch(s0))//false
s1 := []string{“(”, “(”, “)”, “)”, “(”, “)”}
fmt.Println(IsMatch(s1)) //true
s2 := []string{“(”, “(”, “)”, “)”, “(”, “)”, “)”}
fmt.Println(IsMatch(s2))//false
}
func IsMatch(str []string) bool {
//压栈的思想
if len(str) == 0 {
return false
}
stack := NewStack()
for _, v := range str {
if stack.IsEmpty() {
stack.Push(v)
} else {
sp := stack.Pop()
if sp == “(” && string(v) == “)” {
continue
} else {
stack.Push(sp)
stack.Push(v)
}
}
}
if stack.IsEmpty() {
return true
}
return false
}

type Element interface{} //可存入任何类型
type Stack struct {
list []Element
}

//初始化栈
func NewStack() *Stack {
return &Stack{
list: make([]Element, 0),
}
}
//判断栈是否空
func (s *Stack) IsEmpty() bool {
if len(s.list) == 0 {
return true
} else {
return false
}
}

//入栈
func (s *Stack) Push(x interface{}) {
s.list = append(s.list, x)
}

//出栈
func (s *Stack) Pop() Element {
if len(s.list) <= 0 {
fmt.Println(“Stack is Empty”)
return nil
} else {
ret := s.list[len(s.list)-1]
s.list = s.list[:len(s.list)-1]
return ret
}
}


**15、算法题**



题目:上楼梯有一阶和两阶
例如3层楼梯有如下种
1 2 1
1 1 2
1

n阶有多少种:

代码如下:(仅供参考)
func main() {
n := Upstairs(3)
fmt.Println(n)
}

var mapData = make(map[int]int, 0)

func Upstairs(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
if _, ok := mapData[n]; !ok {
mapData[n] = Upstairs(n-1) + Upstairs(n-2)
}
return mapData[n]
}


**16、用go实现单链表的反转**



题目:单链表12345678910反转成10987654321
代码如下:(仅供参考)
func main() {
//实现单链表的反转例如12345678910变成10987654321
var head = new(ListNode)
CreateNode(head, 10)
PrintNode(“前:”, head)
yyy := reverseList(head)
PrintNode(“后:”, yyy)
}

type ListNode struct {
data interface{}
next *ListNode
}

func reverseList(head *ListNode) *ListNode {
cur := head
var pre *ListNode
for cur != nil {
cur.next, pre, cur = pre, cur, cur.next
}
return pre
}
func CreateNode(node *ListNode, max int) {
cur := node
for i := 1; i <= max; i++ {
cur.next = &ListNode{}
cur.data = i
cur = cur.next
}
}

//打印链表的方法
func PrintNode(str string, node *ListNode) {
fmt.Print(str)
for cur := node; cur != nil; cur = cur.next {
if cur.data != nil {
fmt.Print(cur.data, " ")
}
}
fmt.Println()
}
结果:
前:1 2 3 4 5 6 7 8 9 10
后:10 9 8 7 6 5 4 3 2 1


**17、算法题(在线求答案)**



题目:
输入文件构成规则如下:

  1. 每行代表一条记录,字段之间以逗号(,)分隔
  2. 若字段内容包含逗号(,),则以双引号包围该字段
  3. 若字段内容包含双引号("),则以双引号包围该字段,字段内的双引号由一个变两个
    请参照上面三条规则,编写一个解析程序,将解析后的记录内容按行输出,字段之间以TAB(\t)分隔,2小时内完成

示例:
John,33,“足球,摄影”,New York
John,33,“足球,”“摄影”,New York
输出:
John 33 足球,摄影 New York
John 33 足球,"摄影 New York

输入:
2,John,45,“足球,摄影”,New York
3,Carter Job,33,“”“健身”“,远足”,“河北,石家庄”
4,Steve,33,“大屏幕164"”“,“DC”“Home””"
5,“Jul,y”,33,Football,Canada

求输出!



package main

import (
“bufio”
“fmt”
“os”
“strings”
)

func parseRecord(line string) string {
var fields []string
var inQuote bool
var field string

for i := 0; i < len(line); i++ {
	c := line[i]

	if c == ',' && !inQuote {
		fields = append(fields, field)
		field = ""
	} else if c == '"' {
		inQuote = !inQuote
		if i > 0 && line[i-1] == '"' {
			field += "\""
		}
	} else {
		field += string(c)
	}
}

fields = append(fields, field)

return strings.Join(fields, "\t")

}

func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(parseRecord(line))
}

if err := scanner.Err(); err != nil {
	fmt.Fprintln(os.Stderr, "reading standard input:", err)
}

}



**18、slice和map区别代码输出题**



题目一:
func main() {
s1 := []int{1, 2, 3, 4, 5}
changeslice(s1)
fmt.Println(s1)//?s1的结果会变化吗?
fmt.Println(len(s1), cap(s1))//?s1长度和容量会变化吗?
m1 := map[int]int{1: 1, 2: 2, 3: 3, 4: 4}
changeMap(m1)
fmt.Println(m1) //?m1中key等于1的值会变化吗?
fmt.Println(len(m1))//?m1长度和容量会变化吗?
}
func changeslice(s []int) {
s[0] = 2
s = append(s, 7, 8, 9, 10)
}
func changeMap(m map[int]int) {
m[1] = 2
m[5] = 5
m[6] = 6
}
答案:s1的值会变化,但是长度和容量不会变化。m1的值会变化,长度会变化。
切记:
map 没有容量限制,所以内置函数 cap 也不接受 map 类型

题目二:
func main() {
m := make(map[int]int)
mdMap(m)
fmt.Println(m) //输出结果
}

func mdMap(m map[int]int) {
m[1] = 100
m[2] = 200
}

func main() {
var m1 map[int]int
mdMap(m1)
fmt.Println(m1)//输出的结果
}

func mdMap(m map[int]int) {
m = make(map[int]int)
m[1] = 100
m[2] = 200
}
答案:m结果map[2:200 1:100] m1的结果map[]


**19、mysql有那几种存储引擎**



> 
> MyISAM:
> 
> 
> 创建一个myisam存储引擎的表的时候回出现三个文件
> 
> 
> 1.tb\_demo.frm,存储表定义;  2.tb\_demo.MYD,存储数据;  3.tb\_demo.MYI,存储索引。
> 
> 
> MyISAM表无法处理事务,这就意味着有事务处理需求的表,不能使用MyISAM存储引擎。
> 
> 
> MyISAM存储引擎特别适合在以下几种情况下使用:
> 
> 
> 1.选择密集型的表。MyISAM存储引擎在筛选大量数据时非常迅速,这是它最突出的优点。
> 
> 
> 2.插入密集型的表。MyISAM的并发插入特性允许同时选择和插入数据。例如:MyISAM存储引擎很适合管理邮件或Web服务器日志数据。
> 
> 
> 
> InnoDB:
> 
> 
> InnoDB是一个健壮的事务型存储引擎MySQL 5.6.版本以后InnoDB就是作为默认的存储引擎。
> 
> 
> InnoDB还引入了行级锁定和外键约束,在以下场合下,使用InnoDB是最理想的选择:
> 
> 
> 1. 更新密集的表。InnoDB存储引擎特别适合处理多重并发的更新请求。
> 
> 
> 2.事务。InnoDB存储引擎是支持事务的标准MySQL存储引擎。
> 
> 
> 3.自动灾难恢复。与其它存储引擎不同,InnoDB表能够自动从灾难中恢复。
> 
> 
> 4.外键约束。MySQL支持外键的存储引擎只有InnoDB。
> 
> 
> 5.支持自动增加列AUTO\_INCREMENT属性。
> 
> 
> 
> MEMORY:
> 
> 
> 使用MySQL Memory存储引擎的出发点是速度。为得到最快的响应时间,采用的逻辑存储介质是系统内存。虽然在内存中存储表数据确实会提供很高的性能,但当mysqld守护进程崩溃时,所有的Memory数据都会丢失。获得速度的同时也带来了一些缺陷。它要求存储在Memory数据表里的数据使用的是长度不变的格式,这意味着不能使用BLOB和TEXT这样的长度可变的数据类型,VARCHAR是一种长度可变的类型,但因为它在MySQL内部当做长度固定不变的CHAR类型,所以可以使用。
> 
> 
> 一般在以下几种情况下使用Memory存储引擎:
> 
> 
> 1.目标数据较小,而且被非常频繁地访问。在内存中存放数据,所以会造成内存的使用,可以通过参数max\_heap\_table\_size控制Memory表的大小,设置此参数,就可以限制Memory表的最大大小。
> 
> 
> 2.如果数据是临时的,而且要求必须立即可用,那么就可以存放在内存表中。
> 
> 
> 3.存储在Memory表中的数据如果突然丢失,不会对应用服务产生实质的负面影响。Memory同时支持散列索引和B树索引。B树索引的优于散列索引的是,可以使用部分查询和通配查询,也可以使用<、>和>=等操作符方便数据挖掘。散列索引进行“相等比较”非常快,但是对“范围比较”的速度就慢多了,因此散列索引值适合使用在=和<>的操作符中,不适合在<或>操作符中,也同样不适合用在order by子句中
> 
> 
> 
> MERGE:
> 
> 
> MERGE存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,尽管其使用不如其它引擎突出,但是在某些情况下非常有用。说白了,Merge表就是几个相同MyISAM表的聚合器;Merge表中并没有数据,对Merge类型的表可以进行查询、更新、删除操作,这些操作实际上是对内部的MyISAM表进行操作。Merge存储引擎的使用场景。对于服务器日志这种信息,一般常用的存储策略是将数据分成很多表,每个名称与特定的时间端相关。例如:可以用12个相同的表来存储服务器日志数据,每个表用对应各个月份的名字来命名。当有必要基于所有12个日志表的数据来生成报表,这意味着需要编写并更新多表查询,以反映这些表中的信息。与其编写这些可能出现错误的查询,不如将这些表合并起来使用一条查询,之后再删除Merge表,而不影响原来的数据,删除Merge表只是删除Merge表的定义,对内部的表没有任何影响。
> 
> 
> 
> ARCHIVE:
> 
> 
> rchive是归档的意思,在归档之后很多的高级功能就不再支持了,仅仅支持最基本的插入和查询两种功能。在MySQL 5.5版以前,Archive是不支持索引,但是在MySQL 5.5以后的版本中就开始支持索引了。Archive拥有很好的压缩机制,它使用zlib压缩库,在记录被请求时会实时压缩,所以它经常被用来当做仓库使用。
> 
> 
> 


**20、mysql中事务隔离级别有哪几种**



> 
> * 读未提交(READ UNCOMITTED)
> * 读提交(READ COMMITTED)
> * 可重复读(REPEATABLE READ)
> * 串行化(SERIALIZABLE)
> * | 隔离级别 | 脏读 | 不可重复读 | 幻读 |
> | --- | --- | --- | --- |
> | READ UNCOMITTED | √ | √ | √ |
> | READ COMMITTED | × | √ | √ |
> | REPEATABLE READ | × | × | √ |
> | SERIALIZABLE | × | × | × |
> 
>  mysql数据库事务的隔离级别有4个,而默认的事务处理级别就是【REPEATABLE-READ】,也就是可重复读
> 
> 
> 


**21、 B树、B+tree、Hash有什么区别**



> 
> B树是一种多路自平衡搜索树,它类似普通的[二叉树](https://bbs.csdn.net/topics/618658159),但是B书允许每个节点有更多的子节点。B树示意图如下:
> 
> 
> ![](https://img-blog.csdnimg.cn/f81a30bc075544a889f59b74aa89bdcc.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRfemlsaWFuZw==,size_20,color_FFFFFF,t_70,g_se,x_16)
> 
> 
> B树的特点:
> 
> 
> 1. 所有键值分布在整个树中
> 2. 任何关键字出现且只出现在一个节点中
> 3. 搜索有可能在非叶子节点结束
> 4. 在关键字全集内做一次查找,性能逼近二分查找算法
> 5. 树深度会很深,因为树顶放到元素比较少导致,检索元素比较慢。
> 
> 
>  缺点:
> 
> 
>        业务数据的大小可能远远超过了索引数据的大小,每次为了查找对比计算,需要把数据加载到内存以及 CPU 高速缓存中时,都要把索引数据和无关的业务数据全部查出来。本来一次就可以把所有索引数据加载进来,现在却要多次才能加载完。如果所对比的节点不是所查的数据,那么这些加载进内存的业务数据就毫无用处,全部抛弃。
> 
> 
>  B+Tree: 
> 
> 
>  ![](https://img-blog.csdnimg.cn/d9152a755e68475aa453081f57c89a20.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRfemlsaWFuZw==,size_20,color_FFFFFF,t_70,g_se,x_16) 
> 
> 
> 从图中也可以看到,B+树与B树的不同在于:
> 
> 
> 1. 所有关键字存储在叶子节点,非叶子节点不存储真正的data
> 2. 为所有叶子节点增加了一个链指针
> 3. 树顶可以放很多元素,树的深度比较矮,检索元素比较快。
> 
> 
>  缺点:
> 
> 
>        仍然有一个致命的缺陷,那就是它的**索引数据与业务绑定在一块**,而业务数据的大小很有可能远远超过了索引数据,这会大大减小一次 I/O 有用数据的获取,间接的增加 I/O 次数去获取有用的索引数据 
> 
> 
> Hash:
> 
> 
>  ![](https://img-blog.csdnimg.cn/4d5a3a952e9f486a940401c12adb4c41.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRfemlsaWFuZw==,size_20,color_FFFFFF,t_70,g_se,x_16) 
> 
> 
> 特点:数组+链表
> 
> 
>           1、查询单条数据很快,先解析出hash值,根据hash找到链表,然后找到索引最后根据索引找到数据。
> 
> 
>   缺点:
> 
> 
> 1. 容易hash碰撞
> 2. Hash索引仅仅能满足“=”,“IN”,“<=>”查询,不能使用范围查询
> 3. 联合索引中,Hash索引不能利用部分索引键查询。
> 4. Hash索引无法避免数据的排序操作
> 5. Hash索引任何时候都不能避免表扫描
> 6. Hash索引遇到大量Hash值相等的情况后性能会下降
> 
> 
> 


**22、** **Mysql 中 MyISAM 和 InnoDB 的区别有哪些?**



> 
> 1. InnoDB支持事务,MyISAM不支持
> 2. 对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
> 3. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
> 4. InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。
> 5. 但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此主键不应该过大,因为主键太大,其他索引也都会很大。
> 6. 而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
> 7. InnoDB不保存表的具体行数,执行select count(\*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
> 8. Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高
> 
> 
> 



**23、 go向关闭的channel发送和读取数据是否报错**



package main

import “fmt”

//向已关闭的通道读取数不会报错
func main1() {
var ch = make(chan int)
go func() {
close(ch)
}()
fmt.Println(<-ch)
}

//向已关闭的通道发送数据报panic: send on closed channel
func main2() {
var ch = make(chan int)
go func() {
close(ch)
}()
ch <- 1
}

//关闭通道向有缓存区接收数据会报错
func main3() {
var ch = make(chan int, 10)
go func() {
close(ch)
}()
fmt.Println(<-ch)
}

//关闭通道向有缓冲区发送数据会报错panic:send on closed channel
func main4() {
var ch = make(chan int, 10)
go func() {
close(ch)
}()
ch <- 1
}

//关闭通道向有缓冲区循环发送数据会报错 panic: send on closed channel
func main() {
var ch = make(chan int, 10)
go func() {
close(ch)
}()
for {
ch <- 1
}
}


**24、Golang并发模型有几种**



> 
> 控制并发有三种种经典的方式,一种是通过channel通知实现并发控制 一种是WaitGroup,另外一种就是Context。
> 
> 
> **1、无缓冲通道**
> 
> 
> 无缓冲的通道指的是通道的大小为0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送 `goroutine` 和接收 `goroutine` 同时准备好,才可以完成发送和接收操作。
> 
> 
> 从上面无缓冲的通道定义来看,发送 `goroutine` 和接收 `gouroutine` 必须是同步的,同时准备后,如果没有同时准备好的话,先执行的操作就会阻塞等待,直到另一个相对应的操作准备好为止。这种无缓冲的通道我们也称之为同步通道。
> 
> 
> 正式通过无缓冲通道来实现多 `goroutine` 并发控制
> 
> 
> 
> ```
> func main() {
>     ch := make(chan instruct{})
>     go func() {
>         ch <- struct{}{}
>     }()
>     fmt.Println(<-ch)
> }
> ```
> 
> 当主 `goroutine` 运行到 `<-ch` 接受 `channel` 的值的时候,如果该 `channel` 中没有数据,就会一直阻塞等待,直到有值。 这样就可以简单实现并发控制
> 
> 
> #### 2. 通过sync包中的WaitGroup实现并发控制
> 
> 
> 在 `sync` 包中,提供了 `WaitGroup` ,它会等待它收集的所有 `goroutine` 任务全部完成,在主 `goroutine` 中 `Add(delta int)` 索要等待`goroutine` 的数量。在每一个 `goroutine` 完成后 `Done()` 表示这一个`goroutine` 已经完成,当所有的 `goroutine` 都完成后,在主 `goroutine` 中 `WaitGroup` 返回返回。
> 
> 
> 
> ```
>   func main() {
>    var wg sync.WaitGroup
>  
>   // 开N个后台打印线程
>   for i := 0; i < 10; i++ {
>   wg.Add(1)
>  
>    go func() {
>    fmt.Println("你好, 世界")
>    wg.Done()
>    }()
>   }
>  
>   // 等待N个后台线程完成
>    wg.Wait()
>   }
> ```
> 
> #### 3. 在Go 1.7 以后引进的强大的Context上下文,实现并发控制
> 
> 
> 3.1 简介
> 
> 
> 在一些简单场景下使用 `channel` 和 `WaitGroup` 已经足够了,但是当面临一些复杂多变的网络并发场景下 `channel` 和 `WaitGroup` 显得有些力不从心了。比如一个网络请求 `Request`,每个 `Request` 都需要开启一个 `goroutine` 做一些事情,这些 `goroutine` 又可能会开启其他的 `goroutine`,比如数据库和RPC服务。所以我们需要一种可以跟踪 `goroutine` 的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的 `Context`,称之为上下文非常贴切,它就是`goroutine` 的上下文。它是包括一个程序的运行环境、现场和快照等。每个程序要运行时,都需要知道当前程序的运行状态,通常Go 将这些封装在一个 `Context` 里,再将它传给要执行的 `goroutine` 。`context` 包主要是用来处理多个 `goroutine` 之间共享数据,及多个 `goroutine` 的管理。


![img](https://img-blog.csdnimg.cn/img_convert/0d94bca982e72dc0e9199fe19676ca6e.png)
![img](https://img-blog.csdnimg.cn/img_convert/91fd60902790d9451e435c73c68ccdee.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

 {
>    fmt.Println("你好, 世界")
>    wg.Done()
>    }()
>   }
>  
>   // 等待N个后台线程完成
>    wg.Wait()
>   }
> ```
> 
> #### 3. 在Go 1.7 以后引进的强大的Context上下文,实现并发控制
> 
> 
> 3.1 简介
> 
> 
> 在一些简单场景下使用 `channel` 和 `WaitGroup` 已经足够了,但是当面临一些复杂多变的网络并发场景下 `channel` 和 `WaitGroup` 显得有些力不从心了。比如一个网络请求 `Request`,每个 `Request` 都需要开启一个 `goroutine` 做一些事情,这些 `goroutine` 又可能会开启其他的 `goroutine`,比如数据库和RPC服务。所以我们需要一种可以跟踪 `goroutine` 的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的 `Context`,称之为上下文非常贴切,它就是`goroutine` 的上下文。它是包括一个程序的运行环境、现场和快照等。每个程序要运行时,都需要知道当前程序的运行状态,通常Go 将这些封装在一个 `Context` 里,再将它传给要执行的 `goroutine` 。`context` 包主要是用来处理多个 `goroutine` 之间共享数据,及多个 `goroutine` 的管理。


[外链图片转存中...(img-HlCo0FZx-1715523361265)]
[外链图片转存中...(img-L1miElpC-1715523361265)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值