面试题笔试题总结——golang知识点

struct结构体能不能比较?
  • 结构体不可以比较,但是同一类型的结构体的值可以比较是否相等(不可以比较大小);
    结构体所有字段的值都相等,两个结构体才相等;
    比较的两个结构体必须是相同类型才可以,也就是说他们字段的顺序、名称、类型、标签都相同才可以
  • 因为是强类型语言,所以不同类型的结构不能作比较,但是同一类型的实例值是可以比较的,实例不可以比较,因为是指针类型
defer

顺序:先注册后执行,后注册先执行(类似栈、LIFO后进先出法)
触发时机:包含defer的函数返回时
包含return的函数执行到末尾时
所在的goroutine发生panic时
更多相关defer的知识可以参考https://www.jianshu.com/p/79c029c0bd58

select

用法:select就是用来监听和channel有关的IO操作,当IO操作发生时,触发相应`的动作。类似linux中的select,io多路复用机制。可以用作协程的退出

//select基本用法
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}

注意事项:

  • 如果有一个或多个IO操作可以完成,则GO运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行
  • 所有channel表达式都会被求值、所有被发送的表达式都会被求值。求值顺序:自上而下、从左到右。
  • break关键字结束select
waitgroup和context

waitgroup和context都可以用来控制并发,waitgroup类似linux中的waitpid,使用如下:

func main() {
	//创建一个新的channel,输入值限制类型为int
	nochan := make(chan int)
	//指向waitgroup结构体
	waiter := &sync.WaitGroup{}
	//添加两个队列
	waiter.Add(2)
	//从channel里接收数据
	go func(ch chan int, wt *sync.WaitGroup) {
		data := <-ch
		fmt.Println("receive data ", data)
		wt.Done()
	}(nochan, waiter)
	//发送数据到channel
	go func(ch chan int, wt *sync.WaitGroup) {
		ch <- 5
		fmt.Println("send data ", 5)
		wt.Done()
	}(nochan, waiter)
	//先输出1,
	fmt.Println(1)
	//等待协程执行完
	waiter.Wait()
	//再输出2
	fmt.Println(2)
}

udi@udi:~/Udi_work_space/Udi_ws_go/ZipTest_backend$ go run main.go 
1
receive data  5
send data  5
2

context

  • context可以用来跟踪goroutine,比如有一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他goroutine。这样的话,我们就可以通过Context,来跟踪这些goroutine,并且通过Context来控制他们的目的,这就是Go语言为我们提供的Context,中文可以称之为“上下文”。
  • 另外一个实际例子是,在Go服务器程序中,每个请求都会有一个goroutine去处理。然而,处理程序往往还需要创建额外的goroutine去访问后端资源,比如数据库、RPC服务等。由于这些goroutine都是在处理同一个请求,所以它们往往需要访问一些共享的资源,比如用户身份信息、认证token、请求截止时间等。而且如果请求超时或者被取消后,所有的goroutine都应该马上退出并且释放相关的资源。这种情况也需要用Context来为我们取消掉所有goroutine
生产者消费者模型实现

用channel实现生产者消费者模型

//生产者模型,往channel里输入数据
func produce(ch chan<- int) {
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Println("Send:", i)
	}
}
//消费者模型,从channel中取出数据
func consumer(ch <-chan int) {
	for i := 0; i < 10; i++ {
		v := <-ch
		fmt.Println("Receive:", v)
	}
}

ch = make(chan int) //无缓冲
ch = make(chan int) //有缓冲

用互斥锁和条件变量实现的生产者和消费者模型:

lock := new(sync.Mutex)
cond := sync.NewCond(lock)
var count int

func producer() {
	for i:=0;i<10;i++ {
		lock.Lock()
		count++
		cond.Signal()
		lock.Unlock()
	}
}

func consumer(){
	for i:=0;i<10;i++{
		lock.Lock()
		while(count == 0){
			cond.Wait()
		}
		count--
		lock.Unlock()
	}
}
new和make的区别

new的作用是初始化一个指向类型的指针(T),使用new函数来分配空间。传递给new函数的是一个类型,不是一个道,不是一个值。返回值是指向这个新分配的零值的指针。
make的作用是slice,map或chan初始化并返回引用(T)。
make(T,args)函数的目的与new(T)不同,它仅仅用于创建Slice,Map和Channel,并且返回类型是T(不是T)的一个初始化的(不是零值)的实例

go触发异常的场景

空指针解析,数组越界访问,除数为0,调用panic函数。

异常处理机制panic defer recover

panicing:F函数出现panic后,终止当前函数,并调用其defer函数,然后在终止调用F函数的函数并调用其defer函数,直至goroutine退出。
panic的处理:在defer函数中调用recover获得错误值,再处理错误。

函数变长参数使用
类型转换

type类型不能直接转换,只能强制转换,比如:

type Myint int

var a int
var b Myint

a = b // 错误
a = int(b) //正确
append,delete用法

append用来添加map中的元素
delete用来删除map中的元素

接口方法集调用规则
  • 类型T的可调用方法集包含接受者为T或者T的所有方法集
  • 类型T的可调用方法集包含接受者为T的所有方法
  • 类型T的可调用方法集不包含接受者为*T的方法
函数内变量在函数结束后并不释放,由垃圾回收器释放
协程池实现
import "sync"

type Woker interface {
	Work()
}

type Pool struct {
	work chan Worker
	wg sync.WaitGroup
} 

func NewPool(maxRoutine int) *Pool {
	p := Pool{
		work: make(chan Worker)
	}
	p.wg.Add(maxRoutine)
	for i:=0;i<maxRoutine;++i {
		go func(){
			for v := range work {
				v.Work()
			}
			p.wg.Done()
		}()
	}
	return &p
}

//提交工作到池子
func (p *Pool) Run(w Worker) {
	p.work <- w
}

//关闭池子
func (p *Pool) Close() {
	close(p.work)
	p.wg.Wait()
}
go类似多态实现
package main

import "fmt"

type AnimalIF interface {
    Sleep()
    Age() int
    Type() string
}
type Animal struct {
    MaxAge int
}

/*=======================================================*/

type Cat struct {
    Animal Animal
}

func (this *Cat) Sleep() {
    fmt.Println("Cat need sleep")
}
func (this *Cat) Age() int {
    return this.Animal.MaxAge
}
func (this *Cat) Type() string {
    return "Cat"
}

/*=======================================================*/

type Dog struct {
    Animal Animal
}

func (this *Dog) Sleep() {
    fmt.Println("Dog need sleep")
}
func (this *Dog) Age() int {
    return this.Animal.MaxAge
}
func (this *Dog) Type() string {
    return "Dog"
}

/*=======================================================*/

func Factory(name string) AnimalIF {
    switch name {
    case "dog":
        return &Dog{Animal{MaxAge: 20}}
    case "cat":
        return &Cat{Animal{MaxAge: 10}}
    default:
        panic("No such animal")
    }
}

/*======================================================*/

func main() {
    animal := Factory("dog")
    animal.Sleep()
    fmt.Printf("%s max age is: %d", animal.Type(), animal.Age())
}
panic和err的区别

处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。
panic and recover是用来处理真正的异常(无法预测的错误)而不是普通的错误。
当发生像数组下标越界或类型断言失败这样的运行错误时,Go运行时会触发运行时panic,伴随着程序的崩溃抛出一个runtime.Error接口类型的值。这个错误值有个RuntimeError()方法用于区别普通错误。

go语言实现单例模式

双重检验枷锁

type Singleton struct{
}

var ins *Singleton
var mutex sync.Mutex
func GetIns() *Singleton{
	if ins == nil {
		mutex.Lock()
		if ins == nil {
			ins = &Singleton{}
		}
		mutex.Unlock()
	}
	return ins
}

sync.Once实现

type Singleton struct{}

var ins *Singleton
var once sync.Once

func GetIns() *Singleton {
	once.Do(func() {
		ins = &Singleton{}
	})
	return ins
}
go实现简单工厂
package main
import (
    "fmt"
)
type Op interface {
    getName() string
}
type A struct {
}
type B struct {
}
type Factory struct {
}
func (a *A) getName() string {
    return "A"
}
func (b *B) getName() string {
    return "B"
}
func (f *Factory) create(name string) Op {
    switch name {
    case `a`:
        return new(A)
    case `b`:
        return new(B)
    default:
        panic(`name not exists`)
    }
    return nil
}

以上知识来源https://blog.csdn.net/littleflypig/article/details/98628692

Golang中除了加Mutex锁以外还有哪些方法安全读写共享变量?

Golang中Goroutine可以通过Channel进行安全读写共享变量

无缓冲Chan的发送和接受是否同步?

ch := make(chan int)无缓冲的channel由于没有缓冲发送和接收需要同步
ch := make(chan int, 2)有缓冲channel不要求发送和接收操作同步

  • channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读取数据
  • channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接受阻塞
go语言的并发机制以及它所使用的CSP并发模型

CSP模型是上个世纪七十年代提出的,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。用于描述两个独立的并发实体通过共享的通讯channel(管道)进行通信的并发模型。CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。

Golang中channel是被单纯创建并且可以在进程之间传递,它的通信模式类似于boss-worker模式的,一个实体通过将消息发送到channel中,然后又监听这个channel的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中channel是同步的一个消息被发送到channel中,最终是一定要被另外的实体消费掉的,在实现原理上其实类似一个阻塞的消息队列。

Goroutine是Golang实际并发执行的实体,他底层是使用协程实现并发,goroutine是一种运行在用户态的用户线程,类似于greenthread,go底层选择使用goroutine的出发点是因为,它具有以下特点:

  • 用户空间避免了内核态和用户态的切换导致的成本。
  • 可以由语言和框架层进行调度。
  • 更小的栈空间允许创建大量的实例

Golang中的Goroutine的特性:
Golang内部有三个对象:p对象(process)代表上下文(或者可以认为是cpu),M(work thread)代表工作线程,G对象(goroutine)

正常情况下一个cpu对象启一个工作线程对象,线程去检查并执行goroutine对象。碰到goroutine对象阻塞的时候,会启动一个新的工作线程,以充分利用cpu资源,所以有时候线程对象会比处理器对象多很多

G(Goroutine):我们所说的协程,为用户级的轻量级线程,每个Goroutine对象中的sched保存着其上下文信息

M(Machine):对内核级线程的封装,数量对应真实的CPU数(真正干活的对象)

P(Process):即为G和M的调度对象,用来调度G和M之间的关联关系,其数量可通过GOMAXPROCS()来设置,默认为核心数

在单核情况下,所有Goroutine运行在同一个线程(M0)中,每一个线程维护一个上下文(P),任何时刻,一个上下文中只有一个Goroutine,其他Goroutine在runqueue中等待

一个Goroutine运行完自己的时间片后,让出上下文,自己回到runqueue中

当正在运行的Go阻塞的时候(可以需要IO),会再创建一个线程(MI),P转到新的线程中去运行。

当M0返回时,它会尝试从其他线程中“偷”一个上下文过来,如果没有偷到,会把Goroutine放到Global runqueue中去,上下文会定期检查Global runqueue。

Golang是为并发而生的语言,Go语言是为数不多的崽语言层面实现并发的语言;也正是Go语言的并发特性,吸引了全球无数的开发者。

Golang的CSP并发模型,是通过Goroutine和Channel来实现的。

Goroutine是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的“线程”类似,可以理解为“线程”。Channel是Go语言中各个并发结构体(Goroutine)之间的通信机制,通常Channel,是各个Goroutine之间通信的“管道”,有点类似于Linux的信道。

通信机制channel也很方便,传数据用channel<-data,取数据用<-channel。

在通信过程中,传数据channel<-data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。

而且不管传还是取,必阻塞,知道另外的goroutine传或者取为止。

Golang中常用的并发模型

Golang常用的并发模型有三种:

  • 通过channel通知实现并发控制

无缓冲的通道指的是通道大小为0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送goroutine和接收goroutine同时准备好,才可以完成发送和接收操作。

从上面无缓冲的通道定义来看,发送goroutine和接收goroutine必须是同步的,同时准备后,如果没有同时准备好的话,先执行的操作就会阻塞等待,直到另一个相对应的操作准备好为止。这种无缓冲的通道我们也称之为同步通道。

func main() {
	ch := make(chan struct{})
	go func() {
		fmt.Println("start working")
		time.Sleep(time.Second * 1)
		ch <- struct{}{}
	}()
	
	<- ch
	
	fmt.Println("finished")
}

当主goroutine运行到<-ch接收channel的值的时候,如果该channel中没有数据,就会一直阻塞等待,直到有值。这样就可以简单实现并发控制

  • 通过sync包中的waitgroup实现并发控制

Goroutine是异步执行的,有的时候为了防止在结束main函数的时候结束掉Goroutine,所以需要同步等待,这个时候就需要用WaitGroup了,在sync包中,提供了WaitGroup,它会等待它收集的所有goroutine任务全部完成。在WaitGroup里主要有三个方法:
– Add,可以添加或减少Goroutine的数量
– Done,相当于Add(-1)
– Wait,执行后会阻塞主线程,直到WaitGroup里的值减至0

在主goroutine中Add(delta int)索要等待goroutine的数量,在每一个goroutine完成后Done()表示一个goroutine已经完成,当所有的goroutine都完成后,在主goroutine中WaitGroup返回。

func main() {
	var wg sync.WaitGroup
	var urls[] = []string{
		"http://www.golang.org/",
		"http://www.google.com/",
	}
	for _, url := range urls {
		wg.Add(1)
		go func(url string) {
			defer wg.Done()
			http.Get(url)
		}(url)
	}
	wg.Wait()
]

在Golang官网中对于WaitGroup介绍是A WaitGroup must not be copied after first use,在WaitGroup第一次使用后,不能被拷贝
应用示例:

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(wg sync.WaitGroup,i int) {
			fmt.Printf("i:%d", i)
			wg.Done()
		}(wg, i)
	}
	wg.Wait()
	fmt.Println("exit")
}

运行结果:

i:4i:1i:0i:2i:3fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc0000120a8)
        D:/Go/src/runtime/sema.go:56 +0x49
sync.(*WaitGroup).Wait(0xc0000120a0)
        D:/Go/src/sync/waitgroup.go:130 +0x6b
main.main()
        C:/Users/cai97/Desktop/golang/test/main.go:17 +0xc2
exit status 2

它提示所有goroutine都已经睡眠了,出现了死锁。这是因为wg给拷贝传递到了goroutine中,导致只有Add操作,其实Done操作是在wg的副本中执行的。
因此Wait就死锁了。

这是一个修改方式:将匿名函数中wg的传入类型改为*sync。WaitGroup,这样就能引用到正确的WaitGroup了。这是第二个修改方式,将匿名函数中的wg的传入参数去掉,因为Go支持闭包类型,在匿名函数中可以直接使用外部的wg变量

  • 在Go1.7以后引进的强大的Context上下文,实现并发控制

通常在一些简单场景下使用channel和WaitGroup已经足够了,但是当面临一些复杂多变的网络并发场景下channel和WaitGroup显得有些力不从心了。比如一个网络请求Request,每个request都需要开启一个goroutine做一些事情,这些goroutine有可能会开启其他goroutine,比如数据库和RPC服务。所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文非常贴切,他就是goroutine的上下文。它包括一个程序的运行环境、现场和快照等。每个程序要运行时,都需要知道当前程序的运行状态,通常Go讲这些封装在一个Context里,再将它传给要执行的goroutine。

context包主要使用来处理多个goroutine之间共享数据,及多个goroutine的管理。

context包的核心是struct Context,接口声明如下:

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this `Context` is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this Context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}

Done()返回一个只能接受数据的channel类型,当该context关闭或者超时时间到了的时候,该channel就会有一个取消信号

Err()在Done()之后,返回context取消的原因。

Deadline()设置该context cancel的时间点

Value()方法允许context对象携带request作用域的数据,该数据必须是线程安全的。

context对象是线程安全的,你可以把一个context对象传递给任意个数的goroutine,对它执行取消操作时,所有goroutine都会接收到取消信号。

一个context不能拥有cancel方法,同时我们也只能Done channel接收数据。其中的原因是一致的:接收取消信号的函数和发送信号的函数通常不是一个。典型的场景是:父操作为子操作启动goroutine,子操作也就不能取消父操作。

JSON标准库对nil slice和空slice的处理是一致的吗?

首先JSON标准库对nil slice和空slice的处理是不一样的
通常错误的用法,会报数组越界的错误,因为只是声明了slice,却没有给实例化的对象。

var slice []int
slice[1] = 0

此时slice的值是nil,这种情况可以用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值
empty slice是指slice不为nil,但是slice没有值,slice的底层的空间是空的,此时的定义如下:

slcie := make([]int, 0)
slice := []int{}

当我们查询或者处理一个空的列表的时候,这非常有用,它会告诉我们返回的是一个列表,但是列表内没有任何值
总之,nil slice和empty slice是不同的东西,需要我们加以区分的

协程,线程,进程的区别
  • 进程
    进程是具有一定独立功能的程序,关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位、每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
  • 线程
    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计算器,一组寄存器和栈),但是他可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
  • 协程
    协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
互斥锁,读写锁,死锁问题是怎么解决
  • 互斥锁
    互斥锁就是互斥变量mutex,用来锁住临界区的
    条件锁就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行;读写锁,也类似,用于缓冲区等临界资源能互斥访问的。
  • 读写锁
    通常有些公共数据修改的机会很少,但其读的机会很多。并且在读的过程中会伴随着查找,给这种代码加锁会降低我们的程序效率。读写锁可以解决这个问题
    注意:写独占,读共享,写锁优先级高
  • 死锁
    一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,这叫做死锁。另外一种情况是:若线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图会得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和线程B永远处于挂起状态了。

死锁产生的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

a.预防死锁
可以把资源一次性分配:(破坏请求和保持条件)
然后剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏坏路等待条件)

b.避免死锁
预防死锁的几种策略,会严重损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态的申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。

c.检测死锁
首先为每个进程和每个资源制定一个唯一的号码,然后建立资源分配表和进程等待表。

d.解除死锁
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来

e.剥夺资源
从其它进程剥夺足够数量的资源给死锁进程,以解除死锁的状态。

f.撤销资源
可以直接撤销死锁进程或撤销代价最小的进程,直至有足够的资源可用,死锁状态:消除为止,所谓代价是指优先级、运行代价、进程的重要性和价值等。

Golang的内存模型,为什么小对象多了会造成gc压力

通常小对象过多会导致GC三色法消耗过多的GPU。优化思路是,减少对象分配

Data Race问题怎么解决?能不能不加锁解决这个问题?

同步访问共享数据是处理数据竞争的一种有效的方法,golang在1.1之后引入了竞争监测机制,可以使用go run -race 或者go build -race来进行静态检测。其在内部的实现是,开启多个携程执行同一个命令,并且记录下每个变量的状态。
竞争检测器基于c/c++的ThreadSanitizer运行时库,该库在Google内部代码基地和Chromium找到许多错误。这个技术在2012年九月集成到Go中,从那时开始,它已经在标准库中检测到42个竞争条件。现在,它已经是我们持续构建过程的一部分,当竞争条件出现时,它会继续捕捉到这些错误。
竞争检测器已经完全集成到Go工具链中,仅仅添加-race标志到命令行就使用了检测器。

$ go test -race mypkg    // 测试包
$ go run -race mysrc.go  // 编译和运行程序
$ go build -race mycmd   // 构建程序
$ go install -race mypkg // 安装程序

要想解决数据竞争的问题可以使用互斥锁sync.Mutex,解决数据竞争(Data race),也可以使用管道解决,使用管道的效率要比互斥锁高。

什么是channel,为什么它可以做到线程安全

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值