Golang基础7-并发编程

并发编程

https://www.cnblogs.com/Survivalist/p/11527949.html

进程和线程、协程的区别_线程协程进程的区别-CSDN博客

Golang中的并发编程是一个重点,我们要了解Golang中的并发Goroutine因此需要先理解进程、线程、之后再理解协程。

进程:操作系统进行资源分配的最小单元,是程序在执行过程中的一次活动,包括程序,数据集合,程序控制块(PCB)等。每个进程都有独立的内存空间,包括代码、数据、堆栈,因此进程之间相互隔离。进程切换开销大(包括栈、寄存器、页表、文件句柄等切换)。尽管更安全,但也占据了较多系统资源

线程:操作系统进行调度的最小单元,是进程中执行的基本单元,由线程ID,当前指令指针PC,寄存器和堆栈组成。一个进程包含>=1个线程,其中一个为主线程,多个线程时通过共享内存,上下文切换较快,资源开销较小。共享内存尤其需要注意线程同步互斥问题。

协程:用户轻量级线程,由程序控制,不被操作系统内核管理

更轻量,独立栈空间(协程之间不共享内存,但是可以通信(channel)进行交互),更易并发(高效切换无需过多锁)

并行和并发区别

goroutine

参考文章:Goroutine · Golang 学习笔记

每个goroutine是官方实现的超级"线程池"

每个实例4-5KB栈内存和实现机制大幅减少创建和销毁使得go更易实现高并发

goroutine奉行通信(配合channel)实现共享内存。

在go语言层面内置调度和上下文切换机制,并且go程序会智能地将任务合理的分配给CPU

简单demo
package main

import (
    "fmt"
    "time"
)

func Hello() {
    fmt.Println("Hello Function!")
}

func main() {
    //在函数前加入关键字go
    go Hello()
    fmt.Println("Main done")
    //休眠,等go Hello()执行完
    time.Sleep(time.Second)
}

sync.WaitGroup demo
  1. WaitGroup用来启动一组goroutine,等待任务做完再结束goroutine。
  2. wg.Add(delta int):设置将要启动的Goroutine的数量,来设置WaitGroup内部计数器
  3. wg.Done():每个goroutine完成后,计数器-1 ;对于可能panic的可以使用defer wg.Done()
  4. wg.Wait():阻塞自己,等待所有goroutine完成任务,计数器减为0,返回

sync.WaitGroup中的Add和Done线程安全,可以从多个groutine中调用这两个方法,不用担心数据竞争和其他并发问题。

package main

import (
    "fmt"
    "sync"
)
//启动多个goroutine
func main() {
    //协程同步
    var wg sync.WaitGroup
    wg.Add(9)

    for i := 0; i < 9; i++ {
        //当作参数传入会拷贝一份,因此可以保证输出0-8
        go func(i int) {
            defer  wg.Done()
            fmt.Printf("%d ",i)
        }(i)
    }
    // 阻塞主程序,等待所有 Goroutine 完成
    wg.Wait()
}

输出结构并发乱序。0-9的其中一个组合

sync.Map demo
    1. sync.Map并发安全的sync.Map,可以安全并发的读写操作,常见操作见代码
    2. 与之相对应的原生map,线程不安全,并发读写时需要加锁
package main

import (
    "fmt"
    "sync"
)

func main() {
    //sync.Map的key和value都是interface{}
    var m sync.Map

    //写入
    m.Store("1", 18)
    m.Store("2", 20)

    //读取
    age, ok := m.Load("1")
    if ok {
        fmt.Println("读取成功", age, ok)
    } else {
        fmt.Println("读取失败!")
    }

    //遍历!!
    m.Range(func(key, value interface{}) bool {
        fmt.Println("遍历:key=", key, " value=", value)
        return true
    })

    //根据key删除
    m.Delete("2")
    age, ok = m.Load("2")
    if ok {
        fmt.Println("删除后读取成功", age, ok)
    } else {
        fmt.Println("删除后读取失败!")
    }

    //存在则读取否则写入
    //如果存在key=2,ok返回为true,否则false
    age, ok = m.LoadOrStore("2", "100")
    if ok {
        fmt.Println("已存在的:", age)
    } else {
        fmt.Println("不存在,store后的:", age)
    }

}

map并发 demo
    • 原生map实现并发时一定需要加锁来保证安全,不然报错。
    • sync.Map安全Map,不需要上锁解锁操作。
package main

import (
    "fmt"
    "sync"
)

func main() {
    //没有加锁的并发写入,则会报错
    m := make(map[int]int)

    var wg sync.WaitGroup
    var mu sync.Mutex

    for i := 0; i < 9; i++ {
        wg.Add(1)

        go func(i int) {
            for j := 0; j < 9; j++ {
                //上锁
                mu.Lock()
                m[j] = i
                mu.Unlock()
            }
            wg.Done()
        }(i)

    }
    

    //    安全Map
    var sm sync.Map
    for i := 0; i < 9; i++ {
        wg.Add(1)
        go func(i int) {
            for j := 0; j < 9; j++ {
                sm.Store(j, i)
            }
            wg.Done()
        }(i)

    }
    //完成前面并发任务后输出
    wg.Wait()
    
    fmt.Println("最终打印map值:", m)
    fmt.Print("最终打印sync.Map值:")
    sm.Range(func(key, value interface{}) bool {
        fmt.Printf("%d:%d ", key, value)
        return true
    })

}

go的GMP调度原理

channel

go中不要通过共享内存来通信,而是通过通信来共享内存。

参考:Go Channel 详解

go高性能编程:GitHub - wuqinqiang/Go_Concurrency: go concurrency class code

go语言核心类型,管道,并发中可以进行发送或接收数据进行通信。

<-

使用make创建channel,chan底层是一个环形数组

类型:chan chan <- <-chan

使用场景:

    • 消息传递、消息过滤
    • 信号广播
    • 事件的订阅和广播
    • 任务分发
    • 结果汇总
    • 并发控制
    • 同步和异步

简单demo

无缓冲channel(B要第一时间知道A是否完成)、有缓冲channel(生产者消费者模型)

package main

import (
    "fmt"
    "sync"
)

func main() {
    c := make(chan int, 2)
    defer close(c)

    var wg sync.WaitGroup

    wg.Add(1)

    go func() {
        defer wg.Done()
        c <- 3 + 4
        c <- 1
        fmt.Println("发送成功")
    }()

    wg.Add(1)
    go func() {
        defer wg.Done()
        c <- 100
    }()
    
    //wg.Wait()
    i := <-c
    j := <-c
    _ = <-c//忽略这个值
    fmt.Println(i, j)

    wg.Wait()

}

import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}
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 // receive from c
    fmt.Println(x, y, x+y)
}

channel的range

func main() {
    go func() {
        time.Sleep(1 * time.Hour)
    }()
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i = i + 1 {
            c <- i
        }
        close(c)
    }()
    for i := range c {
        fmt.Println(i)
    }
    fmt.Println("Finished")
}

这个range会一直从c中获取,直到c关闭

select demo

类似与linux中io的select、poll、epoll。

select语句类似于switch,随机执行一个可执行的case,select只用于通信操作,如果没有case可运行那么将阻塞,直到有case可运行。默认的字句总是可以运行。

    • 每个case都必须是一个通信
    • 所有channel表达式都会被求值
    • 所有被发送的表达式都会被求值
    • 如果任意某个通信可以进行,它就执行;其他被忽略。
    • 如果有多个case都可以运行,Select会随机地选出一个执行。其他不会执行。
    • 否则:如果有default子句,则执行该语句。
    • 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
package main

import "fmt"

//select用于退出
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    } 
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}
timeout demo
package main

import "time"
import "fmt"

func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    //超时退出
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}
单向通道 demo

send chan <- string//只能发送给send

read <-chan string// 只能读取read

package main

import (
    "fmt"
    "time"
)

func Produce(out chan<- int) {
    for i := 0; i < 10; i++ {
        out <- i * i
    }

}

func Consumer(in <-chan int) {
    for num := range in {
        fmt.Println(num)
    }
}

func main() {
    c := make(chan int, 0)

    go Produce(c)
    go Consumer(c)

    time.Sleep(time.Second)
}

package main

import "fmt"

// 只能发送给管道
func Counter(out chan<- int) {
    for i := 0; i < 10; i++ {
        out <- i
    }
    close(out)
}

// chan <-  只能发送给管道     <-chan 管道发送嘛,因此只能接收
func Squarer(out chan<- int, in <-chan int) {
    for i := range in {
        out <- i * i
    }
    close(out)
}

func Printer(in <-chan int) {
    for i := range in {
        fmt.Println(i)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go Counter(ch1)
    go Squarer(ch2, ch1)
    Printer(ch2)
}

输出0-9的平方。

协程池demo

Golang学习篇——协程池_golang 携程池-CSDN博客

package main

import (
    "fmt"
    "math/rand"
    "sync"
)

// 当前task
type Task struct {
    Id     int
    Random int
}

// 结果
type Result struct {
    Task *Task
    Sum  int
}

// 创建Task
func CreateTask(taskChan chan<- *Task, wg *sync.WaitGroup) {
    defer wg.Done()
    for id := 0; id < 100000; id++ {
        //创建Task
        task := &Task{
            Id:     id,
            Random: rand.Intn(200) + 1,
        }
        // 传递给taskChan管道
        taskChan <- task
    }
    close(taskChan)

}

// 创建线程池来处理
func CreatePool(num int, taskChan <-chan *Task, resultChan chan<- *Result, wg *sync.WaitGroup) {
    for i := 0; i < num; i++ {
        wg.Add(1)
        // 创建多个goroutine并发
        go func() {
            for task := range taskChan {
                // 当前的Num
                currentNum := task.Random
                sum := 0
                // 计算sum的值
                for currentNum != 0 {
                    temp := currentNum % 10
                    sum += temp
                    currentNum /= 10
                }
                // 此时任务的结果是:
                currentResult := &Result{
                    Task: task,
                    Sum:  sum,
                }
                // 发送给结果管道
                resultChan <- currentResult
            }
            wg.Done()
        }()
    }

}

// 开启打印 Result
func PrintResult(resultChan <-chan *Result) {
    //输出
    for res := range resultChan {
        fmt.Printf("输出结果,Id:=%d,Random:=%d,Sum:=%d\n", res.Task.Id, res.Task.Random, res.Sum)
    }
}

func main() {
    // 创建task管道,传递task
    taskChan := make(chan *Task, 128)

    // 结果管道
    resultChan := make(chan *Result, 128)

    // 确保goroutine全部完成
    var wg sync.WaitGroup

    wg.Add(1)
    go CreateTask(taskChan, &wg)

    // 创建协程池
    CreatePool(133, taskChan, resultChan, &wg)

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

    // 创建协程进行打印
    PrintResult(resultChan)

}

channel一定注意防止被阻塞而导致程序出现死锁!!!

并发安全和锁

互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。sync.Mutex

sync.Mutex互斥锁demo

多个goroutine对同一个共享资源(当前的x)的竞争你,x=x+1,在汇编当中并不是原子性的操作,因此并发时会导致数据不一致,方法1,上锁。

package main

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

var (
    total int32
    wg    sync.WaitGroup
    mutex sync.Mutex
)

func Add() {
    defer wg.Done()

    for i := 0; i < 10000; i++ {
        //原子操作
        atomic.AddInt32(&total, 1)
        //mutex.Lock()
        //total++
        //mutex.Unlock()
    }

}

func Del() {
    defer wg.Done()

    for i := 0; i < 10000; i++ {
        atomic.AddInt32(&total, -1)
        //mutex.Lock()
        //total--
        //mutex.Unlock()
    }

}

func main() {

    fmt.Println("origin num:", total)

    wg.Add(2)

    go Add()
    go Del()

    wg.Wait()
    fmt.Println("After num:", total)
}
package main

import (
    "fmt"
    "sync"
)

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func Add() {
    for i := 0; i < 50; i++ {
        lock.Lock()
        x = x + 1
        lock.Unlock()
    }
    wg.Done()
}

func main() {
    wg.Add(2)
    go Add()
    go Add()
    wg.Wait()
    fmt.Println(x)
}

读写互斥锁 demo
package main

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

var (
    x      int64
    wg     sync.WaitGroup
    lock   sync.Mutex
    rwlock sync.RWMutex //读写互斥锁
)

func Write() {
    //lock.Lock() //加互斥锁
    rwlock.Lock()
    x = x + 1
    time.Sleep(10 * time.Millisecond)
    rwlock.Unlock()
    //lock.Unlock() //解互斥锁
    wg.Done()
}

func Read() {
    //lock.Lock()
    rwlock.RLock()
    time.Sleep(time.Millisecond)

    rwlock.RUnlock()
    //lock.Unlock()
    wg.Done()
}

func main() {
    start := time.Now()

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go Write()
    }

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

    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
}

sync

前面介绍过sync的一些方法

sync.WaitGroup

sync.Once

参考:Go sync.Once | Go 语言高性能编程 | 极客兔兔

执行一次的函数,可以在代码任意位置加载,常用于单例模式(懒汉式),并发场景安全。而init是package首次执行时加载(饿汉式)

对外接口:func (o *Once) Do(f func())

sync.Map

这个是并发安全的Map

原子操作

package main

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

var x int64
var l sync.Mutex
var wg sync.WaitGroup

// 普通版加函数
func add() {
    // x = x + 1
    x++ // 等价于上面的操作
    wg.Done()
}

// 互斥锁版加函数
func mutexAdd() {
    l.Lock()
    x++
    l.Unlock()
    wg.Done()
}

// 原子操作版加函数
func atomicAdd() {
    atomic.AddInt64(&x, 1)
    wg.Done()
}

func main() {
    start := time.Now()
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        //go add() // 普通版add函数 不是并发安全的
        //go mutexAdd() // 加锁版add函数 是并发安全的,但是加锁性能开销大
        go atomicAdd() // 原子操作版add函数 是并发安全,性能优于加锁版
    }
    wg.Wait()
    end := time.Now()
    fmt.Println(x)
    fmt.Println(end.Sub(start))
}

Context

context详解:https://www.cnblogs.com/juanmaofeifei/p/14439957.html

Go 语言并发编程与 Context | Go 语言设计与实现

Context是用来用来处理goroutine,可以在多个goroutine中传递取消信号、超时等。

通俗的解释:Context · Go语言中文文档

由于golang的server在goroutine当中,context就是有效管理这些goroutine,相互调用的goroutine之间通过传递context变量保持关联,这样在不用暴露各goroutine内部实现细节的前提下,有效地控制各goroutine的运行。

引入--退出goroutine
方式1,采用全局变量
package main

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

var wg sync.WaitGroup

// 退出全局变量
var stop bool

func worker() {
    defer wg.Done()
    for {
        if stop {
            break
        }
        time.Sleep(time.Second)
        fmt.Println("worker")
    }
}

func main() {
    wg.Add(1)
    go worker()

    time.Sleep(3 * time.Second)
    stop = true
    wg.Wait()
}
方式2,采用管道通信
package main

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

var wg sync.WaitGroup

var ch = make(chan struct{})

func worker() {
    defer wg.Done()
LOOP:
    for {

        select {
        case <-ch:
            fmt.Println("exit")
            break LOOP
        default:
            time.Sleep(time.Second)
            fmt.Println("worker")
        }

    }
}

func main() {
    wg.Add(1)
    go worker()

    time.Sleep(3 * time.Second)
    ch <- struct{}{}
    wg.Wait()
}
方式3,采用context
package main

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

var wg sync.WaitGroup

func worker(ctx context.Context) {
    defer wg.Done()
LOOP:
    for {

        select {
        case <-ctx.Done():
            fmt.Println("exit")
            break LOOP
        default:
            time.Sleep(time.Second)
            fmt.Println("worker")
        }

    }
}

func main() {
    wg.Add(1)
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx)

    time.Sleep(3 * time.Second)
    cancel() //等待子routine结束

    wg.Wait()
}

如果函数当中需要被控制、超时、传递时,但不希望改变原来的接口时,函数第一个参数传入ctx。

context

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

WithDeadline
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    d := time.Now().Add(50 * time.Millisecond)
    ctx, canel := context.WithDeadline(context.Background(), d)

    // 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。
    // 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
    defer canel()

    select {
    case <-time.After(10 * time.Millisecond):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }
}

WithTimeout
package main

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

var wg sync.WaitGroup

func worker(ctx context.Context) {
    defer wg.Done()
LOOP:
    for {

        select {
        case <-ctx.Done():
            fmt.Println("exit")
            break LOOP
        default:
            time.Sleep(time.Second)
            fmt.Println("worker")
        }

    }
}

func main() {
    wg.Add(1)

    //超时控制
    ctx, _ := context.WithTimeout(context.Background(), 6*time.Second)
    go worker(ctx)

    time.Sleep(3 * time.Second)
    wg.Wait()
}
WithValue
package main

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

var wg sync.WaitGroup

func worker(ctx context.Context) {
    //拿到key,value
    fmt.Printf("traceid:%s\n", ctx.Value("traceid"))
    //记录一些日志等等,方便排查

    defer wg.Done()
LOOP:
    for {

        select {
        case <-ctx.Done():
            fmt.Println("exit")
            break LOOP
        default:
            time.Sleep(time.Second)
            fmt.Println("worker")
        }

    }
}

func main() {
    wg.Add(1)

    //超时控制
    ctx, _ := context.WithTimeout(context.Background(), 6*time.Second)

    //传递一些值,后续可能链路追踪id
    childCtx := context.WithValue(ctx, "traceid", "123456")

    go worker(childCtx)

    time.Sleep(3 * time.Second)
    wg.Wait()
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

某翻译平台翻译接口逆向之webpack学习

逆向网址 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw 逆向链接 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLyMv 逆向接口 aHR0cHM6Ly9kaWN0LnlvdWRhby5jb20vd2VidHJhbnNsYXRl 逆向过程 请求方式 POST 逆向参数 sign c168e4cb76169e90f82d28118dbd24d2 接口请求结果解密 过程分析 根据XHR…

免费获取!遗传算法+多目标规划算法+自适应神经模糊系统程序代码!

前言 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世纪70年代提出&#xff0c;该算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型&#xff0c;通过数学的方式&#xff0c;将问题的求解过程转…

全国省级金融发展水平数据集(2000-2022年)

01、数据简介 金融发展水平是一个国家或地区经济实力和国际竞争力的重要体现。它反映了金融体系的成熟程度和发展水平&#xff0c;是衡量一个国家或地区经济发展质量的重要指标。金融发展水平的提高&#xff0c;意味着金融体系能够更好地服务实体经济&#xff0c;推动经济增长…

3.7设计模式——Observer 观察者模式(行为型)

意图 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于他的对象都得到通知并被自动更新。 结构 Subject&#xff08;目标&#xff09;知道它的观察者&#xff0c;可以有任意多个观察者观察同一个目标&#xff0c;提供注册和删…

模块三:二分——153.寻找旋转排序数组中的最小值

文章目录 题目描述算法原理解法一&#xff1a;暴力查找解法二&#xff1a;二分查找疑问 代码实现解法一&#xff1a;暴力查找解法二&#xff1a;CJava 题目描述 题目链接&#xff1a;153.寻找旋转排序数组中的最小值 根据题目的要求时间复杂度为O(log N)可知需要使用二分查找…

电子负载仪的远端控制

前言 最近研究了电子负载仪的远端控制&#xff08;区别于前面板控制&#xff09;&#xff0c;主要是用于程序控制&#xff0c;避免繁琐复杂的人工控制&#xff0c;举了南京嘉拓和艾维泰科的例子。 有纰漏请指出&#xff0c;转载请说明。 学习交流请发邮件 1280253714qq.com …

基于JavaWEB的学生考勤管理系统(含论文)

本系统是用Java语言写的&#xff0c;基于JavaWEB的学生考勤管理系统 主要有三大模块&#xff0c;学生&#xff0c;教师和管理员模块&#xff0c;功能如下&#xff1a; 学生模块 教师模块&#xff1a; 管理员模块

深入了解Semaphore、CountDownLatch等实用工具的用法

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

4月26(信息差)

&#x1f30d; 1170万台 华为跃升重回首位&#xff01;苹果跌至第五位 &#x1f384;工业软件大事件 —— OGG 1.0 发布&#xff0c;华为贡献全部源代码 ✨ 苹果发布 OpenELM&#xff1a;专为在设备端运行而设计的小型开源 AI 模型 1.FisheyeDetNet&#xff1a;首个基于鱼眼相…

LED驱动模块RSC6218A 5W-18W迷你高效驱动电源应用-REASUNOS(瑞森半导体)

一、LED驱动模块RSC6218A REASUNOS(瑞森半导体)通过持续投入研发&#xff0c;提升LLC应用技术&#xff0c;集成控制芯片与功率转换&#xff0c;成功推出新一代产品RSC6218A WSOP-16&#xff0c;延续瑞森LLC拓扑方案&#xff0c;时机趋势完全迎合我国双碳政策&#xff0c;电气特…

【Web】DASCTF X GFCTF 2024|四月开启第一局 题解(全)

目录 EasySignin cool_index SuiteCRM web1234 法一、条件竞争(没成功) 法二、session反序列化 EasySignin 先随便注册个账号登录&#xff0c;然后拿bp抓包改密码(username改成admin) 然后admin / 1234567登录 康好康的图片功能可以打SSRF&#xff0c;不能直接读本地文…

Docker网络模式与cgroup资源控制

前言 在 Docker 中&#xff0c;网络模式和 cgroup 资源控制作为关键功能&#xff0c;对于容器的性能优化和资源管理起着至关重要的作用。本文将介绍 Docker 的网络模式和cgroup资源控制&#xff0c;探讨不同网络模式的特点以及如何利用 cgroup 资源控制机制来有效管理容器的资…

不使用加减运算符实现整数加和减

文章目录 进位 进位 加粗 最近想出了不适用运算符实现加与减 首先按位与找出的是需不需要进位 按位与是两边同时为1,则为1,那么如果两边同时为1的话,是不是就该进位?所以我们用按位与来判断是否需要进位 然后再按位异或找出不同的位数 按位异或是两边不相等,也就是1 和 0的时…

SpringBoot源码阅读2-自动配置

SpringBoot源码阅读2-自动配置 在传统的Spring应用中&#xff0c;开发者需要手动配置一系列Web应用的核心组件&#xff0c;例如DispatcherServlet用于处理请求分发、ViewResolver用于视图解析、CharacterEncodingFilter用于字符编码过滤等。 然而在SpringBoot中只要引入了spr…

力扣HOT100 - 994. 腐烂的橘子

解题思路&#xff1a; 因为要记录轮数&#xff08;分钟数&#xff09;&#xff0c;所以不能一口气遍历到底&#xff0c;所以不能用深搜&#xff08;bfs&#xff09;&#xff0c;而要用广搜&#xff08;bfs&#xff0c;层序遍历&#xff09;。 先记录下新鲜橘子数&#xff0c;…

github+PicGo+obsidian来作为你的免费高效可靠图床吧

前提 一直以来 博客的图床问题都是个大问题 ,如何找到一个 可靠并且 方便的搭建方式 非常重要 今天介绍一种 githubpicGoobsidian的搭建方式 准备github库 生成个人github token 找到个人 设置 生成一个新token 或者已经有的直接用 新生成的token 需要记录下来 这可能是你最后…

在若依Ruoyi-Vue中集成mybatisplus实现mybatis增强

本文相关视频&#xff1a;https://www.bilibili.com/video/BV1Fi4y1q74p?p50&vd_source2894aa0e46c09ba98269f266128b6c6e 若依&#xff08;Ruoyi&#xff09;作为一款优秀的基于Spring Boot和Vue.js的企业级后台管理系统&#xff0c;其良好的架构设计和丰富的功能组件深…

13.JAVAEE之HTTP协议

HTTP 最新的版本应该是 HTTP/3.0 目前大规模使用的版本 HTTP/1.1 使用 HTTP 协议的场景 1.浏览器打开网站 (基本上) 2.手机 APP 访问对应的服务器 (大概率) 学习 HTTP 协议, 重点学习 HTTP 的报文格式 前面的 TCP/IP/UDP 和这些不同, HTTP 的报文格式,要分两个部分来看待.请求…

C# WinForm —— 10 单选按钮与复选框的介绍与使用

单选按钮 RadioButton 一组单选按钮中&#xff0c;只能选择一个&#xff0c;互相排斥 常用属性、事件&#xff1a; 属性用途(Name)单选按钮的ID&#xff0c;在代码里引用的时候会用到,一般以 rb开头Text单选按钮旁边显示的 文本信息Checked单选按钮的勾选状态Appearance控制单…

数据结构:最小生成树(Prim算法和Kruskal算法)、图的最短路径(Dijkstra算法和Bellman-Ford算法)

什么是最小生成树&#xff1f;Prim算法和Kruskal算法是如何找到最小生成树的&#xff1f; 最小生成树是指在一个连通图中&#xff0c;通过连接所有节点并使得总权重最小的子图。 Prim算法和Kruskal算法是两种常用的算法&#xff0c;用于寻找最小生成树。 Prim算法的步骤如下&…
最新文章