5. 并发的学习

5.1 并发基础

5.1.1 并发和并行

5.1.2 goroutine

1) go的执行是非阻塞的,不会等待。

2) go后面函数的返回值会被忽略

3)调度器不能保证多个 goroutine 的执行次序

4) 没有父子goroutine的概念,所有的goroutine是平等地被调用和执行的。

5) Go程序在执行时会单独为 main 函数创建一个 gouroutine , 遇到其他go关键字时再去创建其他的goroutine

6) Go 没有暴露goroutine id 给用户,所以不能在一个goroutine里面显式地操作另一个goroutine,不过 runtime包提供了一些函数访问和设置goroutine的相关信息

1. func GOMAXPROCS

func GOMAXPROCS(n int)  int 用来设置或查询可以并发执行的goroutine数目,n 大于 1表示设置gomaxprocs值,否则表示查询gomaxprocs值
 

import "runtime"

func main() {

    // 获取当前GOMAXPROCS值

    println("GOMAXPROCS=", runtime.GOMAXPROCS(0))

    // 设置GOMAXPROCS值

    runtime.GOMAXPROCS(2)

    // 获取当前的GOMAXPROCS值

    println("GOMAXPROCS=", runtime.GOMAXPROCS(0))

}

2. func Goexit

用于结束当前goroutine

3. func Gosched

放弃当前调度执行机会

5.1.3 chan

Go的哲学,不要通过共享内存来通信 ,而是通过通信来共享内存。chan通道是Go通过通信来共享内存的载体。

通道是有类型的: chan dataType

//创建一个无缓冲的通道,通道存放元素的类型为dataType

make(chan datatype)

//创建一个有10个缓存的通道,通道存放元素的类型为datatype

通道氛围无缓冲的通道和有缓冲的通道,Go提供内置函数len和cap,

无缓冲的通过的len和cap都是0,

有缓冲的通过 len 代表没有背读取的元素数,cap代表整个通道的容量

无缓存的通道既可以用于通信,也可以用于两个goroutine的同步,有缓冲的通道主要用于通信

package main

import(
	"runtime"
)

func main() {
	c := make(chan struct {})
	go func(i chan struct {}) {
		sum := 0
		for i := 0; i < 10000; i++ {
			sum += i
		}
		println(sum)
		c <- struct {} {}
	}(c)
	// NumGoroutine可以返回当前程序的goroutine数目
	println("NumGoroutine =", runtime.NumGoroutine())

	//读通道c,通过通道就进行同步等待
	<-c
}

缓冲通道和消息队列类似,有削峰和增大吞吐量的功能,例如:

package main

import (
	"runtime"
)
func main() {
	c  := make(chan struct {})
	ci := make(chan int, 1000000)
    go func(i chan struct {}, j chan int) {
		for i := 0; i < 1000000; i++ {
			ci <- i
		}
		close(ci)
		//写通道
		c <- struct {} {}
	}(c, ci)

	// NumGoroutine 可以返回当前程序的goroutine数目
	println("NumGoroutine =", runtime.NumGoroutine())

	//读通道c, 通过通道进行同步等待
	<-c

	//此时ci通道已经关闭,匿名函数启动的goroutine已退出
	println("NumGoroutine =", runtime.NumGoroutine())

	//但通道ci还可以继续读取
	for v := range ci {
		println(v)
	}
}

panic

1) 向已经关闭的通道写数据会导致panic

最佳实践是由写入者关闭通道,能最大程度地避免向已经关闭的通道写数据而导致的panic

2)重复关闭的通道会导致panic

阻塞

1) 向未初始化的通道写数据或者读数据都会导致当前goroutine的永久阻塞

2)向缓存区已满的通道写入数据会导致 goroutine 阻塞。

3) 通道中没有数据,读取该通道会导致 goroutine 阻塞

非阻塞

1) 读取已经关闭的通道不会引发阻塞,而是立即返回通道元素类型的零值,可以使用 comma , ok 语法判断通道是否关闭

2)向有缓冲且没有满的通道读/写不会引发阻塞

5.1.4 WaitGroup

type WaitGroup struct {

    // contains filtered or unexported fields

}

 

// 添加等待信号

func (wg *WaitGroup) Add (delta int)

// 释放等待信号

func (wg *WaitGroup) Done()

//等待

func (wg *WaitGroup) Wait()

WaitGroup用来等待多个goroutine完成,main goroutine调用Add设置需要等待goroutine的数目,每一个goroutine结束时调用Done(),Wait()被main用来等待所有的goroutine完成

下面程序演示如何使用sync.WaitGroup完成多个goroutine之间的协同工作。

package main

import (
	"net/http"
	"sync"
)

var wg sync.WaitGroup
var urls = []string {
	"https://www.baidu.com/",
	"https://www.163.com/",
	"https://www.qq.com",
}

func main() {
	for _, url := range urls {

		// 每一个url启动一个goroutine,同时给wg加1
		wg.Add(1)

		// Launch a goroutine to fetch the URL.
		go func(url string) {
			//当前goroutine结束后给wg计数减1, wg.Done()等价于wg.Add(-1)
			// defer wq.Add(-1)
			defer wg.Done()

			//发送HTTP get请求并打印HTTP返回码
			resp, err := http.Get(url)
			if err == nil {
				println(resp.Status)
			}
		}(url)
	}
	//等待所有请求结束
	wg.Wait()
}

5.1.5 select

select 是类UNIX系统提供的一个多路复用系统API,Go语言借用多路复用的概念,提供了select关键字,用于多路监听多个通道。

当监听的通道没有状态是可读可写的,select是阻塞的;

只要监听的通道中有一个状态是可读或可写的,则 select 就不会阻塞,而是进入处理就绪通道的分支流程。

如果监听的通道有多个可读或可写的状态,则 select 随机选取一个处理。例如:

package main

func main() {
	ch := make(chan int, 1)
	go func(chan int) {
		for {
			select {
				//0或1的写入是随机的
			case ch <- 0:
			case ch <- 1:
			}
		}
	}(ch)
	for i := 0; i < 10; i++ {
		println(<-ch)
	}
}

5.1.6 扇入(Fan in) 和 扇出 (Fan out)

扇入指将多路通道聚合到一条通道中处理,Go语言最简单的扇入就是使用select聚合多条通道服务;

扇出指将一条通道伐散到多条通道中处理,Go语言使用 go 关键字启动多个 goroutine 并发处理。

5.1.7 通知退出机制

读取已经关闭的通道不会引发阻塞,也不会导致panic,而是立即返回该通道存储类型的零值。

关闭select监听的某个通道能使select立刻感知这种通知,然后进行处理,这就是所谓的退出通知机制

5.2 并发范式

5.2.1 生成器

159

5.2.2 管道

通道可以分为两个方向,一个是读,一个是写

5.2.3 每个请求一个goroutine

5.2.4 固定 worker 工作池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!很高兴回答你关于Java并发编程的问题。请问你想知道什么方面的内容呢?我可以分享一些学习笔记和建议给你。 1. 并发编程基础:了解并发编程的基本概念,如线程、进程、锁、同步等。学习Java中的并发编程模型以及相关的API,如Thread、Runnable、Lock、Condition等。 2. 线程安全性:学习如何保证多线程环境下的数据安全性,了解共享资源的问题以及如何使用同步机制来防止数据竞争和并发问题。 3. 线程间的通信:掌握线程间的通信方式,如使用wait/notify机制、Lock/Condition等来实现线程的协调与通信。 4. 并发容器:学习并发容器的使用,如ConcurrentHashMap、ConcurrentLinkedQueue等。了解它们的实现原理以及在多线程环境下的性能特点。 5. 并发工具类:熟悉Java提供的并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等,它们可以帮助你更方便地实现线程间的协作。 6. 并发编程模式:学习一些常见的并发编程模式,如生产者-消费者模式、读者-写者模式、线程池模式等。了解这些模式的应用场景和实现方式。 7. 性能优化与调试:学习如何分析和调试多线程程序的性能问题,了解一些性能优化的技巧和工具,如使用线程池、减少锁竞争、避免死锁等。 这些只是一些基本的学习笔记和建议,Java并发编程是一个庞大而复杂的领域,需要不断的实践和深入学习才能掌握。希望对你有所帮助!如果你有更具体的问题,欢迎继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值