Datawhale go语言学习 12并发编程

2023-10-29

12.并发编程

12.1 并发与并行

Erlang 之父 Joe Armstrong曾经以下图解释并发与并行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u21Pi0Ef-1608215197628)(./img/cor.jpg)]

并发在图中的解释是两队人排队接咖啡,两队切换。

并行是两个咖啡机,两队人同时接咖啡。

“Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” — Rob Pike

并发使并行变得容易,并发提供了一种构造解决方案的方法,并行一般伴随这多核。并发一般伴随这CPU切换轮训。

12.2 为什么需要并发?

原因有很多,其中比较重要的原因如下:

  1. 不阻塞等待其他任务的执行,从而浪费时间,影响系统性能。
  2. 并行可以使系统变得简单些,将复杂的大任务切换成许多小任务执行,单独测试。

在开发中,经常会遇到为什么某些进程通常会相互等待呢?为什么有些运行慢,有些快呢?

通常受限来源于进程I/OCPU

  • 进程I/O限制

如:等待网络或磁盘访问

  • CPU限制

如:大量计算

12.3 Go并发原语

12.3.1 协程Goroutines

每个go程序至少都有一个Goroutine:主Goroutine(在运行进程时自动创建)。以及程序中其他Goroutine
例如:下面程序创建了main的Goroutine及匿名的Goroutine。

func main() {
	go func() {
		fmt.Println("you forgot me !")
	}()
}

在go中有个package是sync,里面包含了:

WaitGroup、Mutex、Cond、Once、Pool,下面依次介绍。

1.WaitGroup

假设主线程要等待其余的goroutine都运行完毕,不得不在末尾添加time.Sleep(),但是这样会引发两个问题:

  • 等待多长时间?
  • 时间太长,影响性能?

在go的sync库中的WaitGroup可以帮助我们完成此项工作,Add(n)把计数器设置为n,Done()会将计数器每次减1,Wait()函数会阻塞代码运行,直到计数器减0。

等待多个goroutine完成,可以使用一个等待组。 例如:

// 这是我们将在每个goroutine中运行的函数。
// 注意,等待组必须通过指针传递给函数。
func worker(id int, wg *sync.WaitGroup) {

	defer wg.Done()

	fmt.Printf("Worker %d starting\n", id)

	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {

	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	wg.Wait()
}

这里首先把wg 计数设置为1, 每个for循环运行完毕都把计数器减一,主函数中使用Wait() 一直阻塞,直到wg为1——也就是所有的5个for循环都运行完毕。

使用注意点:

  • 计数器不能为负值
  • WaitGroup对象不是引用类型

2.Once

sync.Once可以控制函数只能被调用一次,不能多次重复调用。

例如:

var doOnce sync.Once

func main() {
	DoSomething()
	DoSomething()
}

func DoSomething() {
	doOnce.Do(func() {
		fmt.Println("Run once - first time, loading...")
	})
	fmt.Println("Run this every time")
}

输出:

Run once - first time, loading...
Run this every time
Run this every tim

3.互斥锁Mutex

互斥锁是并发程序对共享资源进行访问控制的主要手段,在go中的sync中提供了Mutex的支持。

例如:使用互斥锁解决多个Goroutine访问同一变量。

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
  c.mux.Lock()
  defer c.mux.Unlock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  c.v[key]++
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

在这个例子中,我们使用了sync.Mutex的Lock与Unlock方法。

在前面例子中我们使用了sync.Mutex,读操作与写操作都会被阻塞。其实读操作的时候我们是不需要进行阻塞的,因此sync中还有另一个锁:读写锁RWMutex,这是一个单写多读模型。

sync.RWMutex分为:读、写锁。在读锁占用下,会阻止写,但不会阻止读,多个goroutine可以同时获取读锁,调用RLock()函数即可,RUnlock()函数释放。写锁会阻止任何goroutine进来,整个锁被当前goroutine,此时等价于Mutex,写锁调用Lock启用,通过UnLock()释放。

例如: 我们对上述例子进行改写,读的时候用读锁,写的时候用写锁。

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v     map[string]int
	rwmux sync.RWMutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
	// 写操作使用写锁
	c.rwmux.Lock()
	defer c.rwmux.Unlock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	c.v[key]++
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
  // 读的时候加读锁
	c.rwmux.RLock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.rwmux.RUnlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)

	for i := 0; i < 10; i++ {
		fmt.Println(c.Value("somekey"))
	}
}

4.条件变量Cond

sync.Cond是条件变量,它可以让一系列的 Goroutine 都在满足特定条件时被唤醒。

条件变量通常与互斥锁一起使用,条件变量可以在共享资源的状态变化时通知相关协程。
经常使用的函数如下:

  • NewCond

创建一个Cond的条件变量。

func NewCond(l Locker) *Cond
  • Broadcast

广播通知,调用时可以加锁,也可以不加。

func (c *Cond) Broadcast()
  • Signal

单播通知,只唤醒一个等待c的goroutine。

func (c *Cond) Signal()
  • Wait
    等待通知, Wait()会自动释放c.L,并挂起调用者的goroutine。之后恢复执行,Wait()会在返回时对c.L加锁。

除非被Signal或者Broadcast唤醒,否则Wait()不会返回。

func (c *Cond) Wait()

例如:使用WaitGroup等待两个Goroutine完成,
Goroutine1与Goroutine2进入Wait状态,main函数在2s后改变共享数据状态,调用Broadcast函数,此时c.Wait从中恢复并判断条件变量是否已经满足,满足后消费条件,解锁,wg.Done()。

5.原子操作

原子操作即是进行过程中不能被中断的操作。针对某个值的原子操作在被进行的过程中,CPU绝不会再去进行其他的针对该值的操作。
为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。

在sync/atomic 中,提供了一些原子操作,包括加法(Add)、比较并交换(Compare And Swap,简称 CAS)、加载(Load)、存储(Store)和交换(Swap)。

1.加法操作
提供了32/64位有符号与无符号加减操作

var i int64
atomic.AddInt64(&i, 1)
fmt.Println("i = i + 1 =", i)
atomic.AddInt64(&i, -1)
fmt.Println("i = i - 1 =", i

2.比较并交换

CAS: Compare And Swap

如果addr和old相同,就用new代替addr。

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

例如:

var a int32 = 1
var b int32 = 2
var c int32 = 3
ok := atomic.CompareAndSwapInt32(&a, a, b)
fmt.Printf("ok = %v, a = %v, b = %v\n", ok, a, b)
ok = atomic.CompareAndSwapInt32(&a, c, b)
fmt.Printf("ok = %v, a = %v, b = %v, c=%v\n", ok, a, b, c)

输出:

ok = true, a = 2, b = 2
ok = false, a = 2, b = 2, c = 3

3.交换

不管旧值与新值是否相等,都会通过新值替换旧值,返回的值是旧值。

func SwapInt32(addr *int32, new int32) (old int32)

例如:

var x int32 = 1
var y int32 = 2
old := atomic.SwapInt32(&x, y)
fmt.Println(x, old)

输出:2 1

3.加载

当读取该指针指向的值时,CPU 不会执行任何其它针对此值的读写操作

func LoadInt32(addr *int32) (val int32)

例如:

var x1 int32 = 1
y1 := atomic.LoadInt32(&x)
fmt.Println("x1, y1:", x1, y1)

4.存储

加载逆向操作。

例如:

var xx int32 = 1
var yy int32 = 2
atomic.StoreInt32(&yy, atomic.LoadInt32(&xx))
fmt.Println(xx, yy)

5.原子类型

sync/atomic中添加了一个新的类型Value。
例如:

v := atomic.Value{}
v.Store(1)
fmt.Println(v.Load())

6.临时对象池Pool

sync.Pool 可以作为临时对象的保存和复用的集合

P是Goroutine中的重要组成之一,例如:P实际上在操作时会为它的每一个goroutine相关的P生成一个本地P。 本地池没有,则会从其它的 P 本地池获取,或者全局P取。

sync.Pool对于需要重复分配、回收内存的地方,sync.Pool 是一个很好的选择。减少GC负担,如果Pool中有对象,下次直接取,不断服用对象内存,减轻 GC 的压力,提升系统的性能。

例如:

var pool *sync.Pool

type Foo struct {
	Name string
}

func Init() {
	pool = &sync.Pool{
		New: func() interface{} {
			return new(Foo)
		},
	}
}

func main() {
	fmt.Println("Init p")
	Init()

	p := pool.Get().(*Foo)
	fmt.Println("第一次取:", p)
	p.Name = "bob"
	pool.Put(p)

	fmt.Println("池子有对象了,调用获取", pool.Get().(*Foo))
	fmt.Println("池子空了", pool.Get().(*Foo))
}

输出:

Init p
第一次取: &{}
池子有对象了,调用获取 &{bob}
池子空了 &{}
12.3.2 通道Channel

1.Channel

这里引入一下CSP模型,CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,由 Tony Hoare 于 1977 年提出。

简单来说是实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 Channel。Goroutine对应并发实体。

1) 使用

Channel的使用需要通过make创建,例如:

unBufferChan := make(chan int) 
bufferChan := make(chan int, x) 

上述创建了无缓冲的Channel与有缓冲的Channel,创建完成之后,需要进行读写操作,如下:

ch := make(chan int, 1)

// 读操作
x <- ch

// 写操作
ch <- x

最终要正确关闭,只需要调用close即可。

// 关闭
close(ch)

当channel关闭后会引发下面相关问题:

  • 重复关闭Channel 会 panic
  • 向关闭的Channel发数据 会 Panic,读关闭的Channel不会Panic,但读取的是默认值

对于最后一点读操作默认值怎么区分呢?例如:Channel本身的值是默认值又或者是读到的是关闭后的默认值,可以通过下面进行区分:

val, ok := <-ch
if ok == false {
    // channel closed
}

2) Channel分类

  • 无缓冲的Channel

发送与接受同时进行。如果没有Goroutine读取Channel(<-Channel),发送者(Channel<-x)会一直阻塞。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QHinBuyt-1608215197629)(./img/unbufferedchannel.png)]

  • 有缓冲的Channel

发送与接受并非同时进行。当队列为空,接受者阻塞;队列满,发送者阻塞。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtUOvFWq-1608215197630)(./img/bufferedchannel.png)]

2.Select

  • 每个case 都必须是一个通信
  • 所有channel表达式都会被求值
  • 如果没有default语句,select将阻塞,直到某个通信可以运行
  • 如果多个case都可以运行,select会随机选择一个执行

1) 随机选择

select特性之一:随机选择,下面会随机打印不同的case结果。
例如:

ch := make(chan int, 1)
ch <- 1
select {
case <-ch:
	fmt.Println("ch 1")
case <-ch:
	fmt.Println("ch 2")
default:
	fmt.Println("ch default")
}

假设chan中没有值,有可能引发死锁。

例如: 下面执行后会引发死锁。

ch := make(chan int, 1)
select {
case <-ch:
	fmt.Println("ch 1")
case <-ch:
	fmt.Println("ch 2")
}

此时可以加上default即可解决。

default:
	fmt.Println("ch default")

另外,还可以添加超时。

timeout := make(chan bool, 1)
go func() {
	time.Sleep(2 * time.Second)
	timeout <- true
}()
ch := make(chan int, 1)

select {
case <-ch:
	fmt.Println("ch 1")
case <-timeout:
	fmt.Println("timeout 1")
case <-time.After(time.Second * 1):
	fmt.Println("timeout 2")
}

2) 检查chan

select+defaul方式来确保channel是否满

ch := make(chan int, 1)
ch <- 1
select {
case ch <- 1:
	fmt.Println("channel value is ", <-ch)
	fmt.Println("channel value is ", <-ch)
default:
	fmt.Println("channel blocking")
}

如果要调整channel大小,可以在make的时候改变size,这样就可以在case中往channel继续写数据。

3) 选择循环

当多个channel需要读取数据的时候,就必须使用 for+select

例如:下面例子需要从两个channel中读取数据,当从channel1中数据读取完毕后,会像signal channel中输入stop,此时终止for+select。

func f1(c chan int, s chan string) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		c <- i
	}
	s <- "stop"
}

func f2(c chan int, s chan string) {
	for i := 20; i >= 0; i-- {
		time.Sleep(time.Second)
		c <- i
	}
	s <- "stop"
}

func main() {
	c1 := make(chan int)
	c2 := make(chan int)
	signal := make(chan string, 10)

	go f1(c1, signal)
	go f2(c2, signal)
LOOP:
	for {
		select {
		case data := <-c1:
			fmt.Println("c1 data is ", data)
		case data := <-c2:
			fmt.Println("c2 data is ", data)
		case data := <-signal:
			fmt.Println("signal is ", data)
			break LOOP
		}
	}
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Datawhale go语言学习 12并发编程 的相关文章

  • 【Python编程】Python实现生产者消费者模式(多线程爬虫)

    Python实现生产者消费者模式 多线程爬虫 1 多组件的Pipeline技术架构 复杂的事情一般都不会一下子做完 而是会分很多中间步骤一步步完成 2 生产者消费者爬虫的架构 3 多线程数据通信的 queue Queue queue Que
  • Order By 和 Group By的适用场景

    Order By和Group By看起来很像 但实际上使用用途和适用场景实际上很大的不同 Order By Order By是排序根据所给字段进行排序 select from s order by sname desc sage desc
  • tensorflow 1.13.1 安装采坑

    环境 win10 x64位 cuda10 1 cudnn 7 5 vs2013 vs2015 distributed GTX1060 按照网上的教程安装 如下面博客 https blog csdn net huanyingzhizai ar
  • Android WebView打不开网页

    今天给公司前端同事打了个webview测试包 发现页面打不开 有两个原因 1 测试webview包没有配置好 特别是缓存没配置的同事用到h5缓存就打不开了 正确配置如下 设置支持javascript webView getSettings
  • Python第三方拓展包(安装下载方法)

    找到安装的位置 第一种方法 Anaconda集成环境 Anaconda集成环境 优点太多了 百度一大堆介绍 Anaconda的优点总结起来就八个字 省时省心 分析利器 省时省心 Anaconda通过管理工具包 开发环境 Python版本 大
  • JS点击计数存储案例

    项目功能要求 点击页面红色块默认不计数 点击开始按钮后再点击红色块则开始计数 点击结束按钮后再点击红色块则不计数 关闭页面并再次打开页面 界面会显示上次关闭页面时的计数值 代码设计 本项目结合了前端页面的设计和JS的使用 具体代码如下
  • 编译器工具 Flex Bison for Windows 简单入门例子

    最近从事一个系统仿真软件的开发 里面定义了自己的描述性语言MSL MSL语言经FlexBison转换成C语言 然后用C编译器来编译并计算仿真 现在领域驱动开发比较热门 有机会定义自己的语言对程序员来说是比较有成就感的 免不了要去学习编译原理
  • 笔记本电脑电池怎么拆_笔记本电脑电池的容量单位是Wh,手机是mAh,二者怎么比较?...

    Hello大家好 我是兼容机之家的小牛 目前很多轻薄本都搭载了全功能的Type C接口 大都支持PD充电 所以有的机友出门的时候就不会带上笔记本的 板砖 适配器了 往往会选择购买一个手机 电脑通用的PD充电器 大家平时都知道自己的手机电量是
  • steam创客教室

    人生不同阶段都有不同的使命 在学生阶段 学习掌握知识 为他们以后的人生获得成就的能力 就是他们这个阶段最重要的使命 格物斯坦小坦克的学生生涯也是这样做的 为了这个使命 他们必须要学习忍耐 学会放弃 学会付出 这不仅仅是学习的需要 也是人生的
  • 用 Python 爬取股票实时数据

    今天我们一起来学习一个 Python 爬虫实战案例 我们的目标网站就是东方财富网 废话不多说 开搞 网站分析 东方财富网地址如下 http quote eastmoney com center gridlist html hs a boar
  • 解读Android LOG机制的实现:(5)获取LOG的应用程序LogCat

    解读Android LOG机制的实现 5 获取LOG的应用程序LogCat 田海立 CSDN 2011 07 27 Android提供的LOG机制的实现贯穿了Java JNI 本地c c 实现以及LINUX内核驱动等Android的各个层次
  • 三步教你实现MyEclipse的debug远程调试

    MyEclipse远程调试程序是个神奇的东西 有时一个项目本地运行没问题可放到服务器上 同样的条件就是结果不一样 有时服务器上工程出点问题需要远程调测 于是就灰常想看一下程序在远程运行时候的状态 希望让程序在远程运行 还可以在本机打断点跟一
  • 执行力的关键在于中层,成败的关键在于细节

    编者注 这是一位国内著名企业总裁在一次工作会议上的讲话 文中说到完美的 细节 一定是完美执行力的结果 并且切中时弊的指出 执行力的关键在于中层 如何做到这一点 关键就是要建立一个保证中层执行力到位的强势文化 也许真正落实到位 不但首先会冲击
  • Nodejs

    一 是什么 中间件 Middleware 是介于应用系统和系统软件之间的一类软件 它使用系统软件所提供的基础服务 功能 衔接网络上应用系统的各个部分或不同的应用 能够达到资源共享 功能共享的目的 在NodeJS中 中间件主要是指封装http
  • 华为OD机试 - 阿里巴巴找黄金宝箱(IV)(Java)

    题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱子 每个箱子上面有一个数字 箱子排列成一个环 编号最大的箱子的下一个是编号为0的箱子 请输出每个箱了贴的数字之后的第一个比它大的数 如果不
  • java爱心效果代码来了

    爱心 代码来了 村上春树说 仪式是一件很重要的事 现实生活被琐事充斥 仪式感总是被人遗忘 凑合的晚饭 乱丢的衣物 杂乱无章的生活让原本平淡的生活过得一潭死水 找不到生活的乐趣 童话 小王子 里狐狸对小王子说 你最好在每天相同的时间来 比如你
  • scrapy提高爬取速度

    scrapy在单机跑大量数据的时候 在对settings文件不进行设置的时候 scrapy的爬取速度很慢 再加上多个页面层级解析 往往导致上万的数据可能爬取要半个小时之久 这还不包括插入数据到数据库的操作 下面是我在实验中测试并且验证爬取速
  • Docker 之桥接网络(一)

    参考 https docs docker com network bridge 在网络方面 桥接网络是一个链路层设备 它在网络段之间转发流量 网桥可以是运行在主机内核中的硬件设备或软件设备 就Docker而言 桥接网络使用软件桥 允许连接到
  • Windows下配置环境变量

    Windows下配置环境变量 在安装某些软件或者应用程序时 需要配置环境变量 以便在后续可以正常使用 环境变量相当于给系统或用户应用程序设置一些参数 具体起什么作用和对应的环境变量相关 比如path 是告诉系统 当要求系统运行一个程序而没有
  • React 18的新特新

    React 18 最新的特性 批处理文件 在同一个 函数中 调用多次 useState 也只是会更新一次 function App const count setCount useState 0 const flag setFlag use

随机推荐

  • 基于51单片机的水位液位温度监测鱼缸系统仿真原理图程序

    硬件设计 上一篇咱们讲了基于数码管显示的液位检测 本章讲一下基于LCD1602显示的液位检测 同时伴有温度测量 该系统可应用于鱼缸 水塔 水箱的检测 本系统液位检测方案是基于液位传感器 通过传感器嵌入液体反馈回电信号 通过电信号的大小可以反
  • 如何利用python将一个list中的数字提取,然后求和?

    coding UTF 8 import re name score list u 唐僧 100 u 猪八戒 60 u 孙悟空 95 u 沙和尚 80 1 将score list列表转换为以 为分隔符的字符串 name score list
  • 极限学习机ELM原理与实现

    极限学习机 ELM 是当前一类非常热门的机器学习算法 被用来训练单隐层前馈神经网络 SLFN 本篇博文尽量通俗易懂地对极限学习机的原理进行详细介绍 之后分析如何用python实现该算法并对代码进行解释 1 算法的原理 极限学习机 ELM 用
  • 【STM32】 JDY-31蓝牙模块

    目录 一 介绍 二 模块特点 三 产品应用范围 四 模块技术参数 五 模块接口说明 六 蓝牙模块接口电路图 七 蓝牙模块实物图 一 介绍 蓝牙模块是指集成蓝牙功能的芯片基本电路集合 用于无线网络通讯 本蓝牙模块就是为了只能无线数据传输而专门
  • 微服务 - 拆分微服务的问题和拆分方法

    概述 现在被谈论最多的就是微服务和中台系统 我个人的理解是微服务或者是中台好不好 主要看实际的业务场景 架构的变迁往往需要耗费很大的学习成本和时间成本 所以更改架构的时候要三思而后行 适合自己特别重要 由单体到多应用的演变 从我入职开始 公
  • 变压器哪些部位易造成渗油?

    变压器哪些部位易造成渗油 答 1 套管升高座电流互感器小绝缘子引出线的桩头处 所有套管引线桩头 法兰处 2 气体继电器及连接管道处 3 潜油泵接线盒 观察窗 连接法兰 连接螺丝紧固件 胶垫 4 冷却器散热管 5 全部连接通路碟阀 6 集中净
  • [前端] vue开发的项目用ajax发送请求携带cookie

    只需要在main js中写这三行代码即可 import axios from axios axios defaults withCredentials true 让ajax携带cookie Vue prototype axios axios
  • 初谈 ChatGPT

    引子 最近 小编发现互联网中的大 V 突然都在用 ChatGPT 做宣传 ChatGPT不会淘汰你 能驾驭ChatGPT的人会淘汰你 带领一小部分人先驾驭ChatGPT 确实 ChatGPT这个新生事物 如今被视为蒸汽机 电脑 iPhone
  • 众所周知,YouTube是个学习网站

    大数据文摘出品 来源 medium 编译 Hippo 国内有小破站 国外有YouTube 就像国内的b站一样 YouTube不仅是一个多媒体娱乐帝国 还是一个全面的学习平台 每月无需支付订阅费即可在YouTube上观看视频 所有供你观看和学
  • dev GridControl 按条件纵向合并单元格

    dev GridControl 按条件纵向合并单元格 gridView5 OptionsView AllowCellMerge true gridView5 CellMerge gridView5 CellMerge 自定义合并单元格监听事
  • npm安装报错 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree

    项目场景 安装npm报错npm ERR code ERESOLVE npm ERR ERESOLVE unable to resolve dependency tree 原因分析 ERESOLVE与npm 7 x的问题 7 x有些处理检测比
  • 黑马JVM总结(十五)

    1 GC 相关参数 2 GC 分析1 下面通过一个案例去读懂垃圾回收的一个过程 学会读懂垃圾回收的日志 XX UseSerialGC 是一个垃圾回收器 jdk8下默认的垃圾回收器不是它 改为UseSerialGC它的幸存区的比例才不会调整
  • Jenkins系列(五)----Jenkins自动发布spring-boot应用

    一 测试环境 三台服务器来测试 一台SVN服务器 一台Jenkins服务器 一台应用服务器 如下 Jenkins服务器 192 168 84 100 SVN服务器 192 168 84 105 应用服务器 192 168 84 101 二
  • 第二篇:Spring Boot整合JPA、事务处理及AOP的使用

    一 Spring Boot整合JPA的具体步骤 注 Spring Boot整合JPA 源码下载 注 springboot学习资料汇总 参考 spring data jpa的使用 参考 Spring Cloud微服务实战 作者 参考 方志朋博
  • SQLyog软件里无法插入中文(即由默认的latin1改成UTF8编码格式)

    问题详情 无法插入中文 解决办法 需要修改编码格式 由默认的latin1改为utf8 改成 成功
  • ZZULIOJ 2829: 闯关游戏 (DP)

    2829 闯关游戏http acm zzuli edu cn problem php id 2829 include
  • 计算机专业名称bios翻译,bios英文翻译 最全BIOS设置中英文对照表

    对于熟悉电脑的人来说 设置BIOS是一件非常简单的事情 但对于小白来说 别说设置BIOS了 看不看得懂这些乱七八糟的英文都很难说 为了熟悉重装系统的教程 有写用户会在虚拟机上去尝试 今天小编就为你们翻译出BIOS的英文 不懂英文的小伙伴们赶
  • Linux线程:创建(pthread_create),等待(pthread_join),退出(pthread_exit)

    目录 一 线程说明 线程与进程 线程优点 线程缺点 二 线程开发API概要 三 线程控制流程 线程创建 pthread create 线程退出 pthread exit 线程等待 pthread join 线程脱离 pthread deta
  • java超详细小程序对接微信支付(二),看完不会你打我

    4 支付通知回调 B 验证签名 因为这个接口是微信进行回调的 但是如果别人知道了这个接口就给进行伪造信息进行调用这个接口 补充一点 这里这个接口最后要返回给微信success 不然会一直进行调用该接口 以下是我封装的验证签名的方法 要给的参
  • Datawhale go语言学习 12并发编程

    12 并发编程 12 1 并发与并行 Erlang 之父 Joe Armstrong曾经以下图解释并发与并行 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img u21Pi0Ef 1608215197628 img