腾讯一面凉经-光子工作室
- 自我介绍
- 平常玩游戏吗?玩什么游戏
- 对腾讯的印象
- 对加班的看法
make和new的区别
- new()方法传入一个值类型返回一个传入类型的指针,还会初始化内存空间
- make()方法主要用来初始化channel、map、slice,返回初始化类型的引用类型
func main() { // 返回*int a := new(int) // 赋值需要解引用 *a = 2 // 输出地址 fmt.Pintln(a) // 输出值 fmt.Println(*a) // 涉及到指针类型的时候初始化内存空间需要注意 b := new(B) b.name = "test1" // 输出&{test1} fmt.Println(b) var c *B // panic c.name = "test2" fmt.Println(c) var c B c.name = "test2" // 输出{test2} fmt.Println(c) } type B struct { name string }
channel相关
- 向一个nil channel发送信息会发生什么
- 永久阻塞导致死锁,会发生fatal error: all goroutines are asleep - deadlock!
- 从一个nil channel接收消息会发生什么
- 永久阻塞导致死锁,会发生fatal error: all goroutines are asleep - deadlock!
- 以上两种情况的fatal error都是在所有协程进入阻塞或睡眠状态才会发生的报错
- 向一个已经关闭的channel发送信息会发生什么
- 会直接发生panic:panic: send on closed channel
- 从一个已经关闭的channel接收消息会发生什么
- 可以正常接收值,<-channel中可以返回两个值,第一个为接收到的值,第二个代表是否正常接受数据,如果channel已经关闭,第一个为传输数据类型的零值,第二个为false
- channel是同步的还是异步的
- 有缓存的channel是异步的,没有缓存的channel为同步的,在没有缓存的channel中传输数据时需要注意发送数据时会发生阻塞,如果没有协程进行读取数据就会发生fatal dead lock
// sendNilChannel 向一个nil channel发送数据 func sendNilChannel() { var ch1 chan int go func() { time.Sleep(2 * time.Second) fmt.Println("test") }() var a int // 永远阻塞,当子协程执行完成报错 // fatal error: all goroutines are asleep - deadlock! a = <-ch1 fmt.Println(a) } // resvNilChannel 从一个nil channel中读取数据 func resvNilChannel() { var ch1 chan int go func() { time.Sleep(2 * time.Second) fmt.Println("test") }() // 永远阻塞,当子协程执行完成报错 // fatal error: all goroutines are asleep - deadlock! ch1 <- 2 } // sendClosedChannel 向一个已经关闭的channel发送数据 func sendClosedChannel() { ch1 := make(chan int) close(ch1) // 发生panic panic: send on closed channel ch1<-1 } // rersvClosedChannel 从一个已经关闭的channel接收数据 func resvClosedChannel() { ch1 := make(chan int) go func() { v, ok := <-ch1 // 2, true fmt.Println(v, ok) close(ch1) // 0, false v, ok = <-ch1 fmt.Println(v, ok) }() ch1<-2 time.Sleep(2 * time.Second) } // syncChannel 同步channel func syncChannel() { // 不设置缓冲区大小默认为0 ch1 := make(chan int) go func() { fmt.Println(<-ch1) }() ch1 <- 1 // 为了子协程能够正常接收数据,睡眠一秒 time.Sleep(1 * time.Second) } // asyncChannel 异步channel func asyncChannel() { // 创建一个缓冲区大小为2的channel ch1 := make(chan int, 2) // 因为存在缓冲区,所以在缓冲区没满时不会发生阻塞,会继续往下执行 ch1 <- 1 ch1 <- 2 // 如果channel缓冲区已满就会发生阻塞等待缓冲区有剩余空间 // ch1 <- 3 // 打印输出1/n2/n fmt.Println(<-ch1) fmt.Println(<-ch1) }
waitGroup传值会发生什么
// 正常执行 func n(wg *sync.WaitGroup) { defer wg.Done() fmt.Println("TEST2") } // 这时候的wg.Done()没有任何作用,协程执行完成后发生死锁报错,fatal error: all goroutines are asleep - deadlock! // 在goland中会提示'p' passes a lock by the value: type 'sync.WaitGroup' contains 'interface{}' which is 'sync.Locker' // 就是这个原因导致waitGroup失效 func p(wg sync.WaitGroup) { defer wg.Done() fmt.Println("TEST1") }
sync包里面的锁
- sync包里都有哪些锁
- sync.Mutex:互斥锁,同一时间只有一个协程能够使用
// A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct { state int32 sema uint32 }
- sync.RWMutex
// A RWMutex is a reader/writer mutual exclusion lock. // The lock can be held by an arbitrary number of readers or a single writer. // The zero value for a RWMutex is an unlocked mutex. // RWMutex是一个读写锁 // 这个锁可以被任意个读者或一个写着所使用 // 当值为0的时候RWMutex为没有被加锁 // A RWMutex must not be copied after first use. // 一个读写锁在第一次被使用后一定不能被复制 // // If a goroutine holds a RWMutex for reading and another goroutine might // call Lock, no goroutine should expect to be able to acquire a read lock // until the initial read lock is released. In particular, this prohibits // recursive read locking. This is to ensure that the lock eventually becomes // available; a blocked Lock call excludes new readers from acquiring the // lock. // 如果一个 goroutine 持有一个 RWMutex 用于读取并且另一个 goroutine 可能调用 Lock,Lock在这里为获取读写锁, // 则没有 goroutine 应该期望能够获取读取锁直到最初的读锁被释放 // 特别是,这禁止递归读锁定。 这是为了确保锁最终可用; // 阻塞的 Lock 调用会阻止新读者获取锁,也就是说在一个协程申请读写锁的时候发生了阻塞,则后面所有协程的读锁都会被拒绝,再简单点就是说读写锁优先 type RWMutex struct { w Mutex // held if there are pending writers writerSem uint32 // semaphore for writers to wait for completing readers readerSem uint32 // semaphore for readers to wait for completing writers readerCount int32 // number of pending readers readerWait int32 // number of departing readers }
- 加了写锁之后能加读锁吗,加了读锁后能加写锁吗
- 加了写锁之后不能加读锁,加写锁会阻塞并拒绝所有请求的读锁
- 读锁可以加任意个
context的作用
- 主要用来控制其他协程的通信,也可用来传递值
- Go Context 使用和代码分析
面向对象的三大特性
- 封装
- 封装是面向对象方法的重要原则,就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节
- 将类的某些信息隐藏在类的内部,不允许外部程序进行直接的访问调用
- 通过该类提供的方法实现对隐藏信息的操作和访问
- 隐藏对象的信息
- 留出访问的对外接口
- 继承
- 继承就是子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。在父类中拥有私有属性(private修饰),则子类是不能被继承的
- 子类可以拥有父类的属性和方法
- 子类可以拥有自己的属性和方法
- 子类可以重写父类的方法
- 可以提高代码复用性
- 可以轻松的定义子类
- 使设计应用程序变得简单
- 多态
- 多态是同一个行为具有不同表现形式或形态的能力
- 消除类型之间的耦合关系,实现低耦合
- 灵活性、可扩充性、可替换性
- 体现形式:继承、父类引用指向子类、重写
select的作用
- 语法跟switch相似,但case中监听的是发送或接收channel的语句,如果调用select时没有接收到case中的接收或发送channel的信号时,则会发生阻塞,当在seletct中使用default语句时,则会执行default后的语句
- 当在同一时刻多个channel有响应时,go会使用一个伪随机算法进行定位到其中一个case进行执行
func main() { ch1 := make(chan int) go testSelect(ch1) // 主进程睡眠一秒 time.Sleep(1 * time.Millisecond) // 发送消息 ch1<-2 // 打印消息 fmt.Println(<-ch1) // 睡眠3秒等待触发time out time.Sleep(3 * time.Second) } // 运行结果 // 1 // timeout func testSelect(ch1 chan int) { // 死循环监听 for { select { case <-ch1: ch1<- 1 case <-time.After(2 *time.Second): fmt.Println("time out") } } }
map中key为string类型,有序输出输出所有map中的所有元素
func main() { mp := make(map[string]string) mp["c"] = "3" mp["b"] = "2" mp["a"] = "1" printSortMap(mp) } // 输出 1\n2\n3\n func printSortMap(mp map[string]string) { var tmp []string for k := range mp { tmp = append(tmp, k) } quickSort(tmp, 0, len(tmp) - 1) for i := range tmp { fmt.Println(mp[tmp[i]]) } } func quickSort(arr []string, l, r int) { if l >= r { return } j := partition(arr, l, r) quickSort(arr, l, j - 1) quickSort(arr, j + 1, r) } func partition(arr []string, l, r int) int { cur := arr[l] for l < r { for l < r && cur <= arr[r] { r-- } arr[l], arr[r] = arr[r], arr[l] for l < r && cur > arr[l] { l++ } arr[l], arr[r] = arr[r], arr[l] } return l }
defer调用顺序
- defer执行顺序为先进后出,当函数存在命名返回值时,在defer中可以对命名返回值修改
- Golang中的Defer必掌握的7知识点
闭包
GMP
线程和协程的区别
- 线程是操作系统调用的基本单位,是在内核态和用户态中的
- Golang:线程 和 协程 的区别
- 线程和协程的区别的通俗说明
- 写给大忙人看的进程和线程
异步IO和非阻塞IO的区别
非阻塞IO | 异步IO |
需要一直轮询,轮询过程中产生多次系统调用 | 不需要轮询 |
在数据准备好后的一次系统调用中再将数据从内核态复制到用户态中 | 数据未准备好直接返回,数据准备好后将数据从内核态中复制到用户态中,然后发送一个信号给进程表示已经完成了系统调用 |
死锁
- 死锁发生的四个必要条件
- 互斥性:在同一时刻一个资源只能被一个进程所使用
- 不可剥夺性:一个资源在被一个进程使用完前不能被强制剥夺
- 环形等待:所有进程形成一条首尾相连的环形资源等待链
- 请求和保持:一个进程因为请求某一资源被阻塞时保持现在拥有的资源不放
gRPC优势
引用类型和值类型对象方法的区别
引用类型 | 值类型 |
引用类型方法可以改变对象中的属性 | 值类型方法可以不可以改变对象中的属性 |
| |
引用类型和值类型对象实现接口的区别
如果实现接口的方法是引用类型,则不能返回一个结构体的实例返回给接口
type iInterface interface { SetName(name string) } type A struct { Name string } func newA() iInterface { return &A{ Name: "test", } } func newA() iInterface { // wrong return A{ Name: "test", } } func (a *A)SetName(name string) { a.Name = name }
引用对象一定比值对象好吗
- 不知道
c++ stl用过吗
c++ struct用过吗
反转链表Ⅱ
import ( "bufio" "fmt" "os" "strconv" "strings" ) type Node struct { next *Node val int } func main() { var n int fmt.Scan(&n) reader := bufio.NewReader(os.Stdin) data, _, _ := reader.ReadLine() str := string(data) tmp := strings.Split(str, " ") head := &Node{} node := head head.next = node for i := range tmp{ j, _:= strconv.Atoi(tmp[i]) node.next = &Node{ val: j, } node = node.next } var l, r int fmt.Scanf("%d %d", &l, &r) // 找到的是左边结点 left := head var i int for ; i < l - 1; i++ { left = left.next } // 找右边的结点的上一个结点 right := left for ;i < r ; i++ { right = right.next } // 记录左边开始结点和右边结尾结点 newNext := left.next newRight := right.next // 断开结点 left.next = nil right.next = nil // 反转链表 pre := &Node{} cur := newNext for cur != nil { q := cur.next cur.next = pre pre = cur cur = q } newNext.next = newRight left.next = pre node = head.next for node != nil { fmt.Printf("%d ", node.val) node = node.next } }