在Tencent实习的第一周。

398 阅读12分钟

👨‍🏫 在Tencent实习的第一周.

🙏 本文主要分享去大厂实习应该有什么收货和我在腾讯实习一周的感受和收货。

🔖 一、第一周收获.

第一周收获虽然很少,但是都是自己以前没有学到过的或者是以前不知道的.

💡1.1 Golang 小技巧.

下面的几个都是在工作中学习到的,并且觉得用起来比较顺手的。

  • atomic.Value
  1. 为什么要把这个atomic.Value给列举出来呢? 首先是因为,Java 里面是没有的,其次是其通过共享内存来达成goroutine之间的通信。这点是 Java 模拟不出来的。用起来也是比较顺手的。

  2. 怎么使用呢?

    atomic.Vlue结构体有两个方法,一个是Store一个是Load,我们先来看看这两个方法的含义:

    Store: StoreValue的值设置为X,所有对给定的值得 Store 调用都必须使用相同类型的值,类型不一致的存储会出现恐慌,Store(nil) 也是如此. 我们来看下 Store 的源码.

    我们先来看下下面的这个数据结构.

    // ifaceWords is interface{} internal representation.
    // 该结构体是interface内部表示.
    // So? 以后我们写代码的时候是不是也可以这么写呢? 
    type ifaceWords struct {
    	typ  unsafe.Pointer
    	data unsafe.Pointer
    }
    
    func (v *Value) Store(x interface{}) {
    	if x == nil {
    		panic("sync/atomic: store of nil value into Value")
    	}
        // 获取到ifaceWords的类型指针.也就是interface的类型指针. 
        // Interface 可以用来表示一切. 
        // TODO: 需要清除这两个指针的区别. 
    	vp := (*ifaceWords)(unsafe.Pointer(v))
        // 获取到本次要进行存储的类型指针. 
    	xp := (*ifaceWords)(unsafe.Pointer(&x))
    	for {
            // 以原子的方式加载指针内存指向的内存地址. 
            // typ 就是本次存储的类型。 
    		typ := LoadPointer(&vp.typ)
            // 如果typ 为空, 则说明当前的Value是空的,所以是第一次进行添加. 
    		if typ == nil {
    			// Attempt to start first store.
    			// Disable preemption so that other goroutines can use
    			// active spin wait to wait for completion; and so that
    			// GC does not see the fake type accidentally.
                // 尝试第一次进行添加. 
                // 禁止抢占,运行时实现.我理解的是在执行了该行代码之后,Go调度器可能就不允许其他goroutine来调度该方法,大概是这个意思,因为go源码中就只有这个一个方法. 哈哈哈, 但是因为Value的Store是原子性质的,所以我认为这两个函数就相当于加锁和解锁的区别. 
    			runtime_procPin()
                // CAS 操作, 结合For自旋.
                // 如果Value.type的类型不是l,那么就讲type类型设置为uintptr...
                // TODO: 为什么要这么做呢?
                // 如果交换CAS失败的话,那么久重新交换.
                // 总之就是将Value的type设置为uintptr类型. 真正的赋值操作在下面. 
    			if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
    				runtime_procUnpin()
    				continue
    			}
    			// Complete first store.
                // 存储类型和存储数据. 
    			StorePointer(&vp.data, xp.data)
    			StorePointer(&vp.typ, xp.typ)
                // 开启抢占. 
    			runtime_procUnpin()
    			return
    		}
            // 下面的表示不是第一次Store. 
            // 如果是和第一次存储的类型相同
    		if uintptr(typ) == ^uintptr(0) {
    			// First store in progress. Wait.
    			// Since we disable preemption around the first store,
    			// we can wait with active spinning.
                // 如果是有goroutine进行到这里,就说明第一个进行Store的协程还在进行,但是第一个设置了禁用抢占,所以当前的goroutine主动进入自选操作。 
    			continue
    		}
    		// First store completed. Check type and overwrite data.
            // 判断后续Store是不是同一类型. 
            // 如果是就存储且覆盖以前的Store. 如果不是则会panic. 
    		if typ != xp.typ {
    			panic("sync/atomic: store of inconsistently typed value into Value")
    		}
    		StorePointer(&vp.data, xp.data)
    		return
    	}
    }
    

    Value.Load:我们来看看Value.Load方法.

    // Load returns the value set by the most recent Store.
    // It returns nil if there has been no call to Store for this Value.
    // Load 返回由最近的Stroe设置的值,如果没有为此Value调用Store,那么就返回 nil.
    func (v *Value) Load() (x interface{}) {
        
        // 获取到Value的指针. 
    	vp := (*ifaceWords)(unsafe.Pointer(v))
        // 加载指针哪里的值. 
        // typ 就是当前Value存储的类型. 
    	typ := LoadPointer(&vp.typ)
    	if typ == nil || uintptr(typ) == ^uintptr(0) {
    		// First store not yet completed.
            // 还没有调用过Store或者Store正在被调用. 
    		return nil
    	}
        // 正常逻辑. 
        // 加载老数据. 
    	data := LoadPointer(&vp.data)
        // 获取本次要进行存储的数据. 
        // 应该是加载要返回的值得内存地址. 
    	xp := (*ifaceWords)(unsafe.Pointer(&x))
        // 给要进行返回的值得内存进行赋值. 
    	xp.typ = typ
    	xp.data = data
    	// 这里直接返回的就是x. 
    	return
    }
    

    Value的源码分析到这里就已经结束了,那么应该怎么去用呢? 这里可以提示下思路(因为用的太广泛了,所以我在这里就简单说一下):在对于一些结构体来说,某些字段用来进行同步或者进行标志位的,都可以用 Value 来实现,一般来说,经常将该字段设置为某个结构体的字段,用来表示同步等。

  • atomic.Once 挺好用. 用来接进行多个方法内部的初始化

    老规矩,先分析源码,在来说说用途.

    • atomic.Once 源码分析.

    先来看看结构体.

    type Once struct {
    	// done indicates whether the action has been performed.
    	// It is first in the struct because it is used in the hot path.
    	// The hot path is inlined at every call site.
    	// Placing done first allows more compact instructions on some architectures (amd64/386),
    	// and fewer instructions (to calculate offset) on other architectures.
        // done 表示动作是否已经执行. 它是结构中的第一个,
        // 根据上面的注释可以看出 我不是很懂 哈哈哈. 
        // 大师大概的意思就是做过和没有做过之间的标志把. 
    	done uint32
        // 互斥锁.
    	m    Mutex
    }
    

    我们来看下其只能实现一次调用的实现逻辑. Once.Do就算被调用多次,f 也只会调用一次.

    func (o *Once) Do(f func()) {
    	// Note: Here is an incorrect implementation of Do:
    	//
    	//	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    	//		f()
    	//	}
    	// 
    	// Do guarantees that when it returns, f has finished.
    	// This implementation would not implement that guarantee:
    	// given two simultaneous calls, the winner of the cas would
    	// call f, and the second would return immediately, without
    	// waiting for the first's call to f to complete.
    	// This is why the slow path falls back to a mutex, and why
    	// the atomic.StoreUint32 must be delayed until after f returns.
    	// LoadUint32 加载指定内存的值,默认是Uint32类型.
        // 如果done为0则表示还没有进行执行. 
    	if atomic.LoadUint32(&o.done) == 0 {
            // 如果没有执行,那么就执行.
    		// Outlined slow-path to allow inlining of the fast-path.
    		o.doSlow(f)
    	}
    }
    
    func (o *Once) doSlow(f func()) {
        // 互斥,仍然只能保持一个协程执行成功. 
        // DCL思想. 
    	o.m.Lock()
    	defer o.m.Unlock()
    	if o.done == 0 {
            // 在调用了函数f()之后, 将done设置为1. 
    		defer atomic.StoreUint32(&o.done, 1)
    		f()
    	}
    }
    

    sync.Once 可以用来做同步操作,这里的思想是用内存来实现同步,可以应用在下面的操作:在我们加载配置文件的时候,我们可以把加载配置文件的实现逻辑放在一个函数里,然后再Config中保存一个sync.Once,对于多种不同初始化配置的方法,我们可以在第一行通过调用once.Do来实现初始化代码只能进行一次的作用,如果要用Java 来写的话,需要写一个单例模式来保证这种唯一性. 但是 Go 提供的这个特性可以通过一个结构体来完成,我觉得这个特性也是可以通过Java 来实现的. 我们来看下伪代码.

    可以看到,如果使用 sync.Once 可以很好的实现同步.

    // Package config sync.Once demo.
    package config
    
    import "sync"
    
    // 全局配置. 
    var config *Config
    
    type Config struct {
    	ConfigValues            // 表示所有的配置. 
    	once         *sync.Once // 用于进行同步. 
    }
    
    // ConfigValues 表示配置内容. 
    type ConfigValues struct {
    	// TODO: 小Demo.
    	values map[string]interface{} // 存储所有的配置. 
    }
    
    func init() {
    	config = &Config{}
    	// .....
    	config.once.Do(initConfig)
    }
    
    // 用来进行初始化. 
    // 该方法只在once.Do 里面进行调用. 
    func initConfig() {
    	// ......
    	// 这里面是一些用来进行初始化的一些操作. 
    }
    
    func initConfigWithKey(key string, value interface{}) {
    	config.once.Do(initConfig)
    }
    
    
  • atomic.LoadUnit32atomic.StoreUnit32

    用于加载指定内存的值,并且返回为指定的类型。

  • unsafe.Pointer 通过该方法可以突破Go的类型系统,让程序更加灵活等。[参考博客](浅谈Golang的unsafe.Pointer - 简书 (jianshu.com))

    unsafe.Pointer 就是可以将任意类型的指针值都可以转换为 unsafe.Pointer 类型,然后强制转换为自己想要的类型指针。

💡1.2 学习远程开发使用.

刚入职第一天,学习到利用SSH和本地IDE来进行远程开发,那个时候确实惊到了我,因为以前都是在本地写好Code之后,再上传到服务器上进行部署, 但是现在不需要这么麻烦了,我们可以直接通过SSH链接远程服务器,并指定好对应的目录进行开发,开发完毕之后,在服务器上直接进行打包部署,那样岂不是很舒服? 哈哈哈,下面我们就来学习一些 JetBrains系列的IDE如何进行通过SSH来完成远程开发的功能. 该配置网上有很多,如果看我的不是很明白,可以去参考网上的步骤哈~ 这里我使用的是Goland,但是步骤都是一样的。

  • 打开IDE找到tools下面的 Deployment,然后打开其中的 Configuration image-20210619122831558

  • 一些SSH以及文件映射的相关配置.

注意:

  1. 我们要选SFTP类型.
  2. Mapping 映射目录要选对.

按照下面的步骤来即可完成配置.

image-20210619123220858

当我们去选择SSH的时候,会让我们去创建一个新的SSH链接,OK,既然需要我们去创建,那我们就去创建.

这里我使用了SSH进行配置,如果你们想用SSH的话,可以自己去进行配置下,但是也可以使用密码的形式来进行配置等。port.

image-20210619123401041

出现下面的图表示配置SSH成功.

image-20210619123614044

我们看下图,其中SSH 如果能出现上一步配置的SSH,那么就表示可以连接到远程服务器上,如果能连接到远程服务器上的话,那么我们打开Root Path 后面的文件夹就可以看到远程服务器上目录,这个时候我们只需要选择即可。

image-20210619123726276

最重要的就是我们需要配置下本地文件和远程文件的一个映射关系这个也是很简单的,我们看下图即可。

其中下面的Root Path + Deployment path 就是服务器上具体的文件,我们需要将这个文件和本地的文件联系起来才能进行远程开发。

image-20210619124007653

OK,基本上到这里就配置成功了,可是怎么去看自己是否配置成功了呢?

当我们看到下面的图片标红的那几行不是灰的就说明配置好了,由于我这里是做的测试,所以我就没有让 本地和服务器进行关联,所以说我这里是灰的。一般配置好之后,我们可以使用Download from $server$ 从服务器上下载代码.

image-20210619124235476

🔖二、未来三个月的实习目标.

在大厂实习应该有什么收获? 以及我在鹅厂实习的目标.

💡 2.1 在大厂实习应该有什么收获?

先来了解下在大厂实习应该有的收获,在大厂实习,最重要的就是自身能力的提升,不仅仅包括Code的能力提升,也包括一些软实力的提升,不过Code能力提升是最重要自己去收获的,在大厂,我认为首先应该就是熟悉自己所在组的环境和项目周期,熟悉环境就是熟悉项目代码,熟悉代码规范,以及熟悉公司整体环境,比如吃饭等等,哈哈哈,熟悉项目代码,我认为你去看他的项目代码还不如自己跟着它的项目代码自己去吧核心流程敲一下,那样自己会有很大的收获。至少会比只看代码会有好处的。理解公司的项目周期最主要就是要问学长或者同事或者mentor,让他们告诉你公司的迭代速度,那样的话你自己去排期的话会很好的去排期。不会导致时间弄得不是很完美。其中Code能力的提升是最好的,最好能把本组的核心功能的代码流程给理清楚理明白,那样在后续的工作中或者是在后续的面试中对自己是很有好处的(虽然我自己在实习之后还没有面试,但是我认为是应该有帮助的 嘿嘿)。如果能把公司内部自研的框架给看明白,顺便自己照葫芦画瓢搞一个,那么对于以后的面试是非常有帮助的。

💡 2.2 在Tencent实习的目标.

我认为最重要就是要有一个实习目标(一定要大),这个目标我认为可以最为实习最终的目标,因为实习时间是很短的,也就三个月左右,在自己刚刚入职的时候,如果能给自己定一个目标的话,那么在整个实习期间都会向这个目标去努力,那样最后的也是会有实习成果的。可能达不到自己理想的目标成果,但是也不会和自己理想的目标相距太远。就以小马我来说,我给自己定的目标是首先能完成mentor给布置的任务,然后呢就是看懂内部自研Golang分布式框架、打好Golang基础、按照[大佬博客](用 Golang 实现 Redis - 随笔分类 - -Finley- - 博客园 (cnblogs.com))自己用Go写一个Redis服务(不要求和大佬的一样,能实现基本需求就可以),最重要的就是依托腾讯实习经验在秋招中找能进入阿里云-杭州(我自己很想去的部门)或者找到一个SP的Offer。以上就是我这三个月要去实现的目标,虽然很遥远,但是自己也会向着这个目标去努力,只要自己不去放弃,那就是会有机会的。对了,还有就是坚持公众号和掘金每周至少更新一篇文章。

最后,愿自己能在这三个月内坚持下去!💪💪💪💪💪

欢迎关注下我的公众号,一起讨论、一起加油。

img