Go语言并发编程实战:掌握并发模型,提升应用性能

news2024/12/26 11:04:41

1. 引言

1.1 并发编程的重要性

在现代软件开发中,并发编程已经成为了一种不可或缺的技术。随着多核处理器的普及和云计算的兴起,应用程序需要能够有效地利用并发处理能力,以提高性能和用户体验。并发编程使得程序能够在同一时间内处理多个任务,这对于网络应用、数据库操作、实时系统等场景尤为重要。

1.2 Go语言并发模型的优势

在这里插入图片描述

Go语言(也被称为Golang)是由Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go语言的并发模型是其一大特色,具有以下优势:

(1)简洁明了:Go语言的语法简单,易于学习,使得开发者可以更快地投入到并发编程的学习和实践中。

(2)原生支持并发:Go语言在设计之初就充分考虑了并发编程的需求,提供了Goroutine和Channel等原生的并发编程机制。

(3)高效的并发调度:Go语言的运行时(runtime)负责Goroutine的调度,采用了基于时间片的调度算法,能够充分利用多核处理器的计算能力。

(4)轻量级:Goroutine相比于线程,占用的资源更少,切换速度更快,有利于提高应用程序的并发性能。

(5)丰富的并发库:Go语言拥有丰富的并发库,如 sync、context、net/http 等,方便开发者实现各种并发场景的需求。

2. Go语言并发基础

在现代计算机领域,并发编程是提升应用性能的关键之一。Go语言,作为一种现代编程语言,以其强大的并发模型和简洁的语法,受到了开发者的广泛欢迎。本章将带你深入了解Go语言的并发基础,从概念到实际代码,实现并发编程的灵活应用。

2.1 并发编程概念回顾

并发编程允许一个程序在同一时间执行多个任务。并发与并行的区别在于,并发是通过在一个处理器上交替执行任务实现的,而并行是在多个处理器上同时执行任务。在多核处理器盛行的今天,并发编程成为提升应用性能的有效手段。

在并发编程中,常见的模型包括线程、协程和事件驱动模型。传统的线程模型虽然功能强大,但也带来了复杂的同步和共享资源管理问题。协程(如Go中的Goroutine)则提供了更轻量级的解决方案,允许更大规模的并发执行。

2.2 Go语言的Goroutine

在这里插入图片描述

Goroutine是Go语言中的一种轻量级线程管理方式。它们的启动和调度开销极低,可以在一个程序中创建成千上万个Goroutine而不会对系统性能产生重大影响。下面是一个简单的例子,展示如何在Go中创建和使用Goroutine:

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	for i := 0; i < 5; i++ {
		fmt.Println("Hello")
		time.Sleep(time.Millisecond * 500)
	}
}

func main() {
	go sayHello()
	
	for i := 0; i < 5; i++ {
		fmt.Println("World")
		time.Sleep(time.Millisecond * 500)
	}
}

在这个例子中,sayHello函数被作为一个Goroutine启动,同时主函数继续执行。这种并发执行的方式使得程序能够更高效地利用系统资源。

2.3 Go语言的Channel

Channel是Go语言中用于Goroutine之间通信的机制。通过Channel,可以在不同的Goroutine之间传递数据,进而实现协同工作。Channel的使用极大地简化了并发编程中的数据同步问题。

以下是一个示例,展示如何使用Channel在Goroutine之间传递数据:

package main

import (
	"fmt"
)

func sum(s []int, c chan int) {
	total := 0
	for _, v := range s {
		total += v
	}
	c <- total // 将结果发送到channel
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // 从channel中接收结果

	fmt.Println(x, y, x+y)
}

在这个例子中,sum函数计算一个整数切片的和,并将结果通过Channel发送。主函数启动了两个Goroutine,分别计算切片的前半部分和后半部分,然后从Channel中接收结果并求和。这种方式不仅实现了并发计算,还通过Channel确保了数据的正确传递。

3. 并发模型实战:并发下载器

3.1 需求分析

假设我们需要开发一个并发下载器,能够同时下载多个文件,并尽可能地提高下载速度。为了实现这一目标,我们可以利用Go语言的并发特性,启动多个Goroutine来并行下载文件。此外,我们还需要一个机制来管理和跟踪下载任务,以及处理下载完成后的文件保存工作。

3.2 实现并发下载器

下面是一个简单的并发下载器的Go代码实现:

package main

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

func download(url string, wg *sync.WaitGroup) {
	defer wg.Done()
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("Error downloading:", url, err)
		return
	}
	defer resp.Body.Close()

	// 创建文件
	file, err := os.Create(url)
	if err != nil {
		fmt.Println("Error creating file:", url, err)
		return
	}
	defer file.Close()

	// 写入响应体到文件
	_, err = io.Copy(file, resp.Body)
	if err != nil {
		fmt.Println("Error copying response body to file:", url, err)
		return
	}

	fmt.Println("Downloaded:", url)
}

func main() {
	urls := []string{
		"https://example.com/file1.zip",
		"https://example.com/file2.zip",
		"https://example.com/file3.zip",
	}

	var wg sync.WaitGroup
	for _, url := range urls {
		wg.Add(1)
		go download(url, &wg) // 启动Goroutine下载文件
	}
	wg.Wait() // 等待所有下载完成
}

在这个实现中,我们定义了一个download函数,它接受一个URL和一个sync.WaitGroup对象。sync.WaitGroup用于跟踪正在运行的Goroutine数量,并在所有Goroutine完成后进行通知。每个文件下载操作都在一个新的Goroutine中执行,同时主函数继续执行,允许启动更多的下载任务。

3.3 性能测试与分析

为了测试并发下载器的性能,我们可以模拟多个用户同时下载文件的场景。在实际应用中,我们可能需要考虑更多的因素,如网络延迟、服务器响应速度、文件大小等。这里我们简单分析一下代码的并发性能。

在上述代码中,我们启动了三个下载任务。由于下载操作是并行的,理论上,如果服务器能够处理多个并发请求,下载时间将大大缩短。我们可以通过增加下载任务的数量来观察程序的响应时间和资源利用率。

在实际部署时,我们可能还需要考虑以下几点:

  • 错误处理:确保能够妥善处理下载过程中的错误,如网络错误、文件写入错误等。
  • 资源限制:设置合理的资源限制,如最大并发连接数、Goroutine数量等,以避免资源耗尽。
  • 下载管理:可能需要一个更复杂的管理机制来跟踪下载进度、取消任务等。

4. 并发模型实战:实时消息队列

4.1 需求分析

实时消息队列是许多应用的重要组成部分,如消息通知、实时交易数据、日志处理等。在Go语言中,我们可以利用其并发特性来构建一个高效、可靠的实时消息队列。我们需要实现以下功能:

  • 生产者(Producer)可以向队列中发送消息。
  • 消费者(Consumer)可以从队列中读取消息并处理。
  • 队列应能够处理高并发消息,保证消息的顺序性和完整性。

4.2 实现实时消息队列

下面是一个简单的实时消息队列的Go代码实现:

package main

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

type Message struct {
	Data string
}

type Queue struct {
	messages []*Message
	lock     sync.Mutex
}

func (q *Queue) Push(msg *Message) {
	q.lock.Lock()
	q.messages = append(q.messages, msg)
	q.lock.Unlock()
}

func (q *Queue) Pop() (*Message, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if len(q.messages) == 0 {
		return nil, false
	}

	msg := q.messages[0]
	q.messages = q.messages[1:]
	return msg, true
}

func producer(queue *Queue, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 10; i++ {
		msg := &Message{Data: fmt.Sprintf("Message %d", i)}
		queue.Push(msg)
		fmt.Printf("Produced: %s\n", msg.Data)
		time.Sleep(time.Millisecond * 500)
	}
}

func consumer(queue *Queue, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		msg, ok := queue.Pop()
		if !ok {
			break
		}
		fmt.Printf("Consumed: %s\n", msg.Data)
		time.Sleep(time.Millisecond * 1000)
	}
}

func main() {
	queue := &Queue{}
	var wg sync.WaitGroup

	wg.Add(1)
	go producer(queue, &wg)

	wg.Add(1)
	go consumer(queue, &wg)

	wg.Wait()
}

在这个实现中,我们定义了一个Queue类型,它包含一个消息切片messages和一个互斥锁lock,用于保护对队列的并发访问。Push方法用于向队列中添加消息,Pop方法用于从队列中移除并返回第一个消息。

我们创建了两个Goroutine,一个生产者(producer)和一个消费者(consumer)。生产者每500毫秒产生一条消息并推入队列,消费者则不断从队列中取出消息并处理。

4.3 性能测试与分析

为了测试实时消息队列的性能,我们可以模拟高并发生产者和消费者场景。在实际应用中,我们可能需要考虑以下几点:

  • 消息持久化:确保队列在系统崩溃后能够恢复未处理的消息。
  • 消费者恢复:当消费者挂掉后,能够自动恢复,继续处理队列中的消息。
  • 流量控制:在生产者和消费者之间引入流量控制机制,如限流、背压等。

5. 并发模型实战:电商秒杀系统

5.1 需求分析

电商秒杀系统是一种常见的Web应用,允许用户在限定时间内抢购有限的商品。这种系统通常需要处理高并发请求,确保用户能够快速地下单,同时还要防止同一用户重复购买。Go语言的并发特性使得它成为构建高性能秒杀系统的理想选择。

5.2 实现秒杀系统

下面是一个简单的电商秒杀系统的Go代码实现:

package main

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

const (
	maxItems   = 1000
	maxBuyers  = 1000
	itemPrice  = 10
	timeLimit  = 30 * time.Second
	warningMsg = "秒杀结束"
)

func main() {
	items := make([]int, maxItems)
	for i := range items {
		items[i] = i + 1
	}

	var wg sync.WaitGroup
	for i := 0; i < maxBuyers; i++ {
		wg.Add(1)
		go buyItem(items, &wg)
	}
	wg.Wait()

	fmt.Println(warningMsg)
}

func buyItem(items []int, wg *sync.WaitGroup) {
	defer wg.Done()

	// 模拟用户操作
	time.Sleep(time.Duration(randInt(1000)) * time.Millisecond)

	// 尝试购买
	item, err := buy(items)
	if err != nil {
		fmt.Printf("Error buying item: %s\n", err)
		return
	}

	fmt.Printf("Bought item: %d\n", item)
}

func buy(items []int) (int, error) {
	// 模拟支付操作
	time.Sleep(time.Duration(randInt(1000)) * time.Millisecond)

	// 检查商品是否存在
	if len(items) == 0 {
		return 0, fmt.Errorf(warningMsg)
	}

	// 购买商品
	item := items[0]
	items = items[1:]

	return item, nil
}

func randInt(max int) int {
	return int(rand.Float64() * float64(max))
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

在这个实现中,我们定义了一个buy函数,它模拟购买商品的过程。我们使用了一个切片items来模拟商品库存,并通过buy函数来检查和购买商品。每个购买请求由一个Goroutine模拟,它们并行地尝试购买商品。

5.3 性能测试与分析

为了测试电商秒杀系统的性能,我们可以模拟多个用户同时尝试购买商品的场景。在实际应用中,我们可能需要考虑以下几点:

  • 数据库压力:在高并发场景下,数据库可能会成为瓶颈。我们需要确保数据库能够处理大量的并发查询和写入。
  • 缓存机制:使用缓存可以减少对数据库的访问次数,提高系统的响应速度。
  • 限流和反作弊:引入限流和反作弊机制,防止恶意请求和刷单行为。
  • 异步处理:将订单处理、发送通知等操作异步化,以提高系统的吞吐量。

6. Go语言并发进阶

在基本的Go并发编程中,我们通常使用goroutine和channel。然而,为了在复杂的并发环境中更好地控制和优化程序性能,了解更高级的并发模式和同步原语是必不可少的。本章将详细介绍这些进阶内容。

6.1 同步原语:Mutex、RWMutex

Mutex

Mutex(互斥锁)用于在多个goroutine之间安全地访问共享资源。它通过锁定和解锁操作确保同时只有一个goroutine可以访问某个资源,从而避免数据竞争。

示例代码:

package main

import (
	"fmt"
	"sync"
)

var (
	counter int
	mu      sync.Mutex
)

func increment(wg *sync.WaitGroup) {
	defer wg.Done()
	mu.Lock()
	counter++
	mu.Unlock()
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go increment(&wg)
	}
	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

RWMutex

RWMutex(读写互斥锁)是一种允许多goroutine同时读取但只允许一个goroutine写入的锁。它提高了读操作的并发性。

示例代码:

package main

import (
	"fmt"
	"sync"
)

var (
	counter int
	rwMu    sync.RWMutex
)

func readCounter(wg *sync.WaitGroup) {
	defer wg.Done()
	rwMu.RLock()
	defer rwMu.RUnlock()
	fmt.Println("Counter:", counter)
}

func increment(wg *sync.WaitGroup) {
	defer wg.Done()
	rwMu.Lock()
	counter++
	rwMu.Unlock()
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go increment(&wg)
	}
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go readCounter(&wg)
	}
	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

6.2 并发模式:Worker Pool、Task Queue

Worker Pool

Worker Pool模式通过预创建一组工作goroutine来处理任务,从而限制并发任务的数量,优化资源使用。

示例代码:

package main

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

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
	defer wg.Done()
	for job := range jobs {
		fmt.Printf("Worker %d processing job %d\n", id, job)
		time.Sleep(time.Second) // 模拟处理时间
		results <- job * 2
	}
}

func main() {
	const numJobs = 5
	const numWorkers = 3

	jobs := make(chan int, numJobs)
	results := make(chan int, numJobs)

	var wg sync.WaitGroup

	for w := 1; w <= numWorkers; w++ {
		wg.Add(1)
		go worker(w, jobs, results, &wg)
	}

	for j := 1; j <= numJobs; j++ {
		jobs <- j
	}
	close(jobs)

	go func() {
		wg.Wait()
		close(results)
	}()

	for result := range results {
		fmt.Println("Result:", result)
	}
}

Task Queue

Task Queue模式是通过将任务放入队列中并由工作goroutine从队列中取出任务进行处理的并发模式。

示例代码:

package main

import (
	"fmt"
	"sync"
)

type Task struct {
	ID int
}

func worker(id int, tasks <-chan Task, wg *sync.WaitGroup) {
	defer wg.Done()
	for task := range tasks {
		fmt.Printf("Worker %d processing task %d\n", id, task.ID)
	}
}

func main() {
	const numWorkers = 3
	tasks := make(chan Task, 10)

	var wg sync.WaitGroup

	for w := 1; w <= numWorkers; w++ {
		wg.Add(1)
		go worker(w, tasks, &wg)
	}

	for i := 1; i <= 10; i++ {
		tasks <- Task{ID: i}
	}
	close(tasks)

	wg.Wait()
}

6.3 并发安全:如何避免竞态条件

竞态条件是指两个或多个goroutine在没有正确同步的情况下访问共享数据,导致数据不一致或程序行为异常。避免竞态条件的关键是正确使用同步原语。

使用Mutex避免竞态条件

如前文所示,通过使用sync.Mutex可以确保对共享资源的互斥访问,从而避免竞态条件。

使用Channel避免竞态条件

Channel是一种在goroutine之间传递数据的安全方式,可以避免竞态条件。

示例代码:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	dataChan := make(chan int)

	// 写入goroutine
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			dataChan <- i
		}
		close(dataChan)
	}()

	// 读取goroutine
	wg.Add(1)
	go func() {
		defer wg.Done()
		for data := range dataChan {
			fmt.Println("Received:", data)
		}
	}()

	wg.Wait()
}

通过使用channel,我们可以确保数据在goroutine之间的安全传递,避免了显式的锁操作。

7. 实战案例解析:微服务架构下的并发通信

微服务架构是一种流行的应用开发方法,它将单一应用程序划分为一组小的服务,每个服务运行在其独立的进程中,服务之间通过轻量级的通信机制(通常是HTTP RESTful API或gRPC)进行交互。

7.1 微服务架构简介

微服务架构的核心理念是将应用拆分成多个独立、松耦合的服务,每个服务负责一个单一的功能。这些服务可以独立部署和扩展,有助于提高系统的可维护性和可伸缩性。

7.2 基于Go语言的RPC通信

在微服务架构中,服务之间的通信至关重要。Go语言的RPC框架(如gRPC)提供了高性能、跨语言的服务间通信能力。

示例代码:

首先,定义服务接口和数据结构:

// server.proto
syntax = "proto3";

option go_package = "example.com/rpc";

service CalculatorService {
  rpc Add (AddRequest) returns (AddResponse);
  rpc Subtract (SubtractRequest) returns (SubtractResponse);
}

message AddRequest {
  int64 a = 1;
  int64 b = 2;
}

message AddResponse {
  int64 result = 1;
}

message SubtractRequest {
  int64 a = 1;
  int64 b = 2;
}

message SubtractResponse {
  int64 result = 1;
}

然后,生成Go代码:

protoc --go_out=plugins=grpc:. server.proto

最后,实现服务:

package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "example.com/rpc/server"
)

type server struct{}

func (s *server) Add(ctx context.Context, in *pb.AddRequest) (*pb.AddResponse, error) {
	return &pb.AddResponse{Result: in.A + in.B}, nil
}

func (s *server) Subtract(ctx context.Context, in *pb.SubtractRequest) (*pb.SubtractResponse, error) {
	return &pb.SubtractResponse{Result: in.A - in.B}, nil
}

func main() {
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterCalculatorServiceServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}

7.3 分布式锁与同步

在分布式系统中,分布式锁是一种重要的同步机制,用于在多个服务之间协调对共享资源的访问。Go语言中,我们可以使用sync包中的RWMutex来实现分布式锁。

示例代码:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"sync"

	"github.com/gorilla/mux"
)

var (
	mu      sync.RWMutex
	counter int
)

func increment(wg *sync.WaitGroup) {
	defer wg.Done()
	mu.RLock()
	counter++
	mu.RUnlock()
}

func decrement(wg *sync.WaitGroup) {
	defer wg.Done()
	mu.Lock()
	counter--
	mu.Unlock()
}

func main() {
	r := mux.NewRouter()
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go increment(&wg)
	}

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go decrement(&wg)
	}

	wg.Wait()
	fmt.Println("Final Counter:", counter)

	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8000", nil))
}

在这个例子中,我们使用RWMutex来保护counter变量的读写操作。虽然这个例子是在单个进程中运行的,但其思想可以扩展到分布式系统中,通过在不同的服务实例之间实现类似的锁机制来协调对共享资源的访问。

8. 总结

Go语言的并发模型是它的一大亮点,它让开发者能够轻松地创建并发应用程序。在本篇文章中,我们通过实战代码深入了解了Go语言的并发编程,并学习了如何将其应用于实际项目中。

8.1 Go语言并发模型在实际应用中的优势

  1. 轻量级goroutine:goroutine是Go语言中的轻量级线程,它们在用户态中运行,由Go运行时管理,因此创建和切换goroutine的开销非常小。

  2. 内置的channel:channel是Go语言中用于goroutine间通信的内置类型,它们提供了同步和数据传递的功能,是实现并发编程的关键。

  3. 丰富的同步原语:Go提供了多种同步原语,如Mutex、RWMutex、WaitGroup等,它们可以帮助开发者避免竞态条件和实现复杂的并发控制。

  4. 高效的任务队列:通过使用Worker Pool和Task Queue模式,可以有效地管理并发任务,提高程序的吞吐量。

  5. 简化的并发模式:Go语言的并发模型使得构建复杂的并发应用变得更加简单,开发者可以专注于业务逻辑,而不是并发控制。

  6. 跨平台的一致性:Go语言的并发模型在不同的操作系统和硬件平台上都有很好的表现,这意味着开发者不需要担心并发代码在不同环境下的兼容性问题。

8.2 面向未来的并发编程趋势

随着微服务、容器化和云计算的不断发展,并发编程在现代应用开发中变得越来越重要。以下是并发编程的一些未来趋势:

  1. 异步编程:异步编程将变得更加普及,它允许开发者编写非阻塞的代码,提高应用程序的响应性和吞吐量。

  2. 服务网格:服务网格(如Istio、Linkerd)将成为微服务架构中的关键组件,它们提供了服务间通信的抽象层,简化了分布式系统的复杂性。

  3. 函数即服务(FaaS):FaaS将使得开发者能够通过事件驱动的方式,将业务逻辑作为函数部署到云平台上,实现更细粒度的并发控制和资源管理。

  4. 并发编程语言的发展:随着并发编程变得越来越重要,未来的编程语言可能会提供更多的内置并发支持,使开发者能够更容易地构建高性能的并发应用。

  5. 安全性:随着攻击者利用并发编程中的漏洞,安全性将成为并发编程中的一个重要考虑因素。开发者需要确保他们的并发代码不会成为安全漏洞的源头。

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

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

相关文章

《MySQL 数据库》 安装、配置、字符集修改 —/—<1>

一、MySQL介绍 1、介绍MySQL 数据库是数据的仓库&#xff0c;与普通的数据仓库不同的是,数据库依据数据结构来组织数据,因为数据结构的存在,所以看到的数据是条理化的 数据库和普通文件系统的区别在与&#xff1a;数据库拥有数据结构,能都快速查找对应的数据 常说的MySQL数据库…

Pytorch-张量的创建

&#x1f308;个人主页&#xff1a; 羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 简介&#xff1a; 一个Python深度学习框架&#xff0c;它将数据封装成张量&#xff08;Tensor&#xff09;进行处理&#xff0c;Python中的张量就是元素为同一种数据类型的多维…

南京观海微电子----静电放电ESD保护设计方案

ESD 保护方案 图 5- 2 所示的片上保护设计概念被用来避免来自几乎随机组合的引脚之间的HBM / MM ESD 应力损伤。对每一个输入或输出引脚&#xff0c; 在压焊点与 VDD 和 VSS 电源线之间采用 ESD 钳位器件&#xff0c; 用于泄放 I / O 引脚上的四种模式的 ESD 应力。为了克服引脚…

Qt题目知多少-3

1.事件与信号的区别 使用场合和时机不同 一般情况下&#xff0c;在“使用”窗口部件时&#xff0c;我们经常需要使用信号&#xff0c;并且会遵循信号与槽的机制&#xff1b;而在“实现”窗口部件时&#xff0c;我们就不得不考虑如何处理事件了。举个例子&#xff0c;当使用 QPu…

界面组件Kendo UI for Vue 2024 Q2亮点 - 发布一系列新组件

随着最新的2024年第二季度发布&#xff0c;Kendo UI for Vue为应用程序开发设定了标准&#xff0c;包括生成式AI集成、增强的设计系统功能和可访问的数据可视化。新的2024年第二季度版本为应用程序界面提供了人工智能(AI)提示&#xff0c;从设计到代码的生产力增强、可访问性改…

算法23:寻找旋转排序数组中的最小值

寻找旋转排序数组中的最小值. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/ 这个题乍一看可以用二分查找&#xff0c;并且…

C#——使用S7netplus包实现西门子s7协议

使用S7netplus包实现西门子s7协议 S7netplus是一个用于与Siemens S7 PLC进行通信的C#库。它提供了一种简单的方式来读取和写入PLC中的数据。 安装 可以通过NuGet包管理器来安装。 S7netplus的使用 一、连接 // CpuType&#xff1a;PLC的CPU型号&#xff0c;咱用的这个设备…

write_sdc和write_script区别

文章目录 一、set_disable_clock_gating_check二、write_sdc和write_script区别1. write_sdc2. write_script 一、set_disable_clock_gating_check set_disable_clock_gating_check对指定的cell/pin/lib_cell/lib_pin设置是否进行clock gating的时序检查。 对于工具插入或者…

C语言——计算1-1/2+1/3-...+1/99-1/100+...相似题目集合

题目一&#xff1a; #include<stdio.h> #include<math.h>int main() {int n 1;float sum 0, term 1, sign 1;while (fabs(term) > 1e-4){term sign / n;sum term;sign -sign;n;}printf("sum%f", sum);return 0; } 题目二&#xff1a; #include…

服务器数据恢复—Raid故障导致存储中数据库数据丢失的数据恢复案例

服务器存储数据恢复环境&故障情况&#xff1a; 一台光纤存储中有一组由16块硬盘组成的raid。 该存储出现故障导致数据丢失。RAID中2块盘掉线&#xff0c;还有1块盘smart状态为“警告”。 服务器存储数据恢复过程&#xff1a; 1、通过该存储自带的存储管理软件将当前存储的完…

关于之前写的一篇“快速构建MES系统”引发的争议,后续来了~~

之前因写了一篇用低代码开发平台快速搭建MES系统&#xff0c;结果被大量的网友骂得体无完肤&#xff0c;不过&#xff0c;默默点赞、默默收藏、默默分享的用户还是大有人在。 在某些方面&#xff0c;或许在语言表达上不够精确&#xff0c;不够准确&#xff0c;这里也给各位看官…

免费微博批量取关工具

下载地址&#xff1a;https://pan.quark.cn/s/1d507dcfffe9 软件功能 批量删除微博批量取消关注批量移除粉丝批量取消收藏批量取消赞批量删除评论

<数据集>停车场空位识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;12416张 标注数量(xml文件个数)&#xff1a;12416 标注数量(txt文件个数)&#xff1a;12416 标注类别数&#xff1a;2 标注类别名称&#xff1a;[space-empty, space-occupied] 序号类别名称图片数框数1space-occ…

科技驱动健康,景联文科技提供高质量高血压数据采集

当前&#xff0c;穿戴手表市场呈现出快速发展趋势&#xff0c;已成为可穿戴设备领域的一个重要组成部分。市场上智能手表的厂商包括小米、华为、苹果、步步高、vivo、努比亚、三六零、科大讯飞、等。 高血压数据采集可为高血压的预防提供支持&#xff0c;持续监测可以帮助用户及…

云原生课程-k8s基础系列-k8s实战之pod详解

pod是可以在k8s中创建和管理的&#xff0c;最小的可部署的计算单元&#xff0c;pod(就像在豌豆荚中&#xff09;是一组&#xff08;一个或多个&#xff09;容器&#xff1b;这些容器共享存储&#xff0c;网络&#xff0c;以及怎样运行这些容器的声明。 创建pod kubectl run my…

【VScode编辑器】VScode基本使用全面讲解

1&#xff0c;VScode是什么&#xff1f; Visual Studio Code 简称 VS Code&#xff0c;是一款跨平台的、免费且开源的现代轻量级代码编辑器。它功能强大&#xff0c;提供了丰富的功能和扩展性&#xff0c;几乎支持主流开发的特性。 VSCode里面有对应的插件安装功能&#xff0c;…

基于51单片机的智能车库系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1Pw9pbytbYqCoecGxSFNqpg 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

【网络爬虫篇】逆向实战—某东:滑块验证码(逆向登录)2024.8.7最新发布,包干货,包详细

【网络爬虫篇】更多优秀文章借鉴&#xff1a; 1. 使用Selenium实现黑马头条滑块自动登录 2. 使用多线程采集爬取豆瓣top250电影榜 3. 使用Scrapy爬取去哪儿网游记数据 4. 数据采集技术综合项目实战1&#xff1a;国家水稻网数据采集与分析 5. 数据采集技术综合项目实战2&#x…

电子画册设计源码系统:轻松制作各行各业在线画册展示 带完整的安装代码包以及搭建教程

在信息化时代&#xff0c;传统的纸质画册已经无法满足现代人的阅读需求。而电子画册具有信息更新迅速、展示方式多样、交互性强等优点&#xff0c;能够更好地满足用户对于信息的获取和展示需求。同时&#xff0c;随着移动互联网的普及&#xff0c;用户对于在线浏览和分享的需求…

GEE:设置自定义风格底图

作者&#xff1a;CSDN _养乐多_ 本文将介绍如何在 Google Earth Engine &#xff08;GEE&#xff09;平台上&#xff0c;设置底图风格。结果如下图所示&#xff0c; 文章目录 一、代码二、完整代码链接 一、代码 以下代码将GEE的底图根据自己的需求设置风格。 var land { …