go巨页输出

go的调度 goroutine调度用了什么系统调用

go的调度原理是基于GMP模型,G代表一个goroutine,不限制数量;M=machine,代表一个线程,最大1万,所有G任务还是在M上执行;P=processor代表一个处理器,包含G运行的一切资源。G运行需要获取P,在M上运行。

参考:

https://cloud.tencent.com/developer/article/1422385

https://blog.csdn.net/chushoufengli/article/details/114940228

go struct能不能比较

除了slice、map、function不可比较,其他的基本的类型数字、指针、接口、chan、数组都能比较。

同一个struct的两个实例的比较,如果包含了不可比较的类型的字段则不能比较。否则可以比较。

不同struct的两个实例的比较,需要先进行结构类型的转换 转换成同一个结构类型才可以进行比较 结构体之间进行转换需要他们具备完全相同的成员(字段名、字段类型、字段个数) 。

func testEqual(){
   var a chan int
   var b chan int
   print(a == b)
   aA := [2]int{1, 2,}
   aB := [2]int{1, 2,}
   print(aA == aB)
   _p1 := p1{"1", 2, [2]int{1,2},}
   _p2 := p1{"1", 2, [2]int{1,2},}
   print(_p1 == _p2)
   _p3 := p2{"1", 2, [2]int{1,2},}
   //强制类型转换
   print(p1(_p3) == _p2)
}

type p1 struct {
   f1 string
   f2 int
   f3 [2]int
}

type p2 struct {
   f1 string
   f2 int
   f3 [2]int
}

select可以用于什么

golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作,每个case语句里必须是一个IO操作,确切的说,应该是一个面向channel的IO操作

client如何实现长连接

server设置超时时间。客户端for循环遍历接收。

长连接:客户端发送RESTFUL请求,需要监测某一资源变化情况,服务端提供watch机制,在资源有变化时通知client端。

func httpClientLongCon() {
   req, err := http.NewRequest("GET", "https://www.baidu.com", nil)
   if err != nil {
      log.Fatal(err)
   }
   httpClient := &http.Client{}
   ret, err := httpClient.Do(req)
   if err != nil {
      log.Fatal(err)
   }
   buf := make([]byte, 4096)
   for {
      n, err := ret.Body.Read(buf)
      if n == 0 && err != nil {
         break
      }
      fmt.Println(string(buf[:n]))
   }
}

主协程如何等其余协程完再操作

sync.waitgroup

slice,len,cap,共享,扩容

append函数,因为slice底层数据结构是,由指向数组的指针、len、cap组成,所以,在使用append扩容时,会查看数组后面有没有连续内存快,有就在后面添加,没有就重新生成一个大的素组

切片的扩容

如果切片的容量小于1024个元素,那么扩容的时候slice的cap就在当前容量的基础上翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加当前容量的四分之一。

当向切片中添加数据时,如果没有超过容量,直接添加,如果超过容量,自动扩容(成倍增长)

当超过容量,切片指向的就不再原来的数组,而是内存地址中开辟了一个新的数组

map如何顺序读取

map不能顺序读取,是因为他是无序的,想要有序读取,首先的解决的问题就是,把key变为有序,所以可以把key放入切片,对切片进行排序,遍历切片,通过key取值。

func orderIterMap() {
   m := make(map[string]string)
   m["a"] = "123"
   m["b"] = "456"
   keys := []string{
      "a", "b",
   }
   for i, _ := range keys {
      if value, ok := m[keys[i]]; ok {
         fmt.Println(value)
      } else {
         fmt.Printf("%s not exists", keys[i])
      }
   }
}

实现set

func main() {
	mySet := New()
	mySet.Add("a")
	mySet.Add("b")
	mySet.Add(1)
	print(mySet)
}

type Set struct {
	e map[interface{}]bool
}

//对外暴露的构造函数
func New() *Set {
	return &Set{e: make(map[interface{}]bool)}
}

func (set *Set) Add(element interface{}) bool {
	if !set.e[element] {
		set.e[element] = true
		return true
	}
	return false
}

func (set *Set) Remove(element interface{}) {
	delete(set.e, element)
}

func (set *Set) Clear() {
	set.e = make(map[interface{}]bool)
}

func (set *Set) Contains(element interface{}) bool {
	return set.e[element]
}

func (set *Set) String() string {
	var buf bytes.Buffer
	buf.WriteString("Set{")
	for k := range set.e {
		buf.WriteString(fmt.Sprintf("%v,", k))
	}
	buf.WriteString("}")
	return buf.String()
}

func print(o ...interface{}) {
	fmt.Println(o)
}

实现消息队列(多生产者,多消费者)

使用切片加锁可以实现。

使用channel实现。

//chan
func producer(c chan int, i int) {
   c <- i
}

func customer(c chan int) {
   fmt.Println(<-c)
}

func testPC() {
   queue := make(chan int, 10)
   for i := 0; i < 10; i++ {
      go producer(queue, i)
   }
   for i := 0; i < 10; i++ {
      go customer(queue)
   }
   time.Sleep(time.Second)
}
//slice + lock
type queue struct {
	msg  []int
	lock sync.Mutex
}

func producer(q *queue, i int) {
	q.lock.Lock()
	defer q.lock.Unlock()
	q.msg = append(q.msg, i)
}

func customer(q *queue) {
	q.lock.Lock()
	defer q.lock.Unlock()
	for len(q.msg) > 0 {
		m := q.msg[0]
		fmt.Println(GetGID(), m)
		q.msg = q.msg[1:]
	}
}

func testPC() {
	q := queue{}
	for i := 0; i < 10; i++ {
		go producer(&q, i)
	}
	for i := 0; i < 10; i++ {
		go customer(&q)
	}
	time.Sleep(time.Second)
}

func GetGID() uint64 {
	b := make([]byte, 64)
	b = b[:runtime.Stack(b, false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, _ := strconv.ParseUint(string(b), 10, 64)
	return n
}

Go的反射包

参考:https://blog.csdn.net/u012291393/article/details/78378386

手写循环队列

写的循环队列是不是线程安全,不是,怎么保证线程安全,加锁,效率有点低啊,然后面试官就提醒Go推崇原子操作和channel。。。

func testSqQueue(){
   sq := InitQueue()
   sq.EnQueue(1)
   sq.EnQueue(2)
}

const CAP = 5

type SqQueue struct {
   data  [CAP]int
   front int
   rear  int
}

func InitQueue() *SqQueue {
   return &SqQueue{front: 0, rear: 0}
}

func (s *SqQueue) EnQueue(d int) error {
   if (s.rear+1)%CAP == s.front {
      return errors.New("full")
   }
   s.data[s.rear] = d
   s.rear = (s.rear + 1) % CAP
   return nil
}
func (s *SqQueue) DeQueue() (int, error) {
   if s.rear == s.front {
      return 1, errors.New("empty")
   }
   e := s.data[s.front]
   s.data[s.front] = 0
   s.front = (s.front + 1) % CAP
   return e, nil
}

高效地拼接字符串

Go 语言中,字符串是只读的,也就意味着每次修改操作都会创建一个新的字符串。如果需要拼接多次,应使用 strings.Builder,最小化内存拷贝次数。strings.Builder不是线程安全的。

func testStrBuilder() {
   a := strings.Builder{}
   a.WriteString("123")
}
//底层
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, s...)
	return len(s), nil
}

go优缺点

  1. 静态强类型
  2. 编译型
  3. 并发型。GMP调度器,内置一个大小的协程池
  4. 有自动垃圾回收功能的编程语言

go使用踩过什么坑

函数传值、传引用;for range 切片不能改变值

sync.Pool用过吗,为什么使用,对象池,避免频繁分配对象(GC有关),那里面的对象是固定的吗?

是用来保存和复用临时对象,以减少内存分配,降低CG压力。 里面的对象不是固定的。sync.Pool可以安全被多个线程同时使用,保证线程安全。sync.Pool中保存的任何项都可能随时不做通知的释放掉,所以不适合用于像socket长连接或数据库连接池。sync.Pool主要用途是增加临时对象的重用率,减少GC负担。

func (s *Student) String() string {
   return s.name
}

func testPool() {
   studentPool := sync.Pool{
      New: func() interface{} {
         return &Student{"abc"}
      }}

   for i:=0;i<100000;i++{
      stud := studentPool.Get().(*Student)
      fmt.Printf("%p %v\n", stud, stud)
   }
}
//对比
type C1 struct {
	B1 [10000000]int
}

func usePool() {
	pool := sync.Pool{New:
	func() interface{} {
		return new(C1)
	}}
	startTime := time.Now()
	for i := 0; i < 10000; i++ {
		c := pool.Get().(*C1)
		c.B1[0] = 1
		pool.Put(c)//需要加上
	}
	fmt.Println("Used time : ", time.Since(startTime))
}

func standard() {
	startTime := time.Now()
	for i := 0; i < 10000; i++ {
		var c C1
		c.B1[0] = 1
	}
	fmt.Println("Used time : ", time.Since(startTime))
}
//standard Used time :  2m36.8892607s
//usePool Used time :  70.8105ms

go的runtime如何实现

Go 的 Runtime 是整个Go语言的核心,负责协程的调度,垃圾回收,以及协程运行环境维护等等。 Go代码由Go提供的专门的编译器编译。而 runtime 其实就是一个静态链接库,Go编译器会在链接阶段将runtime的部分与go代码进行静态链接。Runtime 相当于用户代码和系统内核的一个中间层。用户层使用channel,goroutine等等,都是调用的Runtime提供的接口,对于Go的业务代码来说,是接触不到真正的内核调用的。

GMP模型:一个G表示一个单个的 goroutine,对于用户层来说,每次调用 go 关键字就会创建一个G,编译器会将使用 go 关键字的代码片段(或函数)通过 newProc() 生成一个G,G的结构包括了这个代码段的上下文环境,最终这些G会交给runtime去调度。 M是runtime在OS层创建的线程,每个M都有一个正在M上运行的G,还有诸如缓存,锁,以及一个指向全局的G队列的指针等数据。

go的锁实现

type Mutex struct {
	state int32
	sema  uint32
}
  • Mutex.state表示互斥锁的状态,比如是否被锁定等。
  • Mutex.sema表示信号量,协程阻塞等待该信号量,解锁的协程释放信号量从而唤醒等待信号量的协程。

我们看到Mutex.state是32位的整型变量,内部实现时把该变量分成四份,用于记录Mutex的四种状态。下图展示Mutex的内存布局:

img

  • Locked: 表示该Mutex是否已被锁定,0:没有锁定 1:已被锁定。
  • Woken: 表示是否有协程已被唤醒,0:没有协程唤醒 1:已有协程唤醒,正在加锁过程中。
  • Starving:表示该Mutex是否处理饥饿状态, 0:没有饥饿 1:饥饿状态,说明有协程阻塞了超过1ms。
  • Waiter: 表示阻塞等待锁的协程个数,协程解锁时根据此值来判断是否需要释放信号量。

多协程加锁解锁释放信号量场景;自旋;自旋条件;防止协程饿死

引用:https://blog.csdn.net/weixin_34208283/article/details/91699624

看过sql的连接池实现吗

github.com/jinzhu/gorm/dialects/mysql github.com/go-sql-driver/mysql gorm复用标准库sql的连接池。go标准库sql已经实现了连接池。

引用:https://www.cnblogs.com/ZhuChangwu/p/13412853.html#%E4%B8%80%E3%80%81%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5

go什么情况下会发生内存泄漏?

ctx没有cancel的时候。。。

定时器

func testAfter() {
   tchan := time.After(time.Second * 3)
   fmt.Println(time.Now().String(),"tchan=", <-tchan)
}

func testNewTicker() {
   ticker := time.NewTicker(2 * time.Second)
   defer ticker.Stop()
   done := make(chan bool)
   go func() {
      time.Sleep(5 * time.Second)
      done <- true
   }()
   for {
      select {
      case t := <-ticker.C:
         fmt.Println("Current time: ", t)
      case <-done:
         return
      }
   }
}

func testTick() {
   c := time.Tick(2 * time.Second)
   for next := range c {
      fmt.Printf("%v \n", next)
   }
}

context包的用途

http包、sql中也用 。

Context 的主要作用就是在不同的 Goroutine 之间同步请求特定的数据、取消信号以及处理请求的截止日期。

引用:https://segmentfault.com/a/1190000024441501

func testCancelCtx() {
   ctx, cancel := context.WithCancel(context.Background())
   defer cancel()
   //每隔1s说一话,testCancelCtx函数在10s后执行cancel,那么speak检测到取消信号就会退出。
   go func(ctx context.Context) {
      for range time.Tick(time.Second) {
         select {
         case <-ctx.Done():
            return
         default:
            fmt.Println("speak")
         }
      }
   }(ctx)
   time.Sleep(10 * time.Second)
}

func testDeadlineCtx() {
   later, _ := time.ParseDuration("10s")
   deadline := time.Now().Add(later)
   ctx, cancel := context.WithDeadline(context.Background(), deadline)
   defer cancel()
   go func(ctx context.Context) {
      select {
      case <-ctx.Done():
         fmt.Println(ctx.Err())
      case <-time.After(20 * time.Second)://其他的case没有io的话,该case io会阻塞20秒后输入
         fmt.Println("stop")
      }
   }(ctx)
   time.Sleep(20 * time.Second)
}

func testTimeoutCtx() {
   ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   defer cancel()
   go func(ctx context.Context) {
      select {
      case <-ctx.Done():
         fmt.Println(ctx.Err())
      case <-time.After(20 * time.Second):
         fmt.Println("stop monitor")
      }
   }(ctx)
   time.Sleep(20 * time.Second)
}

type key string

func testValueCtx() {
   ctx := context.WithValue(context.Background(), key("a"), "123")
   Get(ctx, "a")
   Get(ctx, "b")
}
func Get(ctx context.Context, k key) {
   if v, ok := ctx.Value(k).(string); ok {
      fmt.Println(v)
   }
}

怎么实现协程完美退出?

1、通过channel传递退出信号。这种方式可以实现优雅地停止goroutine,但是当goroutine特别多的时候,这种方式 不太行。

2、使用waitgroup

go为什么高并发好?

语言层支持并发。Goroutine非常轻量:可以轻松支持10w 级别的Goroutine 运行上下文切换代价小;而对比线程的上下文切换则需要涉及模式切换(从用户态切换到内核态);内存占用少:线程栈空间通常是 2M,Goroutine 栈空间最小 2K;

Go调度器:GMP模型。。。

引用:https://cloud.tencent.com/developer/article/1594342

怎么理解go的interface

interface的内部实现包含了 2 个字段,类型 T 和 值 V

空的interface类似一个任意类型,任何类型的struct都实现了空接口。

协程泄露

协程泄露是指协程创建后,长时间得不到释放。程序后续不断地创建新的协程,最终导致内存耗尽,程序崩溃。常见的导致协程泄露的场景有以下几种:

  • 缺少接收器,导致发送阻塞

这个例子中,每执行一次 query,则启动1000个协程向信道 ch 发送数字 0,但只接收了一次,导致 999 个协程被阻塞,不能退出。

func query() int {
	ch := make(chan int)
	for i := 0; i < 1000; i++ {
		go func() { ch <- 0 }()
	}
	return <-ch
}

func main() {
	for i := 0; i < 4; i++ {
		query()
		fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
	}
}
  • 缺少发送器,导致接收阻塞

那同样的,如果启动 1000 个协程接收信道的信息,但信道并不会发送那么多次的信息,也会导致接收协程被阻塞,不能退出。

  • 死锁(dead lock)

两个或两个以上的协程在执行过程中,由于竞争资源或者由于彼此通信而造成阻塞,这种情况下,也会导致协程被阻塞,不能退出。

  • 无限循环(infinite loops)

在协程中,为了避免网络等问题,采用了无限重试的方式,发送 HTTP 请求,直到获取到数据。那如果 HTTP 服务宕机,永远不可达,导致协程不能退出,发生泄漏。

定位方法:goroutine泄露会使用到pprof,pprof是Go的性能工具 ;监控内存,协程数。

解决方法:goroutine泄漏处理,设置timeout,select加定时器。监测机制

引用:https://zhuanlan.zhihu.com/p/74090074

用channel实现自定义定时器

type MyTimer struct {
	timeDelay time.Duration
	c         chan time.Time
}

func NewTimer(t time.Duration) *MyTimer {
	return &MyTimer{t, make(chan time.Time)}
}

func (t *MyTimer) Tick() {
	go func() {
		for {
			time.Sleep(t.timeDelay)
			t.c <- time.Now()
		}
	}()
}

func testMyTimer() {
	timer := NewTimer(2 * time.Second)
	timer.Tick()
	for i := range timer.c {
		fmt.Println(i)
	}
}

逃逸分析

Go 语言的局部变量分配在栈上还是堆上由编译器决定。Go 语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有超出函数范围,就可以在栈上,反之则必须分配在堆上。

foo() 函数中,如果 v 分配在栈上,foo 函数返回时,&v 就不存在了,但是这段函数是能够正常运行的。Go 编译器发现 v 的引用脱离了 foo 的作用域,会将其分配在堆上。因此,main 函数中仍能够正常访问该值。

func foo() *int {
	v := 11
	return &v
}

func main() {
	m := foo()
	println(*m) // 11
}

new和make的区别

new 的作用是初始化一个指向类型的指针(*T)。new函数是内建函数,函数定义:func new(Type) *Type

使用new函数来分配空间。传递给new 函数的是一个类型,不是一个值。返回值是 指向这个新分配的零值的指针

make 的作用是为 slice,map 或 chan 初始化并返回引用(T)。make函数是内建函数,函数定义:func make(Type, size IntegerType) Type。make(T, args)函数的目的与new(T)不同。它仅仅用于创建 Slice, Map 和 Channel,并且返回类型是 T(不是T*)的一个初始化的(不是零值)的实例。

go命令

go env: #用于查看go的环境变量

go run: #用于编译并运行go源码文件

go build: #用于编译源码文件、代码包、依赖包

go get: #用于动态获取远程代码包

go install: #用于编译go文件,并将编译结构安装到bin、pkg目录

go clean: #用于清理工作目录,删除编译和安装遗留的目标文件

go version: #用于查看go的版本信息

go mod 包管理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值