Go(二):包管理、通道、协程并发、互斥锁基础

news2024/12/23 12:08:08

包管理、协程并发基础

    • 生成包管理文件`go-mod`
      • 第一步(初始化创建包管理文件)
      • 第二步(导入包)
    • 常用命令
    • 导入远程包(示例:`gin`)
      • 第一步(导入包)
      • 第二步(安装包)
      • 第三步(示例代码)
    • 进程、线程、协程
      • 进程
      • 线程
      • 协程
    • 协程 `go ...`
      • 协程示例一
      • 协程示例二
    • 通道`channel`
      • 通过通道传递消息
      • 通道等待
      • 通道遍历
    • `WaitGroup`
    • 互斥锁`Mutex`
      • `未加锁`协程程序
      • 加了`互斥锁`的协程程序
    • `runtime`
      • `runtime.Gosched()`
      • `runtime.Goexit()`
      • `runtime.GOMAXPROCS(n)` `runtime.NumCPU()`
    • `select`
    • `time.NewTimer`
    • `time.NewTicker`
      • 定时器和停止定时器的示例
    • 并发之原子操作`sync/atomic`

生成包管理文件go-mod

第一步(初始化创建包管理文件)

在这里插入图片描述

$ go mod init go-app # go mod init 项目路径
/* go-app/go.mod */
module go-app

go 1.20

第二步(导入包)

/* go-app/main.go */
package main

import "go-app/services"

func main() {
	services.Login("Lee", "123456")  // Lee 123456
	services.GetUserInfoById("1024") // 1024 Lee
}
/* go-app/services/login.go */
package services

import "fmt"

func Login(username string, password string) {
	fmt.Println(username, password)
}
/* go-app/services/user.go */
package services

import "fmt"

func GetUserInfoById(id string) {
	fmt.Println(id, "Lee")
}

常用命令

$ go run      # 编译并运行一个Go程序。
$ go build    # 编译一个Go程序,生成可执行文件。
$ go test     # 运行Go程序的测试用例。
$ go get      # 从远程仓库下载并安装Go包。
$ go install  # 编译并安装一个Go程序。
$ go fmt      # 格式化Go源码。
$ go doc      # 查看Go包的文档。
$ go mod      # 管理Go模块(依赖管理)。
$ go env      # 查看Go环境变量。
$ go version  # 查看Go版本信息

导入远程包(示例:gin

查询使用第三方包的链接地址如下:

https://pkg.go.dev/

第一步(导入包)

import "github.com/gin-gonic/gin"

第二步(安装包)

$ go get -u github.com/gin-gonic/gin
# 解决无法安装问题

$ go env # 查看
# 修改如下
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
module go-app

go 1.20

require (
	github.com/bytedance/sonic v1.8.7 // indirect
	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
	github.com/gin-contrib/sse v0.1.0 // indirect
	github.com/gin-gonic/gin v1.9.0 // indirect
	github.com/go-playground/locales v0.14.1 // indirect
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/go-playground/validator/v10 v10.12.0 // indirect
	github.com/goccy/go-json v0.10.2 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
	github.com/leodido/go-urn v1.2.3 // indirect
	github.com/mattn/go-isatty v0.0.18 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.0.7 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/ugorji/go/codec v1.2.11 // indirect
	golang.org/x/arch v0.3.0 // indirect
	golang.org/x/crypto v0.8.0 // indirect
	golang.org/x/net v0.9.0 // indirect
	golang.org/x/sys v0.7.0 // indirect
	golang.org/x/text v0.9.0 // indirect
	google.golang.org/protobuf v1.30.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)

第三步(示例代码)

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

在这里插入图片描述

进程、线程、协程

进程

本质上是一个独立执行的程序,进程是操作系统进行资源分配和调度的基本概念,操作系统进行资源分配和调度的一个独立单位。

线程

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程执行不同的任务,切换受系统控制。

协程

又称为微线程,是一种用户态的轻量级线程,协程不像线程和进程需要进行系统内核上的上下文切换,协程的上下文切换是由用户自己决定的,有自己的上下文,所以说是轻量级的线程,也称之为用户级别的线程就叫协程,一个线程可以多个协程,线程进程都是同步机制,而协程则是异步。

协程 go ...

在一个函数的前边加上一个go关键字,代表了新开辟了一个子协程。

当程序启动时,main函数执行将会被作为主协程,当主协程执行完毕后会自动销毁所有子协程。

协程示例一

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

func request(method string, url string) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	fmt.Println(url, string(b))
}

请求同步进行,后请求等待最先请求出结果后出结果

func main() {
request("GET", "https://www.google.com/")
request("GET", "https://www.baidu.com/")
}

请求异步进行,先请求成功的先出结果

func main() {
go request("GET", "https://www.google.com/")
go request("GET", "https://www.baidu.com/")

time.Sleep(time.Millisecond * 60000)
}

协程示例二

非协程形式

package main

import (
	"fmt"
	"net/http"
	"time"
)

var links = []string{
	"https://www.baidu.com/",
	"https://www.jd.com/",
	"https://www.taobao.com/",
	"https://www.abcd.com/",
	"https://www.sogou.com/",
	"https://www.csdn.net/",
}

func main() {
	// 0 https://www.baidu.com/ 	Success
	// 1 https://www.jd.com/ 		Success
	// 2 https://www.taobao.com/ 	Success
	// 3 https://www.abcd.com/ 		Fail
	// 4 https://www.sogou.com/ 	Success
	// 5 https://www.csdn.net/ 		Success
	for i, link := range links {
		_, err := http.Get(link)
		if err == nil {
			fmt.Println(i, link, "Success")
		} else {
			fmt.Println(i, link, "Fail")
		}
	}
}

协程形式

package main

import (
	"fmt"
	"net/http"
	"time"
)

var links = []string{
	"https://www.baidu.com/",
	"https://www.jd.com/",
	"https://www.taobao.com/",
	"https://www.abcd.com/",
	"https://www.sogou.com/",
	"https://www.csdn.net/",
}

func main() {
	// 3 https://www.abcd.com/ 		Fail
	// 2 https://www.taobao.com/ 	Success
	// 1 https://www.jd.com/ 		Success
	// 0 https://www.baidu.com/ 	Success
	// 4 https://www.sogou.com/ 	Success
	// 5 https://www.csdn.net/ 		Success
	for i, link := range links {
		go func(i int, link string) {
			_, err := http.Get(link)
			if err == nil {
				fmt.Println(i, link, "Success")
			} else {
				fmt.Println(i, link, "Fail")
			}
		}(i, link)
	}

	time.Sleep(time.Millisecond * 10000)
}

通道channel

通道是协程之间的通道,可以让协程之间相互通信。

通过通道传递消息

以下程序主线程会等待子线程发送消息,当接收到第一条消息后立即在主协程中执行接收数据程序,随后主线程执行完毕,故而只打印出一条消息

package main

import (
	"fmt"
	"net/http"
)

var links = []string{
	"https://www.baidu.com/",
	"https://www.jd.com/",
	"https://www.taobao.com/",
	"https://www.abcd.com/",
	"https://www.sogou.com/",
	"https://www.csdn.net/",
}

func main() {

	// 字符串类型通道
	msg := make(chan string)

	for i, link := range links {
		// 协程
		go func(i int, link string, msg chan string) {
			_, err := http.Get(link)
			if err == nil {
				// 发送消息
				msg <- fmt.Sprintln(i, link, "Success")
			} else {
				// 发送消息
				msg <- fmt.Sprintln(i, link, "Fail")
			}
		}(i, link, msg)
	}

	// 接收消息
	data := <-msg
	// 打印接收到的消息
	fmt.Println(data)
}

通道等待

当主协程接收不到消息时,程序会一直停滞在接收消息过程中,故而能输出所有子线程输出的打印结果,或者可定义多个接收消息变量

package main

import (
	"fmt"
	"net/http"
)

var links = []string{
	"https://www.baidu.com/",
	"https://www.jd.com/",
	"https://www.taobao.com/",
	"https://www.abcd.com/",
	"https://www.sogou.com/",
	"https://www.csdn.net/",
}

func main() {

	// 字符串类型通道
	msg := make(chan string)

	for i, link := range links {
		// 协程
		go func(i int, link string, msg chan string) {
			_, err := http.Get(link)
			if err == nil {
				fmt.Println(i, link, "Success")
			} else {
				fmt.Println(i, link, "Fail")
			}
		}(i, link, msg)
	}

	// 接收消息
	data := <-msg
	// 打印接收到的消息
	fmt.Println(data)
}
package main

import (
	"fmt"
	"net/http"
)

var links = []string{
	"https://www.baidu.com/",
	"https://www.jd.com/",
	"https://www.taobao.com/",
	"https://www.abcd.com/",
	"https://www.sogou.com/",
	"https://www.csdn.net/",
}

func main() {

	// 字符串类型通道
	msg := make(chan string)

	for i, link := range links {
		// 协程
		go func(i int, link string, msg chan string) {
			_, err := http.Get(link)
			if err == nil {
				// 发送消息
				msg <- fmt.Sprintln(i, link, "Success")
			} else {
				// 发送消息
				msg <- fmt.Sprintln(i, link, "Fail")
			}
		}(i, link, msg)
	}
	// 打印接收到的消息
	fmt.Println(0, "--->", <-msg) // 0 ---> 3 https://www.abcd.com/ 	Fail
	fmt.Println(1, "--->", <-msg) // 1 ---> 1 https://www.jd.com/ 		Success
	fmt.Println(2, "--->", <-msg) // 2 ---> 2 https://www.taobao.com/ 	Success
	fmt.Println(3, "--->", <-msg) // 3 ---> 4 https://www.sogou.com/ 	Success
	fmt.Println(4, "--->", <-msg) // 4 ---> 0 https://www.baidu.com/ 	Success
	fmt.Println(5, "--->", <-msg) // 5 ---> 5 https://www.csdn.net/ 	Success
}

通道遍历

package main

import "fmt"

func main() {
	i := make(chan int)

	go func() {
		for j := 0; j < 5; j++ {
			i <- j
		}
		close(i) // 关闭通道
	}()

	//for {
	//	v, ok := <-i
	//	if ok {
	//		fmt.Println(v, ok)
	//	} else {
	//		break
	//	}
	//}

	for j := range i {
		fmt.Println(j)
	}

	//for j := 0; j < 10; j++ {
	//	fmt.Println(<-i)
	//}
}

WaitGroup

类似前端的axios拦截器实现加载状态的程序,当所有请求完成后才关闭加载状态

package main

import (
	"fmt"
	"net/http"
	"sync"
)

// 定义WaitGroup变量
var wg = sync.WaitGroup{}

var links = []string{
	"https://www.baidu.com/",
	"https://www.jd.com/",
	"https://www.taobao.com/",
	"https://www.abcd.com/",
	"https://www.sogou.com/",
	"https://www.csdn.net/",
}

func main() {
	for i, link := range links {
		// 协程
		go func(i int, link string) {
			// 每次完成请求后-1
			defer wg.Done()
			_, err := http.Get(link)
			if err == nil {
				fmt.Println(i, link, "Success")
			} else {
				fmt.Println(i, link, "Fail")
			}
		}(i, link)
		// 每次执行协程+1
		wg.Add(1)
	}

	// 等待
	wg.Wait()
}

互斥锁Mutex

未加锁协程程序

异步并发执行

在这里插入图片描述

package main

import (
	"fmt"
	"sync"
	"time"
)

var i = 0

var wg sync.WaitGroup

func add() {
	defer wg.Done()
	i++
	fmt.Printf("add->%v;", i)
	time.Sleep(time.Second * 2)
}

func sub() {
	defer wg.Done()
	i--
	fmt.Printf("sub->%v;", i)
}

/*
未加锁:(异步并发执行)

	结果1:add->1;add->1;sub->0;sub->0;add->1;sub->0;add->1;sub->0;add->2;sub->-1;-1
	结果2:add->1;sub->0;sub->-1;add->0;sub->-1;add->0;add->1;sub->1;add->2;sub->0;0
*/
func main() {
	for j := 0; j < 5; j++ {
		wg.Add(1)
		go add()
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	fmt.Println(i)
}

加了互斥锁的协程程序

并发程序下利用互斥锁实现同步依次执行,等待解锁后在向下执行

在这里插入图片描述

package main

import (
	"fmt"
	"sync"
	"time"
)

var i = 0

var wg sync.WaitGroup

var mutex sync.Mutex

func add() {
	defer wg.Done()
	mutex.Lock()
	i++
	fmt.Printf("add->%v;", i)
	time.Sleep(time.Second * 2)
	mutex.Unlock()
}

func sub() {
	mutex.Lock()
	defer wg.Done()
	i--
	fmt.Printf("sub->%v;", i)
	mutex.Unlock()
}

/*
加锁后:(并发程序下利用互斥锁实现同步依次执行,等待解锁后在向下执行)

	结果1:add->1;sub->0;sub->-1;add->0;sub->-1;add->0;add->1;sub->0;sub->-1;add->0;0
	结果2:add->1;sub->0;add->1;sub->0;add->1;add->2;sub->1;sub->0;add->1;sub->0;0
	结果3:add->1;sub->0;sub->-1;add->0;sub->-1;add->0;sub->-1;add->0;sub->-1;add->0;0
*/
func main() {
	for j := 0; j < 5; j++ {
		wg.Add(1)
		go add()
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	fmt.Println(i)
}

runtime

runtime.Gosched()

用于让出当前 Goroutine 的执行权限,让其他 Goroutine 有机会运行。这个函数的作用是让 Goroutine 主动让出 CPU 时间片,以便让其他 Goroutine 运行

不使用runtime.Gosched()

package main

import (
	"fmt"
	"time"
)

func printNum(msg string, n int) {
	for i := 0; i < n; i++ {
		fmt.Print(msg, i, " ")
	}
}

// 结果
// a0 a1 a2 b0 b1 b2 b3 b4 Done
// b0 b1 b2 b3 b4 a0 a1 a2 Done
func main() {
	go printNum("a", 3)
	go printNum("b", 5)

	time.Sleep(1 * time.Second) // 主 Goroutine 等待 1 秒钟

	fmt.Println("Done")
}

使用runtime.Gosched()

package main

import (
	"fmt"
	"runtime"
	"time"
)

func printNum(msg string, n int) {
	for i := 0; i < n; i++ {
		fmt.Print(msg, i, " ")
		runtime.Gosched() // 让出执行权限
	}
}

// 结果
// a0 b0 a1 a2 b1 b2 b3 b4 Done
// b0 b1 b2 b3 b4 a0 a1 a2 Done
// a0 b0 b1 a1 b2 a2 b3 b4 Done
// a0 b0 a1 b1 a2 b2 b3 b4 Done
func main() {
	go printNum("a", 3)
	go printNum("b", 5)

	time.Sleep(1 * time.Second) // 主 Goroutine 等待 1 秒钟

	fmt.Println("Done")
}

使用runtime.Gosched()目的

使用 runtime.Gosched() 函数可以让两个 Goroutine 更加公平地竞争 CPU 资源

runtime.Goexit()

用于立即终止当前 goroutine 的执行, 不会影响其他的 goroutine,也不会导致整个程序的退出。可以将其理解为一个强制性的 return 语句,用于从当前 goroutine 中立即返回

package main

import (
	"fmt"
	"runtime"
	"time"
)

func printNumA() {
	for i := 0; i < 10; i++ {
		fmt.Print("A", i, " ")
		if i >= 5 {
			runtime.Goexit()
		}
	}
}

func printNumB() {
	for i := 0; i < 10; i++ {
		fmt.Print("B", i, " ")
	}
}

// 结果
// A0 A1 A2 A3 A4 A5 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 Done
// B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 A0 A1 A2 A3 A4 A5 Done
func main() {
	go printNumA()
	go printNumB()

	time.Sleep(1 * time.Second) // 主 Goroutine 等待 1 秒钟

	fmt.Println("Done")
}

runtime.GOMAXPROCS(n) runtime.NumCPU()

runtime.GOMAXPROCS(n)设置程序采用CPU核心数,默认为最多核心

runtime.NumCPU()获取当前CPU核心数

默认

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 20; i++ {
		fmt.Print("a")
	}
}

func b() {
	for i := 0; i < 20; i++ {
		fmt.Print("b")
	}
}

func main() {
	fmt.Print(runtime.NumCPU()) // 12 获取当前CPU核心数

	// 默认结果(交替出现)
	// 12 bbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaabaaaab
	go a()
	go b()

	time.Sleep(time.Second)
}

单核

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 20; i++ {
		fmt.Print("a")
	}
}

func b() {
	for i := 0; i < 20; i++ {
		fmt.Print("b")
	}
}

func main() {
	fmt.Print(runtime.NumCPU()) // 12 获取当前CPU核心数

	// 单核结果(顺序出现)
	//runtime.GOMAXPROCS(1)  12 bbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa
	runtime.GOMAXPROCS(1) // 设置为单核
	go a()
	go b()

	time.Sleep(time.Second)
}

双核

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 20; i++ {
		fmt.Print("a")
	}
}

func b() {
	for i := 0; i < 20; i++ {
		fmt.Print("b")
	}
}

func main() {
	fmt.Print(runtime.NumCPU()) // 12 获取当前CPU核心数

	// 双核结果(交替出现)
	// runtime.GOMAXPROCS(2)  12 bbbaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbaaaaa
	// runtime.GOMAXPROCS(2)  12 aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb
	// runtime.GOMAXPROCS(2)  12 aaaaaaabbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaa
	runtime.GOMAXPROCS(2) // 设置为双核
	go a()
	go b()

	time.Sleep(time.Second)
}

select

用于监听多个通道的输入和输出,可以实现非阻塞的通信,等待多个通道操作的函数

select 中,每个 case 都必须是一个通道操作。当 select 函数被调用时,它会等待其中的一个 case 操作完成,然后执行该 case 语句块。如果有多个 case 操作同时完成,那么 Go 会随机地选中其中一个

package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		c1 <- "Hello"
	}()

	go func() {
		c2 <- "World"
	}()

	for {
		select {
		case msg1 := <-c1:
			fmt.Println(msg1)
		case msg2 := <-c2:
			fmt.Println(msg2)
		default: // 避免死锁
			fmt.Println("default")
		}
		time.Sleep(time.Second)
	}
}
package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		c1 <- "Hello"
		close(c1) // 避免死锁
	}()

	go func() {
		c2 <- "World"
		close(c2) // 避免死锁
	}()

	for {
		select {
		case msg1 := <-c1:
			fmt.Println(msg1)
		case msg2 := <-c2:
			fmt.Println(msg2)
		}
		time.Sleep(time.Second)
	}
}

time.NewTimer

用于创建一个新的定时器并返回该定时器,在指定的时间后触发一个事件

package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(2 * time.Second)

	fmt.Println("计时器已启动")

	<-timer.C // 阻塞等待计时器的时间到达

	fmt.Println("时间到!")
}

停止定时器

package main

import (
	"fmt"
	"time"
)

/**
 * 结果:
 *      计时器已启动
 *      时间到!
 */
func main() {
	timer := time.NewTimer(5 * time.Second)

	fmt.Println("计时器已启动")

	time.Sleep(1 * time.Second)
	if !timer.Stop() {
		fmt.Println(<-timer.C)
	}

	fmt.Println("时间到!")
}
package main

import (
	"fmt"
	"time"
)

/**
 * 结果:
 *      计时器已启动
 *      2023-05-02 12:06:31.980288 +0800 CST m=+5.001244626
 *      时间到!
 */
func main() {
	timer := time.NewTimer(5 * time.Second)

	fmt.Println("计时器已启动")

	time.Sleep(6 * time.Second)
	if !timer.Stop() {
		fmt.Println(<-timer.C) // 2023-05-02 12:06:31.980288 +0800 CST m=+5.001244626
	}

	fmt.Println("时间到!")
}

重置定时器

package main

import (
	"fmt"
	"time"
)

/**
 * 结果:
 * 		计时器已启动
 * 		2023-05-02 12:11:00.035369 +0800 CST m=+1.001328668
 * 		时间到!
 */
func main() {
	timer := time.NewTimer(5 * time.Second)

	fmt.Println("计时器已启动")
	timer.Reset(time.Second) // 重置定时器为1s

	fmt.Println(<-timer.C)

	fmt.Println("时间到!")
}

time.NewTicker

定时器和停止定时器的示例

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个定时器,每隔2秒钟执行一次
	ticker := time.NewTicker(time.Second * 2)

	count := 0

	for t := range ticker.C {
		count++
		fmt.Println(t)
		if count == 3 {
			ticker.Stop()
			fmt.Println("定时器停止!")
			break
		}
	}
}

并发之原子操作sync/atomic

指不会被中断的一个或一系列操作,即使在并发的情况下也能保证操作的原子性

协程累加累减变量

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

var wg sync.WaitGroup

var i int32 = 100

func add() {
	defer wg.Done()
	atomic.AddInt32(&i, 1)
}

func sub() {
	defer wg.Done()
	atomic.AddInt32(&i, -1)
}

func main() {
	for {
		for j := 0; j < 100; i++ {
			wg.Add(1)
			go add()
			wg.Add(1)
			go sub()
		}
		wg.Wait()
		fmt.Println(i) // 100 100 100 100 100 100 ...
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/483714.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统之进程同异步、互斥

引入 异步性是指&#xff0c;各并发执行的进程以各自独立的、不可预知的速度向前推进。 但是在一定的条件之下&#xff0c;需要进程按照一定的顺序去执行相关进程&#xff1a; 举例说明1&#xff1a; 举例说明2: 读进程和写进程并发地运行&#xff0c;由于并发必然导致异步性…

【Python】如何在Python中绘制带有连接线的双饼图?

文章目录 一、导入所需的库二、准备数据三、绘制双饼图3.1 创建画布和子图对象3.2 绘制大饼图3.3 绘制小饼图3.4 连接线1&#xff0c;连接大饼图的上边缘和小饼图的饼块3.5 连接线2&#xff0c;连接大饼图的下边缘和小饼图的饼块3.6 添加连接线3.7 调整子图布局 四、源代码 在 …

Linux 内核组织(kernel.org)将关闭 FTP 服务

Linux 内核组织&#xff08;kernel.org&#xff09;是一家建立于 2002 年的加利福尼亚公共福利公司&#xff0c;其目的是公开地免费分发 Linux 内核和其它开源软件。它接受 Linux 基金会的管理&#xff0c;包括技术、资金和人员支持&#xff0c;用以维护kernel.org 的运营。 Li…

2 ROS2话题通讯基础(1)

2 ROS2话题通讯基础 2.1 ROS2话题通讯介绍2.2 ROS2常用的消息类型介绍2.2.1 std_msgs消息类型2.2.2 geometry_msgs消息类型 2.3 使用C/C创建基础消息类型的话题通讯2.3.1 创建C/C发布话题信息的功能包并配置VSCode环境2.3.2 编写ROS2发布话题节点CPP文件2.3.3 配置C/C发布话题功…

【Elasticsearch】SQL操作相关

文章目录 SQL操作数据准备查询索引下的数据SQL转化为DSL(本质)SQL与DSL混合使用查看所有索引查询指定索引查看索引(表)结构where条件过滤group by分组having 对分组后的数据进行过滤order by 排序limit 限制查询数量cursor 游标->为缓存设计聚合操作支持的函数和运算比较运算…

虚拟机和Docker有什么区别?

虚拟机 对于虚拟机&#xff0c;抽象层或抽象软件成为管理程序。管理程序就是帮助虚拟机模拟物理计算机的东西。在管理程序下面&#xff0c;我们有些硬件。管理程序管理单个物理主机上不同虚拟机之间的资源分配。管理程序管理单个物理主机上不同虚拟机之间的资源分配。也就是管…

微信小程序学习实录3(环境部署、百度地图微信小程序、单击更换图标、弹窗信息、导航、支持腾讯百度高德地图调起)

百度地图微信小程序 一、环境部署1.need to be declared in the requiredPrivateInfos2.api.map.baidu.com 不在以下 request 合法域名3.width and heigth of marker id 9 are required 二、核心代码&#xff08;一&#xff09;逻辑层index.js&#xff08;二&#xff09;渲染层…

vue diff算法与虚拟dom知识整理(2) snabbdom简介并搭建开发环境

snabbdom算是diff算法 和 虚拟dom 的一个鼻租了 vue源码借鉴了snabbdom 这个单词翻译出来叫速度 命名还是用了点心的 后面是 dom 这个 我们大概去猜作者的意思 大概想表示的就是 一个比较快的dom操作 snabbdom的get地址如下 https://github.com/snabbdom/snabbdom 这里的简…

「OceanBase 4.1 体验」|快速安装部署[OBD方式]

文章目录 一、Oceanbase数据库简介1.1 核心特性1.2 系统架构1.2.1 存储层1.2.2 复制层1.2.3 均衡层1.2.4 事务层1.2.4.1 原子性1.2.4.2 隔离性 1.2.5 SQL 层1.2.5.1 SQL 层组件1.2.5.2 多种计划 1.2.6 接入层 二、OceanBase 数据库社区版部署2.1 部署方式2.2 基础环境配置2.3 通…

【华为OD机试真题】信号发射和接收(javaC++python)100%通过率 超详细代码注释 代码深度解读

信号发射和接收 知识点数组栈 单调栈时间限制: 1s 空间限制: 256MB 限定语言:不限 题目描述: 有一个二维的天线矩阵&#xff0c;每根天线可以向其他天线发射信号也能接收其他天线的信号&#xff0c;为了简化起见&#xff0c;我们约定每根天线只能向东和向南发射信号&#xf…

【ROS仿真实战】获取机器人在gazebo位置真值的三种方法(三)

文章目录 前言一. 使用ROS tf库二、 使用Gazebo Model Plugin三、 使用libgazebo_ros_p3d插件四、总结 前言 在ROS和Gazebo中&#xff0c;获取机器人的位置信息通常通过ROS消息传递进行。在这篇文章中&#xff0c;我们将介绍三种获取机器人在Gazebo中位置真值的方法&#xff1…

CTF ASCII码 密码解密题 简单

1. 题目 这次的CTF题目就是一张图片如下&#xff0c;并且说有几个蛋被打乱过。明显是一个密码学的解码题。 2. 解题思路 左边表格给出10种颜色&#xff0c;特别是第二列给出了数字0&#xff0c;种种迹象都指向了10进制。每一个蛋都有三种颜色&#xff0c;代表每个蛋都是三位…

【GORM框架】一文学会用gorm实现对单表的增删改查操作

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: GORM框架学习 近期目标&#xff1a;写好专栏的每一篇文章 文章目录 一、…

M1 Mac配置JAVA环境

1、下载JDK 目前JDK有Oracle的JDK还有zulu的Open JDK可供选择&#xff0c;因为需要JAVA1.8所以下文以zulu的JDK为例。 Zulu官网&#xff1a;https://www.azul.com/downloads/?packagejdk 选择所需的JDK版本&#xff08;注意选择ARM架构&#xff09;> 下载.dmg包 > 安装 …

DAY 47 Ngnix优化与防盗链

Ngnix优化主要有两种&#xff0c;一种是配置上的优化&#xff0c;一种是内核上的优化 隐藏响应头中的版本号 方法一&#xff1a;curl命令 网页查看 隐藏版本信息 修改nginx的运行用户和组 方法一&#xff1a;在编译安装时&#xff0c;指定运行用户和组 [root nginx-1.12.2]#…

【英语】100个句子记完7000个雅思单词

其实主要的7000词其实是在主题归纳里面&#xff0c;不过过一遍100个句子也挺好的&#xff0c;反正也不多。 文章目录 Sentence 01Sentence 02Sentence 03Sentence 04Sentence 05Sentence 06Sentence 07Sentence 08Sentence 09Sentence 10Sentence 11Sentence 12Sentence 13Sent…

Linux常用的压缩、解压缩以及scp远程传输命令的使用

Linux常用的压缩、解压缩以及scp远程传输命令的使用 1.压缩命令2 解压命令3. 大文件压缩分割为多个压缩文件4. 远程传输命令scp4.1 将本地文件复制到远程主机目录4.2 将本地目录复制到远程主机目录4.3 将远程主机的文件复制到本机4.4 复制远程主机目录到本机 1.压缩命令 tar -…

Packet Tracer - 综合技能练习(配置新交换机的初始设置、SSH 和端口安全)

Packet Tracer - 综合技能练习 地址分配表 设备 接口 IP 地址 子网掩码 S1 VLAN 1 10.10.10.2 255.255.255.0 PC1 NIC 10.10.10.10 255.255.255.0 PC2 NIC 10.10.10.11 255.255.255.0 场景 网络管理员要求您配置新交换机。 在本练习中&#xff0c;您将使用一…

二分搜索算法通解框架

文章介绍了二分搜索最常见的几个场景的使用&#xff1a;寻找一个数、寻找左侧边界以及寻找右侧边界。阅读本文只需读者了解二分搜索的使用限制和基本原理即可。 我相信&#xff0c;友好的讨论交流会让彼此快速进步&#xff01;文章难免有疏漏之处&#xff0c;十分欢迎大家在评…

密码学【java】初探究加密方式之对称加密

文章目录 一 常见加密方式二 对称加密2.1 Cipher类简介2.2 Base算法2.3 补充&#xff1a;Byte&bit2.4 DES加密演示2.5 DES解密2.6 补充&#xff1a;对于IDEA控制台乱码的解决方法2.7 AES加密解密2.8 补充&#xff1a; toString()与new String ()用法区别2.9 加密模式2.9.1 …