《Go语言并发之道》读书笔记

news2024/11/25 14:26:20

《Go语言并发之道》

      • 第一章: 并发概述
      • 第二章:对你的代码建模:通信顺序进程
      • 第三章:GO语言并发组件

由于不怎么熟悉GO,只做简单的摘录,敲敲示例代码
鸭子类型:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
面试扣分点:什么是鸭子类型

Go-FAQ 翻译 | Seeker

第一章: 并发概述

令人尴尬的并行问题:

Many may wonder the etymology of the term “embarrassingly”. In this case, embarrassingly has nothing to do with embarrassment; in fact, it means an overabundance—here referring to parallelization problems which are “embarrassingly easy”.

cpu并行算法和gpu并行_令人尴尬的并行算法介绍
Web-Scale IT 我之见!

竞争条件
当两个或多个操作必须按正确的顺序执行,而程序并未保证这个顺序,就会发生竞争条件。

// 循环执行示例程序,记录各个结果出现次数
func main() {
	var cnt [2]int
	for i := 0; i < 10000000; i++ {
		var data int
		go func() {
			data++
		}()
		if data == 0 {
			cnt[data]++
		}
	}
	fmt.Printf("cnt:%v", cnt)
}
// 执行三次
go run compete.go
cnt:[9999977 0]                                                                                                                                                          
cnt:[9999992 0]                                                                                                                                                           
cnt:[9999980 0]

在大多数情况下,引入数据竞争的原因是因为开发人员用顺序性的思维来思考问题。他们假设,某一行代码逻辑会在另一行代码逻辑之前先运行。我发现,有时候想象在两个操作之间会间隔很长一段时间是很有帮助的。
你应该始终以逻辑正确性为目标。在代码中引入休眠可以方便调试程序,但这并不能称之为一个解决方案。

原子性
当某些东西被认为是原子的,或者具有原子性的时候,这意味着在它运行的环境中,它是不可分割的或不可中断的。
在考虑原子性时,经常第一件需要做的事就是定义上下文或范围,然后再考虑这些操作是否是原子性的。一切都应当遵循这个原则。

死锁
死锁程序是所有并发进程彼此等待的程序。在这种情况下,如果没有外界的干预,这个程序将永远无法恢复。

示例程序

type value struct {
	mu  sync.Mutex
	val int
}

func main() {
	var wg sync.WaitGroup
	// 获取锁后睡眠两秒再次获取另一个锁
	printSum := func(v1, v2 *value) {
		defer wg.Done()
		v1.mu.Lock()
		defer v1.mu.Unlock()
		time.Sleep(2 * time.Second)

		v2.mu.Lock()
		defer v2.mu.Unlock()
		fmt.Printf("sum=%v\n", v1.val+v2.val)
	}
	var a, b value
	wg.Add(2)
	go printSum(&a, &b)
	go printSum(&b, &a)
	wg.Wait()
}

运行输出

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc0000a6018)
        /usr/lib/go-1.13/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc0000a6010)
        /usr/lib/go-1.13/src/sync/waitgroup.go:130 +0x64
main.main()
        /root/mit6.824/6.824/src/expr/deadlock.go:30 +0x122

goroutine 18 [semacquire]:
sync.runtime_SemacquireMutex(0xc0000a6034, 0x1300, 0x1)
        /usr/lib/go-1.13/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc0000a6030)
        /usr/lib/go-1.13/src/sync/mutex.go:138 +0xfc
sync.(*Mutex).Lock(...)
        /usr/lib/go-1.13/src/sync/mutex.go:81
main.main.func1(0xc0000a6020, 0xc0000a6030)
        /root/mit6.824/6.824/src/expr/deadlock.go:22 +0x1f4
created by main.main
        /root/mit6.824/6.824/src/expr/deadlock.go:28 +0xea

goroutine 19 [semacquire]:
sync.runtime_SemacquireMutex(0xc0000a6024, 0x1300, 0x1)
        /usr/lib/go-1.13/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc0000a6020)
        /usr/lib/go-1.13/src/sync/mutex.go:138 +0xfc
sync.(*Mutex).Lock(...)
        /usr/lib/go-1.13/src/sync/mutex.go:81
main.main.func1(0xc0000a6030, 0xc0000a6020)
        /root/mit6.824/6.824/src/expr/deadlock.go:22 +0x1f4
created by main.main
        /root/mit6.824/6.824/src/expr/deadlock.go:29 +0x114
exit status 2
root@ubuntu ~/m/6/s/expr (master) [1]# go run deadlock.go 
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc0000a6018)
        /usr/lib/go-1.13/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc0000a6010)
        /usr/lib/go-1.13/src/sync/waitgroup.go:130 +0x64
main.main()
        /root/mit6.824/6.824/src/expr/deadlock.go:30 +0x122

goroutine 18 [semacquire]:
sync.runtime_SemacquireMutex(0xc0000a6034, 0x1300, 0x1)
        /usr/lib/go-1.13/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc0000a6030)
        /usr/lib/go-1.13/src/sync/mutex.go:138 +0xfc
sync.(*Mutex).Lock(...)
        /usr/lib/go-1.13/src/sync/mutex.go:81
main.main.func1(0xc0000a6020, 0xc0000a6030)
        /root/mit6.824/6.824/src/expr/deadlock.go:22 +0x1f4
created by main.main
        /root/mit6.824/6.824/src/expr/deadlock.go:28 +0xea

goroutine 19 [semacquire]:
sync.runtime_SemacquireMutex(0xc0000a6024, 0x1300, 0x1)
        /usr/lib/go-1.13/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc0000a6020)
        /usr/lib/go-1.13/src/sync/mutex.go:138 +0xfc
sync.(*Mutex).Lock(...)
        /usr/lib/go-1.13/src/sync/mutex.go:81
main.main.func1(0xc0000a6030, 0xc0000a6020)
        /root/mit6.824/6.824/src/expr/deadlock.go:22 +0x1f4
created by main.main
        /root/mit6.824/6.824/src/expr/deadlock.go:29 +0x114
exit status 2

一个逻辑上“完美”的死锁将需要正确地同步。
Coffman死锁条件如下:
相互排斥
 并发进程同时拥有资源的独占性
等待条件
 并发进程必须同时拥有一个资源并等待额外的资源。
没有抢占
 并发进程拥有的资源只能被该进程释放即可满足这个条件
循环等待
 一个并发进程(P1)必须等待其余并发进程(P2),这些并发进程同时也在等待进程(P1)

活锁
活锁是正在主动执行并发操作的程序,但是这些操作无法向前推进程序的状态。

func main() {
	cadence := sync.NewCond(&sync.Mutex{})
	go func() {
		for range time.Tick(1 * time.Millisecond) { // 定时发布广播
			cadence.Broadcast()
		}
	}()
	takeStep := func() {
		cadence.L.Lock()
		cadence.Wait() // 等待唤醒
		cadence.L.Unlock()
	}
	tryDir := func(dirName string, dir *int32, out *bytes.Buffer) bool {
		fmt.Fprintf(out, " %v", dirName)
		atomic.AddInt32(dir, 1)
		takeStep()
		if atomic.LoadInt32(dir) == 1 { // 只有一个进程选择该方向
			fmt.Fprintf(out, ".success!")
		}
		takeStep()
		atomic.AddInt32(dir, -1)
		return false
	}
	var left, right int32
	tryLeft := func(out *bytes.Buffer) bool {
		return tryDir("left", &left, out)
	}
	tryRight := func(out *bytes.Buffer) bool {
		return tryDir("right", &right, out)
	}
	walk := func(walking *sync.WaitGroup, name string) {
		var out bytes.Buffer
		defer walking.Done()
		defer func() { // 需放在Done后面保证一定输出
			fmt.Println(out.String())
		}()
		fmt.Fprintf(&out, "%v is try to scoot:", name)
		for i := 0; i < 5; i++ {
			if tryLeft(&out) || tryRight(&out) {
				return
			}
		}
		fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation!", name)
	}
	var peopleInHallway sync.WaitGroup
	peopleInHallway.Add(2)
	go walk(&peopleInHallway, "Alice")
	go walk(&peopleInHallway, "Bob")
	peopleInHallway.Wait()
}

/*
Bob is try to scoot: left right left right left right left right left right
Bob tosses her hands up in exasperation!
Alice is try to scoot: left right left right left right left right left right
Alice tosses her hands up in exasperation!
*/

饥饿
饥饿是在任何情况下,并发进程都无法获得执行工作所需的所有资源。

func main() {
	var wg sync.WaitGroup
	var sharedLock sync.Mutex
	const runtime = 1 * time.Second
	greedWorker := func() {
		defer wg.Done()
		var count int
		for begin := time.Now(); time.Since(begin) < runtime; {
			sharedLock.Lock()
			time.Sleep(3 * time.Nanosecond)
			sharedLock.Unlock()
			count++
		}
		fmt.Printf("Greedy worker was able to execute %v work loops\n", count)
	}
	politeWorker := func() {
		defer wg.Done()
		var count int
		for begin := time.Now(); time.Since(begin) < runtime; {
			sharedLock.Lock()
			time.Sleep(1 * time.Nanosecond)
			sharedLock.Unlock()

			sharedLock.Lock()
			time.Sleep(1 * time.Nanosecond)
			sharedLock.Unlock()

			sharedLock.Lock()
			time.Sleep(1 * time.Nanosecond)
			sharedLock.Unlock()
			count++
		}
		fmt.Printf("Polite worker was able to execute %v work loops\n", count)
	}
	wg.Add(2)
	go greedWorker()
	go politeWorker()
	wg.Wait()
}
/*
Greedy worker was able to execute 831906 work loops
Polite worker was able to execute 564963 work loops
*/

第二章:对你的代码建模:通信顺序进程

并发属于代码,并行属于一个运行中的程序

  1. 首先,我们并没有编写并行的代码,只有我们希望可以并行执行的并发代码。另外,并行是我们程序运行时的属性,而不是我们的代码。
  2. 其次,就是可能对我们所写的并发代码是否真的并行执行,保持不知情。这只有在我们的程序模型之下的抽象层实现:并发原语,程序的运行时,操作系统,操作系统所运行的平台(运行在hypervisor,容器和虚拟机时),以及最终的CPU,这些抽象给予我们区分并发与并行的能力,最终给了我们灵活而有力的表达。让我们回到这个问题本身。
  3. 第三个也是最后一个有意思的事情是并行是一个时间或者上下文的函数。

通常来说,一种语言会将它们的抽象链结束在系统线程和内存访问同步的层级。GO语言采用了一种不同的路线,并使用goroutine和channel来代替这些概念

不要通过共享内存进行通信,而是通过通信来共享内存
在这里插入图片描述
GO语言的并行性哲学可以这样总结:追求简洁,尽量使用channel,并且认为goroutine的使用是没有成本的。

第三章:GO语言并发组件

GO语言中的goroutine是独一无二的(尽管其他的一些语言有类似的并发原语)。它们不是OS线程,也不是绿色线程(由语言运行时管理的线程),它们是一个更高级别的抽象,称为协程。协程是一种非抢占式的简单并发子goroutine(函数,闭包或方法),也就是说,它们不能被中断。取而代之的是,协程有多个point,允许暂停或重新进入。

GO语言的主机托管机制是一个名为M:N调度器的实现,这意外这它将M个绿色线程映射到N个OS线程,然后将goroutine运行在绿色线程上。当我们的goroutine数量超过可用的绿色线程时,调度程序将处理分布在可用线程上的goroutine,并确保当这些goroutine阻塞时,其他的goroutine可以运行。
GO语言遵循一个称为fork-join的并发模型。fork这个词指的是在程序中的任意一个节点,可以将子节点与父节点同时运行。join这个词指的是,在将来某个时候,这些并发的执行分支将会合并在一起。joint point是保证程序正确性和消除竞争条件的关键。

// 证明goroutine在它们所创建的相同地址空间内执行
func main() {
	var wg sync.WaitGroup
	str := "hello"
	wg.Add(1)
	go func() {
		defer wg.Done()
		str = "world"
	}()
	wg.Wait()
	fmt.Println(str)
}
// world

空goroutine大小

func main() {
	memConsumed := func() uint64 {
		runtime.GC()
		var s runtime.MemStats
		runtime.ReadMemStats(&s)
		return s.Sys
	}
	var c <-chan interface{}
	var wg sync.WaitGroup
	noop := func() {
		wg.Done()
		<-c
	}
	const numGoroutines int = 1e5
	wg.Add(numGoroutines)
	before := memConsumed()
	for i := 0; i < numGoroutines; i++ {
		go noop()
	}
	wg.Wait()
	after := memConsumed()
	fmt.Printf("before:%vkb after:%vkb consume:%.3fkb", before/1000, after/1000, float64(after-before)/float64(numGoroutines)/1000)
}

// before:69994kb after:281516kb consume:2.115kb

上下文切换时间

func BenchmarkContextSwitch(b *testing.B) {
	var wg sync.WaitGroup
	begin := make(chan struct{})
	c := make(chan struct{})

	var token struct{}
	sender := func() {
		defer wg.Done()
		<-begin
		for i := 0; i < b.N; i++ {
			c <- token
		}
	}
	receiver := func() {
		defer wg.Done()
		<-begin
		for i := 0; i < b.N; i++ {
			<-c
		}
	}
	wg.Add(2)
	go sender()
	go receiver()
	b.StartTimer()
	close(begin)
	wg.Wait()
}

go test -bench=. -cpu=1 bench_test.go
goos: linux
goarch: amd64
BenchmarkContextSwitch   8860370               157 ns/op
PASS
ok      command-line-arguments  1.534s

sync包
你可以将WaitGroup视为一个并发-安全的计数器:调用通过传入的整数执行Add方法增加计数器的增量,并调用Done方法对计数器进行递减,Wait方法阻塞,直到计数器为零。注意,Add调用是在它们帮助跟踪的goroutine之外完成的。

读写锁

func main() {
	producer := func(wg *sync.WaitGroup, l sync.Locker) {
		defer wg.Done()
		for i := 5; i >= 0; i-- {
			l.Lock()
			l.Unlock()
			time.Sleep(1)
		}
	}
	observer := func(wg *sync.WaitGroup, l sync.Locker) {
		defer wg.Done()
		l.Lock()
		l.Unlock()
	}
	test := func(count int, mutex, rwMutex sync.Locker) time.Duration {
		var wg sync.WaitGroup
		wg.Add(count + 1)
		beginTestTime := time.Now()
		go producer(&wg, mutex)
		for i := count; i > 0; i-- {
			go observer(&wg, rwMutex)
		}
		wg.Wait()
		return time.Since(beginTestTime)
	}
	tw := tabwriter.NewWriter(os.Stdout, 0, 1, 2, ' ', 0)
	defer tw.Flush()

	var m sync.RWMutex
	fmt.Fprintf(tw, "Reader\tRWMutex\tMutex\n")
	for i := 0; i < 20; i++ {
		count := int(math.Pow(2, float64(i)))
		fmt.Fprintf(tw, "%d\t%v\t%v\n", count, test(count, &m, m.RLocker()), test(count, &m, &m))
	}
}
/*
Reader  RWMutex       Mutex
1       11.421µs      2.805µs
2       4.819µs       2.685µs
4       3.556µs       3.125µs
8       13.856µs      3.867µs
16      10.039µs      5.53µs
32      37.981µs      9.057µs
64      60.037µs      114.142µs
128     143.297µs     38.381µs
256     161.08µs      58.189µs
512     343.771µs     141.238µs
1024    474.765µs     727.275µs
2048    1.106501ms    987.48µs
4096    1.100992ms    1.41115ms
8192    2.010095ms    2.5819ms
16384   3.592384ms    4.176407ms
32768   8.957634ms    7.668959ms
65536   19.622861ms   14.164301ms
131072  31.256883ms   36.022752ms
262144  64.181958ms   59.230185ms
524288  120.306972ms  113.102032ms
*/

看不懂互斥锁与读写锁的时间对比是啥用意

cond:一个goroutine的集合点,等待或发布一个event。
注意,调用Wait不只是阻塞,它挂起了当前的goroutine,允许其他的goroutine在OS线程上运行。当你调用Wait时,会发生一些其他事情:进入Wait后,在Cond变量的Locker上调用Unlock方法;在退出Wait时,在Cond变量的Locker上执行Lock方法。

Signal示例

func main() {
	c := sync.NewCond(&sync.Mutex{})
	queue := make([]interface{}, 0, 10)
	removeFromQueue := func(delay time.Duration) {
		time.Sleep(delay)
		c.L.Lock()
		queue = queue[:1]
		fmt.Println("removed from queue")
		c.L.Unlock()
		c.Signal()
	}
	for i := 0; i < 10; i++ {
		c.L.Lock()
		for len(queue) == 2 {
			c.Wait()
		}
		fmt.Println("add to queue")
		queue = append(queue, struct{}{})
		go removeFromQueue(1 * time.Second)
		c.L.Unlock()
	}
}
/*
add to queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
removed from queue
add to queue
*/

BroadCast示例

type Button struct {
	Clicked *sync.Cond
}

func main() {
	button := Button{Clicked: sync.NewCond(&sync.Mutex{})}
	subscribe := func(c *sync.Cond, fn func()) {
		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			wg.Done()
			c.L.Lock()
			defer c.L.Unlock()
			c.Wait()
			fn()
		}()
		wg.Wait()
	}
	var clickRegister sync.WaitGroup
	clickRegister.Add(3)
	subscribe(button.Clicked, func() {
		fmt.Println("Maximizing")
		clickRegister.Done()
	})
	subscribe(button.Clicked, func() {
		fmt.Println("Display")
		clickRegister.Done()
	})
	subscribe(button.Clicked, func() {
		fmt.Println("Mouse")
		clickRegister.Done()
	})
	button.Clicked.Broadcast()
	clickRegister.Wait()
}
/*
Mouse
Maximizing
Display
*/

once
顾名思义,sync.Once是一种类型,它在内部使用一些sync原语,以确保即使在不同的goroutine上也只会调用一次Do方法传递进来的函数。

grep -ir sync.Once $(go env GOROOT)/src | wc -l
112

示例代码

func main() {
	var count int
	increment := func() {
		count++
		fmt.Println("call increment function")
	}

	var once sync.Once
	var wg sync.WaitGroup
	wg.Add(100)
	for i := 0; i < 100; i++ {
		go func() {
			defer wg.Done()
			once.Do(increment)
		}()
	}
	wg.Wait()
	fmt.Printf("count is %d.\n", count)
}
/*
call increment function
count is 1.
*/

另一个示例代码

func main() {
	var count int
	increment := func() {
		count++
	}
	decrement := func() {
		count--
	}
	var once sync.Once
	once.Do(increment)
	once.Do(decrement)
	fmt.Printf("count is %d\n", count)
}
/*
count is 1
*/

sync.Once只计算调用Do方法的次数,而不是多少次唯一调用Do方法。

Pool
sync.Pool是Pool模式的并发安全实现,在较高的层次上,Pool模式是一种创建和提供可供使用的固定数量实例或Pool实例的方法。它通常用于创建昂贵的场景(数据库连接),以便只创建固定数量的实例,但不确定数量的操作仍然可用请求访问这些场景(什么鬼翻译)。对于Go语言的sync.Pool,这种数据类型可以被多个goroutine安全地使用

示例1

func main() {
	var numCalcsCreated int
	calcPool := &sync.Pool{New: func() interface{} {
		numCalcsCreated += 1
		mem := make([]byte, 1024)
		return &mem
	}}

	// 用4KB初始化pool
	calcPool.Put(calcPool.New())
	calcPool.Put(calcPool.New())
	calcPool.Put(calcPool.New())
	calcPool.Put(calcPool.New())

	const numWorkers = 1024 * 1024
	var wg sync.WaitGroup
	wg.Add(numWorkers)
	for i := numWorkers; i > 0; i-- {
		go func() {
			defer wg.Done()
			mem := calcPool.Get().(*[]byte) // 断言
			defer calcPool.Put(mem)
		}()
	}
	wg.Wait()
	fmt.Printf("%d calculators were created.\n", numCalcsCreated)
}
/*
8 calculators were created.
*/

示例代码2

func connectToService() interface{} {
	time.Sleep(1 * time.Second)
	return struct{}{}
}

func startNetworkDaemon() *sync.WaitGroup { // 开启后台服务协程,监听8080端口
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		server, err := net.Listen("tcp", "localhost:8080")
		if err != nil {
			log.Fatalf("cannot listen: %v", err)
		}
		defer server.Close()
		wg.Done()
		for {
			conn, err := server.Accept()
			if err != nil {
				log.Printf("cannot accept connection:%v", err)
			}
			connectToService()
			fmt.Fprintln(conn, "")
			conn.Close()
		}
	}()
	return &wg
}

func warmServiceConnCache() *sync.Pool { // 创建连接池
	p := &sync.Pool{
		New: connectToService,
	}
	for i := 0; i < 10; i++ { // 初始化连接池,放入10个连接
		p.Put(p.New())
	}
	return p
}

func startNetworkDaemonWithPool() *sync.WaitGroup { // 开启后台服务协程,监听8080端口,使用连接池
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		connPool := warmServiceConnCache()
		server, err := net.Listen("tcp", "localhost:8080")
		if err != nil {
			log.Fatalf("cannot listen: %v", err)
		}
		defer server.Close()
		wg.Done()
		for {
			conn, err := server.Accept()
			if err != nil {
				log.Printf("cannot accept connection:%v", err)
			}
			svcConn := connPool.Get()
			fmt.Fprintln(conn, "")
			connPool.Put(svcConn)
			conn.Close()
		}
	}()
	return &wg
}

func init() {
	// daemonStarted := startNetworkDaemon()
	daemonStarted := startNetworkDaemonWithPool()
	daemonStarted.Wait()
}

func BenchmarkNetworkRequest(b *testing.B) {
	for i := 0; i < b.N; i++ {
		conn, err := net.Dial("tcp", "localhost:8080") // 客户端程序
		if err != nil {
			b.Fatalf("cannot dial host:%v", err)
		}
		if _, err := ioutil.ReadAll(conn); err != nil { // 一直读取直到文件末尾
			b.Fatalf("cannot read:%v", err)
		}
		conn.Close()
	}

}

/*
goos: linux
goarch: amd64
BenchmarkNetworkRequest-8             10        1001305398 ns/op
PASS
ok      command-line-arguments  11.023s


goos: linux
goarch: amd64
BenchmarkNetworkRequest-8          17644           1448938 ns/op
PASS
ok      command-line-arguments  60.237s
*/

当你使用Pool工作时,记住以下几点:

  1. 当实例化sync.Pool,使用new方法创建一个成员变量,在调用时是线程安全的
  2. 当你收到一个来自Get的实例时,不要对所接收的对象的状态

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

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

相关文章

微服务框架 SpringCloud微服务架构 多级缓存 49 缓存同步 49.3 监听Canal

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 多级缓存 文章目录微服务框架多级缓存49 缓存同步49.3 监听Canal49.3.1 Canal 客户端49 缓存同步 49.3 监听Canal 49.3.1 Canal 客户端 C…

深耕无线通讯细分领域 可信华成产品受市场青睐

深圳市可信华成通信科技有限公司&#xff08;以下简称可信华成&#xff09;&#xff0c;成立于2010年&#xff0c;是一家在无线通信领域中崛起的集研发、智能制造、销售为一体的国家高新技术企业&#xff0c;深圳市专精特新企业&#xff1b; 注册资金2200万元&#xff0c;员工8…

【图像压缩】余弦变换及霍夫曼编码jpeg压缩和解压【含Matlab源码 2086期】

⛄一、DCT图像无损压缩简介 1 图像压缩 图像压缩按照压缩过程中是否有信息的损失以及解压后与原始图像是否有误差可以分为无损压缩和有损压缩两大类。无损压缩是指不损失图像质量的压缩&#xff0c;它是对文件的存储方式进行优化&#xff0c;采用某种算法表示重复的数据信息&a…

网络安全——【收藏】网络设备安全加固规范

一、Cisco网络设备安全基线规范 本建议用于Cisco路由器和基于Cisco IOS的交换机及其三层处理模块&#xff0c;其软件版本为CISCO IOS 12.0及以上版本。加固前应该先备份系统配置文件。 01 账号管理、认证授权 1.1.本机认证和授权 初始模式下&#xff0c;设备内一般建有没有…

Linux——用户、组的管理以及文件的权限设置

一、用户和组 Linux系统中的用户唯一的标识码为用户ID&#xff0c;即UID&#xff0c;每个用户至少属于一个组&#xff0c;即为用户分组。用户分组存在唯一的标识码&#xff0c;为GID。不同的用户拥有不同的权限。 1&#xff0e;认识用户账号文件/etc/passwd和用户影子文件/et…

Java项目:SSM汽车租车管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目包含管理员、普通用户两种角色&#xff1b; 管理员主要功能包括&#xff1a; 后台首页、停车位信息管理、车辆求租信息审核、车辆出租信息…

热门技术中的应用:容器技术中的网络-第31讲-容器网络之Calico:为高效说出善意的谎言

上一节我们讲了Flannel如何解决容器跨主机互通的问题,这个解决方式其实和虚拟机的网络互通模式是差不多的,都是通过隧道。但是Flannel有一个非常好的模式,就是给不同的物理机设置不同网段,这一点和虚拟机的Overlay的模式完全不一样。 在虚拟机的场景下,整个网段在所有的物…

操作系统(3)银行家算法模拟实现

参考博客&#xff1a;银行家算法详解&#xff08;C语言&#xff09;_Sparky*的博客-CSDN博客_银行家问题c语言 1. 效果展示 2. 程序流程图 3. 数据结构设计 /**定义数据结构*/ vector<vector<int>> Max;// 最大需求矩阵 vector<vector<int>> Allocat…

小白如何入门Python爬虫?这是我见过最详细的入门教学

本文针对初学者&#xff0c;我会用最简单的案例告诉你如何入门python爬虫&#xff01; 想要入门Python 爬虫首先需要解决四个问题 熟悉python编程 了解HTML 了解网络爬虫的基本原理 学习使用python爬虫库 01了解什么是爬虫&#xff0c;它的基本流程是什么&#xff1f; 网络…

IDEA 2022 之 Lombok 使用 教程

文章目录**1.Lombok是什么****1.1 Lombok 是什么&#xff1f;****Lombok 引入**2、POM 中引入依赖3、IDE 中安装插件**4. Lombok 使用****4.1 Lombok 使用注意**5.代码案例&#xff1a;**Lombok 原理**6. 常用注解结语1.Lombok是什么 ​ Lombok是使用java编写的一款开源类库。…

【Redis】Redis缓存穿透、缓存雪崩、缓存击穿详解与解决办法(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

Java项目:springboot大学生实习管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本系统的用户可以分为三种&#xff1a;管理员、教师、学生。三种角色登录后会有不同菜单界面&#xff1b; 管理员主要功能&#xff1a; 信息管…

graalvm 拯救生命,速速入手

graalvm 拯救生命&#xff0c;速速入手 标题很夸张&#xff0c;graalvm怎么就拯救生命了&#xff1f;把一个启动5-6秒的项目加速到3秒启动&#xff0c;不就是在拯救生命&#xff0c;拯救发际线吗&#xff1f; 我在上一篇博客"SpringBoot3.0工程建立"末尾启动了工程…

高级网络应用复习——三层热备生成树速端口OSPF实验(带命令)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.知识点总结 路由器热备份技术HSRP &#xff08;思科私有 HS…

学web前端开发和学习其他编程语言一样吗?

前言&#xff1a; web前端是编程中门槛较低&#xff0c;较易入门的&#xff0c;对年龄和学历要求也不是特别高&#xff0c;但如果学历过低&#xff0c;年龄比较大&#xff0c;又完全没有基础&#xff0c;会在学习时感到吃力&#xff0c;另外也会因为用人公司对学历和年龄的限制…

电巢:半导体投资锐减库存调整消费者需求疲软,半导体下行周期何时结束?

前言 投行PitchBook的资料显示截止到本月5日&#xff0c;2022 年全球半导体初创企业的风险投资达到 78 亿美元。与去年创纪录的 145 亿美元投资者注入硅公司的资金相比下降了 46%&#xff0c;与 2020年的103 亿美元相比下降了 24%。 高盛&#xff08;Goldman sachs&#xff09;…

【LSTM回归预测】基于灰狼算法优化长短时记忆GWO-LSTM时序时间序列数据预测(含前后对比)附Matlab代码

​✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法…

Mycat(8):分片详解之取模

1 找到conf/schema.xml修改 2 取模的路由规则 和轮询一样&#xff0c;取模有什么好处&#xff0c;有什么坏处&#xff1f; 优点&#xff1a;利用的写的负载均衡效果&#xff0c;写入速度很快 缺点&#xff1a;批量写入&#xff0c;失败后事务的回滚有难度&#xff01;代表写…

Svelte 带来哪些新思想?赶紧学起来!

本文介绍 点赞 关注 收藏 学会了 Svelte 是我用过最爽的框架&#xff0c;就算 Vue 和 React 再强大&#xff0c;生态再好&#xff0c;我还是更喜欢 Svelte&#xff0c;因为它开发起来真的很爽。 其实在很久之前我就注意到 Svelte &#xff0c;但一直没把这个框架放在心上。…

【Python百日进阶-数据分析】Day133 - plotly饼图:px.pie()实例

文章目录四、实例4.1 带有 plotly express 的饼图4.1.1 欧洲大陆的人口4.1.2 带有重复标签的饼图4.1.3 使用 px.pie 设置饼图扇区的颜色4.1.4 对离散颜色使用显式映射4.1.5 自定义使用 px.pie 创建的饼图4.1.13 Dash 中的饼图四、实例 饼图是一种圆形统计图表&#xff0c;它被…