并发编程

小凯   |     |   Golang学习笔记   |   27分钟   |   210浏览  

并行与并发

并发:单核运行环境下,多个线程或进程同时运行(极短时间内线程或进程切换运行)。
并行:多核运行环境下,多个线程在不同核中同时运行(不需要切换)
串行:一个任务执行完才执行下一个任务。

在golang中实现并发与其他语言相比具有明显的优势:创建更简单、性能更好。

goroutine

golang中并发执行的实现是通过协程。开启协程可以通过go关键字:
go newTaskFunc()开启后的协程会与主协程main()方法同时运行。

主协程退出时,会导致运行的子协程也跟着停止运行

在 Golang 中,当主协程返回时,程序会直接退出,而不会等待其他的协程继续执行。这意味着,如果在主协程退出时仍有其他子协程在运行,它们会被强制停止,即使它们尚未完成任务。
如果希望在主协程退出前等待所有子协程完成,可以使用通道(Channel)或者 sync 包中的 WaitGroup 来实现同步机制,确保所有子协程执行完成后再退出主协程。

package main

import (
    "fmt"
    "time"
)

func worker(id int, done chan bool) {
    fmt.Printf("Worker %d started\n", id)
    time.Sleep(time.Second) // 模拟子协程执行一段时间

    fmt.Printf("Worker %d finished\n", id)
    done <- true // 子协程执行完成后通过通道通知主协程
}

func main() {
    done := make(chan bool)
    //开启3个协程
    for i := 1; i <= 3; i++ {
        go worker(i, done)
    }
    //3个协程处理回调后才能进行最后的fmt打印
    for i := 1; i <= 3; i++ {
        <-done // 等待子协程通过通道通知执行完毕
    }

    fmt.Println("All workers finished, exiting main goroutine")
}

相关函数

runtime.Gosched 协程切换就绪状态,调度CPU

**runtime.Gosched() **函数的作用是将当前 goroutine 从运行状态切换到就绪状态,让出 CPU 给其他 goroutine 执行。它是 Golang runtime 包提供的一个用于协程调度的函数。

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go foo()

    for i := 0; i < 10; i++ {
        fmt.Println("Main goroutine")
        runtime.Gosched() // 让出 CPU 给其他协程执行
    }
}

func foo() {
    for i := 0; i < 10; i++ {
        fmt.Println("Child goroutine")
        runtime.Gosched() // 让出 CPU 给其他协程执行
    }
}

runtime.Goexit 终止当前协程,不影响其他协程

**runtime.Goexit()**函数用于终止当前 goroutine 的执行。当某个 goroutine 调用了 runtime.Goexit() 函数后,它会立即终止自己的执行,但不会影响其他正在运行的 goroutine,即使在主协程调用runtime.Goexit()
通常情况下,在一个 goroutine 中调用 runtime.Goexit() 函数后,可以使用 defer 关键字来确保在 goroutine 终止前执行某些清理操作。

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        defer wg.Done()

        fmt.Println("Worker goroutine started")
        defer fmt.Println("Worker goroutine ended")

        // 执行一些任务...

        runtime.Goexit() // 终止当前 goroutine 的执行
    }()

    wg.Wait()

    fmt.Println("Main goroutine exiting")
}

sync.WaitGroup{} 协程等待组

sync.WaitGroup 是 Go 语言中用于等待一组协程执行完毕的同步原语。
wg.Add(1)向等待组中添加指定的计数值。调用 Add 方法会增加等待组的计数器。通常在主协程中使用 Add 来设置要等待的协程数量。
wg.Done()表示一个协程已经执行完毕。调用 Done 方法会将等待组的计数器减一。通常在协程的末尾使用 Done 来标记该协程已经完成。
wg.Wait()等待所有协程执行完毕。调用 Wait 方法会阻塞当前协程,直到等待组的计数器归零。该方法通常在主协程中使用,用于等待所有协程完成任务。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    // 设置要等待的协程数量为 2
    wg.Add(2)

    // 启动两个协程
    go func() {
        // 模拟耗时操作
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("协程 A 执行:", i)
        }
    }()

    go func() {
        // 模拟耗时操作
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("协程 B 执行:", i)
        }
    }()

    // 等待所有协程执行完毕
    wg.Wait()

    fmt.Println("所有协程执行完毕")
}

通道(channel)

这里暂不介绍,想要了解自行查找资料

channel 同步协程

/*  测试通过channel同步主协程与子协程 */
func TestSyncGoroutineByChannel() {
    // 创建一个缓冲通道
    intChan := make(chan int, 5)
    fmt.Printf("channel 创建成功{ len: %v, cap: %v }\n", len(intChan), cap(intChan))
    fmt.Printf("主协程启动...\n")

    // 启动一个子协程:一次性执行所有操作
    go func(boolsChan chan int) {
        fmt.Printf("子协程启动...\n")
        for i := 1; i <= 5; i++ {
            boolsChan <- i
            fmt.Printf("子协程{ %v }\n", i)
            fmt.Printf("channel { len: %v, cap: %v }\n", len(boolsChan), cap(boolsChan))
        }
        fmt.Printf("子协程结束...\n")
    }(intChan)

    //启动一个协程:每秒执行一个操作
    go func(boolsChan chan int) {
        fmt.Printf("子协程Ⅱ启动...\n")
        for i := 6; i <= 10; i++ {
            time.Sleep(4 * time.Second)
            boolsChan <- i
            fmt.Printf("子协程Ⅱ{ %v }\n", i)
            fmt.Printf("channel { len: %v, cap: %v }\n", len(boolsChan), cap(boolsChan))
        }
        fmt.Printf("子协程Ⅱ结束...\n")
    }(intChan)
    /**
    main主协程接受11次 channel 通道中的数据,
    但是前2个协程中分别发送了5次数据,
    后5次数据需要多耗时发送。
    此时主协程接受会阻塞,
    但是第11次接受时 channel 中已无协程再往里发送数据,
    所以 main主协程会出现死锁错误而停止运行
    */
    for i := 1; i <= 11; i++ {
        // 从boolsChan通道中接收数据,并赋值给b
        time.Sleep(1 * time.Second)
        var b int = <-intChan
        fmt.Printf("%v同步协程{ %v }\n", i, b)
    }
    fmt.Printf("channel { len: %v, cap: %v }\n", len(intChan), cap(intChan))
    fmt.Printf("主协程结束...\n")
}

网络编程

网络分层架构

OSI模型

应用层 表示层 会话层 传输层 网络层 数据链路层 物理层

TCP/IP协议

应用层 传输层 网络层 接口层

层级 协议 说明
应用层 HTTP、DNS、FTP、SMTP、TELNET 应用程序 到 应用程序
传输层 TCP、UDP 进程 到 进程
网络层 IP、ICMP、ARP、RARP 主机 到 主机
接口层 各种物理通信网络接口 设备 到 设备 必须有网卡(MAC地址)

http包

在Go语言中,net/http包是用于创建基于HTTP协议的客户端和服务器的包。它提供了一组功能丰富的方法,使得开发者可以轻松地进行HTTP请求和处理HTTP响应。下面是net/http包的一些常用功能的介绍:
客户端:

  • http.Get:发送一个GET请求并返回响应。
  • http.Post:发送一个POST请求并返回响应。
  • http.Do:发送自定义的HTTP请求并返回响应。
  • http.Client:自定义客户端配置,如设置超时、TLS配置等。
  • http.NewRequest:创建一个自定义的HTTP请求。
  • http.Clienthttp.Transport:用于控制客户端的请求和传输行为,如设置代理、Cookie管理等。

服务器:

  • http.ListenAndServe:启动一个HTTP服务器,监听指定的地址和端口。
  • http.HandleFunc:注册HTTP请求处理函数。
  • http.Handlerhttp.HandlerFunc:处理HTTP请求的接口和函数类型。
  • http.FileServer:用于提供静态文件服务的处理器。
  • http.ServeMux:多路复用器,用于路由和分发HTTP请求。

响应处理:

  • http.Response:表示一个HTTP响应包。
  • http.Header:表示HTTP请求或响应的头部。
  • http.Cookies:用于处理HTTP Cookie。
  • http.ResponseWriter:用于构建HTTP响应的接口。
  • http.Error:发送一个指定状态码的错误响应。

中间件:

  • http.Handler接口的实现可以包装其他http.Handler类型,实现请求的前置或后置处理。常用的中间件有gzip压缩、日志记录、跨域资源共享(CORS)等。

此外,net/http包还提供了用于处理HTTP协议相关的URL解析、Cookie管理、TLS配置等功能。你可以在Go官方文档中找到详细的文档和示例。通过使用import "net/http"来导入net/http包,就可以在代码中使用这些功能了。

如果你觉得文章对你有帮助,那就请作者喝杯咖啡吧☕
微信
支付宝
  条评论