channel相关
1.数据类型
属于引用类型
2.声名方式
var 变量 chan 元素类型 || make(chan 元素类型, [缓冲大小])
3.常用操作
通道有发送(send)、接收(receive)和关闭(close)三种操作。操作符号(<-)
①同步的channel
a.必须先存在接收方,然后才能存在发送方,这是个阻塞式的channel(同步)。
func main() {
// 创建一个无缓冲区通道
ch := make(chan string)
// 接收通道的值
go receive(ch)
// 发送的数据
ch <- "light"
fmt.Println("发送成功")
// 关闭通道
close(ch)
fmt.Println("通道已关闭")
}
// 接收通道的值
func receive(ch chan string) {
// 接收的数据
result := <-ch
fmt.Println("接收的数据:" + result)
}
结果右图所示:
b.如果先存在发送方,可以通过编译,但会报运行时异常---死锁(可以理解为生产者和消费者的关系,一定要先有生产者,才能有消费者,不然就单向阻塞)。
func main() {
// 创建一个无缓冲区通道
ch := make(chan string)
// 发送的数据
ch <- "light"
fmt.Println("发送成功")
// 关闭通道
close(ch)
fmt.Println("通道已关闭")
// 接收通道的值
go receive(ch)
}
// 接收通道的值
func receive(ch chan string) {
// 接收的数据
result := <-ch
fmt.Println("接收的数据:" + result)
}
结果右图所示:
②异步的channel
a.创建携带缓冲区的通道,且发送方数量不大于缓冲区数量(通道容量)。
func main() {
// 创建一个容量为1的有缓冲区通道
ch := make(chan string, 1)
// 发送的数据
ch <- "light"
fmt.Println("发送成功")
// 接收通道的值
go receive(ch)
// 关闭通道
close(ch)
time.Sleep(1)
fmt.Println("通道已关闭")
}
// 接收通道的值
func receive(ch chan string) {
// 接收的数据
result := <-ch
fmt.Println("接收的数据:" + result)
}
结果右图所示:
b.若发送方数量大于缓冲区数量(通道容量),编译会通过,但运行会死锁,因为当发送方数量超过缓冲区数量的情况下,就不再属于异步执行了,而是同步阻塞,要等待缓冲区里的任务释放掉,才会执行后面的任务。
func main() {
// 创建一个容量为1的有缓冲区通道
ch := make(chan string, 1)
// 发送的数据
ch <- "light"
ch <- "1"
fmt.Println("发送成功")
// 接收通道的值
go receive(ch)
// 关闭通道
close(ch)
time.Sleep(1)
fmt.Println("通道已关闭")
}
// 接收通道的值
func receive(ch chan string) {
// 接收的数据
result := <-ch
fmt.Println("接收的数据:" + result)
}
结果右图所示:
③关闭通道
a.通道的特性:可以被自动回收,不需要每次都手动关闭,但如果手动关闭,必须注意以下几点:
| channel | nil | 已关闭的 | 非空 | 空的 |
|---|---|---|---|---|
| 关闭情况 | 报panic | 报panic | 关闭成功,读取完数据后返回0值 | 关闭成功,返回0值 |
4.channel存在的意义
channel存在的意义是为了解决在并发编程中数据交换的问题。一般在并发中实现数据交换常用的方式是使用共享内存,但这样有个缺陷就是内存中的数据不一定会准确复制到各个线程中,存在资源抢占的问题(即不能保证CAP原则中的C---数据一致性)。此种情况下,解决方案势必是使用互斥量对内存加锁来保证线程安全,这样就会存在性能上的问题。而channel就是天然可以解决这个问题,channel提倡通过通信来实现共享内存,而不是通过共享内存实现通信。channel本质上就是队列,遵循先进先出原则。各个goroutine(线程)之间都通过它来实现共享内存,可以发送/接收数据在同一个channel中。
5.综合演练
便于大家更好的理解和使用channel以及goroutine,自己做了一个简易版的ATM自动取款机,代码如下:
var group sync.WaitGroup
// Atm 取款机
type Atm struct {
// users 用户信息
users []User
// balance 取款机余额
balance int
}
// User 用户
type User struct {
// Name 用户名称
Name string
// Money 取出的金额
Money int
}
// TestAtm 简易版的ATM自动取款机
func TestAtm(t *testing.T) {
atmChain := make(chan *Atm, 1)
atmChain <- &Atm{balance: 100}
fmt.Println("==========取款机已经准备就绪==========")
userChain := make(chan *User, 4)
for i := 0; i < 4; i++ {
group.Add(1)
id := i + 1
go func() {
defer group.Done()
user := User{Name: "用户_" + strconv.Itoa(id), Money: 20 * id}
userChain <- &user
}()
}
group.Wait()
close(userChain)
fmt.Println("==========4个用户正在进行取款操作==========")
group.Add(1)
go WithdrawMoney(&atmChain, &userChain)
fmt.Println("==========全部用户已经完成取款操作==========")
group.Wait()
// 打印取款机中的详情
for atm := range atmChain {
fmt.Printf("已成功取款的信息:%v\n", atm.users)
fmt.Printf("取款机的余额:%d\n", atm.balance)
}
}
// WithdrawMoney 取款操作
func WithdrawMoney(atm *chan *Atm, user *chan *User) {
defer group.Done()
ATM, ok := <-*atm
if ok {
balance := ATM.balance
users := make([]User, 0)
for u := range *user {
balance -= u.Money
if 0 <= balance {
users = append(users, *u)
} else {
balance += u.Money
break
}
}
if 0 < len(users) {
*atm <- &Atm{users: users, balance: balance}
close(*atm)
}
}
}
结果右图所示: