Golang utiliza el grupo de espera síncrono del canal WaitGroup para desarrollar rastreadores concurrentes

Hay un dicho clásico en la programación concurrente de Go: no use la memoria compartida para comunicarse, sino que use la comunicación compartida para compartir la memoria.

El lenguaje Go no fomenta el uso de bloqueos para proteger el estado compartido en diferentes Goroutines para compartir información (para comunicarse en una memoria compartida). Más bien, alienta la transferencia de estado compartido o cambios de estado compartido entre cada Goroutine a través de canales (para compartir memoria por comunicación), lo que también puede garantizar que solo una Goroutine acceda al estado compartido al mismo tiempo que una cerradura.

Por supuesto, en los lenguajes de programación convencionales, para garantizar la seguridad y la coherencia del intercambio de datos entre múltiples subprocesos, se proporcionará un conjunto básico de herramientas de sincronización, como bloqueos, variables de condición, operaciones atómicas, etc. La biblioteca estándar de idiomas Go también proporciona estos mecanismos de sincronización sin sorpresa, y el uso es similar a otros idiomas.

 

 

WaitGroup

WaitGroup, grupo de espera de sincronización.

En términos de tipo, es una estructura. El propósito de un WaitGroup es esperar a que se complete la ejecución de una colección de rutina. La gorutina principal llama al método Add () para establecer el número de gorutinas a esperar. Luego, se ejecutará cada rutina y se llamará al método Done () después de que se complete la ejecución. Al mismo tiempo, puede usar el método Wait () para bloquear hasta que se ejecuten todas las rutinas.

Método Add ()

Agregar este método se utiliza para establecer el valor del contador en WaitGroup. Podemos entender que hay un contador en cada grupo de espera para indicar el número de goroutin que se ejecutará en este grupo de espera de sincronización.

Si el valor del contador se convierte en 0, significa que las gorutinas bloqueadas se liberan mientras esperan. Si el valor del contador es negativo, provocará pánico y el programa informará un error.

Método Done ()

El método Done () consiste en establecer el valor del contador de este WaitGroup menos 1 cuando WaitGroup espera sincrónicamente la ejecución de una rutina en el grupo.

Método Wait ()

El método Wait () significa dejar que la rutina actual espere y entre en el estado de bloqueo. Hasta que el contador de WaitGroup sea cero. Para desbloquear, la rutina puede continuar ejecutándose.

Código de muestra

 
  1. package main

    import (
        "fmt"
        "sync"
    )
    var wg sync.WaitGroup // 创建同步等待组对象
    func main()  {
        /*
        WaitGroup:同步等待组
            可以使用Add(),设置等待组中要 执行的子goroutine的数量,

            在main 函数中,使用wait(),让主程序处于等待状态。直到等待组中子程序执行完毕。解除阻塞

            子gorotuine对应的函数中。wg.Done(),用于让等待组中的子程序的数量减1
         */
        //设置等待组中,要执行的goroutine的数量
        wg.Add(2)
        go fun1()
        go fun2()
        fmt.Println("main进入阻塞状态。。。等待wg中的子goroutine结束。。")
        wg.Wait() //表示main goroutine进入等待,意味着阻塞
        fmt.Println("main,解除阻塞。。")

    }
    func fun1()  {
        for i:=1;i<=10;i++{
            fmt.Println("fun1.。。i:",i)
        }
        wg.Done() //给wg等待中的执行的goroutine数量减1.同Add(-1)
    }
    func fun2()  {
        defer wg.Done()
        for j:=1;j<=10;j++{
            fmt.Println("\tfun2..j,",j)
        }
    }

canal canal

El canal puede considerarse como un canal de comunicación para Goroutines. Similar al flujo de agua en una tubería de un extremo al otro, los datos pueden enviarse de un extremo al otro y recibirse a través del canal.

Cuando hablamos sobre la concurrencia del lenguaje Go anteriormente, dijimos que cuando múltiples Goroutines desean implementar datos compartidos, aunque también proporcionan un mecanismo de sincronización tradicional, el lenguaje Go recomienda enfáticamente el uso de canales de canal para lograr entre Goroutines. Comunicación.

"No se comunique a través de la memoria compartida, pero comparta la memoria a través de la comunicación" Esta es una frase clásica que popularizó la comunidad de Golang

Recibir y enviar

Un canal para enviar y recibir datos está bloqueado de forma predeterminada. Cuando se envía una pieza de datos a un canal, se bloquea en la declaración de envío hasta que otra Goroutine lea los datos del canal. En contraste, cuando se leen datos de un canal, la lectura se bloquea hasta que un Goroutine escribe los datos en el canal.

Código de muestra: el siguiente código agrega suspensión, que puede comprender mejor el bloqueo del canal

 
  1. package main

    import (
        "fmt"
        "time"
    )

    func main() {
        ch1 := make(chan int)
        done := make(chan bool) // 通道
        go func() {
            fmt.Println("子goroutine执行。。。")
            time.Sleep(3 * time.Second)
            data := <-ch1 // 从通道中读取数据
            fmt.Println("data:", data)
            done <- true
        }()
        // 向通道中写数据。。
        time.Sleep(5 * time.Second)
        ch1 <- 100

        <-done
        fmt.Println("main。。over")

    }

En el programa anterior, primero creamos un canal chan bool. Luego comencé una sub-Goroutine e imprimí 10 números en un bucle. Luego escribimos la entrada verdadera al canal ch1.
Luego, en la rutina principal, leemos los datos de ch1. Esta línea de código está bloqueada, lo que significa que la gorutina principal no se ejecutará en la siguiente línea de código hasta que la Goroutina secundaria escriba datos en el canal.

Por lo tanto, podemos realizar la comunicación entre la sub-goroutina y la goroutina principal a través del canal. Cuando se ejecuta la goroutina infantil, la gorutina principal se bloqueará leyendo los datos en ch1. Esto asegura que la sub-gorutina se ejecutará primero. Esto elimina la necesidad de tiempo.

En el programa anterior, ponemos la gorutina principal a dormir para evitar que salga la gorutina principal. Utilice WaitGroup para asegurarse de que la subgutina se ejecute antes de que finalice la gorutina principal.

Punto muerto

Un factor importante a tener en cuenta al usar canales es el punto muerto. Si Goroutine envía datos en un canal, se espera que otras Goroutines reciban datos. Si esto no sucede, el programa se bloqueará mientras se ejecuta.

Del mismo modo, si Goroutine está esperando recibir datos del canal, algunos Goroutine escribirán datos en el canal, de lo contrario, el programa se bloqueará.

Código de muestra

 
  1. package main

    func main() {  
        ch := make(chan int)
        ch <- 5
    }

Error:

 
  1. fatal error: all goroutines are asleep - deadlock!

    goroutine 1 [chan send]:
    main.main()
        /Users/ruby/go/src/l_goroutine/demo08_chan.go:5 +0x50

Goroutine

Goroutine es una entidad que se ejecuta de manera simultánea. Su capa inferior utiliza la rutina para lograr la concurrencia. Coroutine es un hilo de usuario que se ejecuta en modo de usuario. Similar a greenthread, el punto de partida para ir a la capa inferior para elegir la rutina es porque tiene las siguientes características :

El espacio de usuario evita el costo causado por el cambio entre el modo kernel y el modo de usuario.
Puede programarse por las capas de lenguaje y marco. El
espacio de pila más pequeño permite que se cree una gran cantidad de instancias.

Planificador Goroutine

Ir a la programación concurrente: modelo GPM

Además de los hilos del núcleo proporcionados por el sistema operativo, Go ha creado un modelo único de hilos de dos niveles. El mecanismo de goroutina implementa el modelo de subprocesos de M: N. El mecanismo de goroutina es una implementación de rutinas.El programador incorporado de Golang permite que cada CPU en una CPU de múltiples núcleos ejecute una rutina.

El contenido anterior es de  https://github.com/rubyhan1314/Golang-100-Days
explica principalmente el uso básico de los grupos y canales de espera de sincronización, y cómo se maneja la concurrencia. Para más información, puede continuar refiriéndose a lo anterior, vaya a Qianfeng Tutorial

Combate de reptiles

He dicho mucho antes, pero esto es solo para la preparación de este script, de lo contrario sería demasiado brusco.
Escribí un script de rastreo aquí, usando un canal para hacer concurrencia, y hay grupos de espera síncronos para hacer operaciones awit ()

Mira directamente el código

Obtén html

 
  1. func HttpGet(url string) (result string, err error) {
        resp, err1 := http.Get(url)
        if err != nil {
            err = err1
            return
        }
        defer resp.Body.Close()
        //读取网页的body内容
        buf := make([]byte, 4*1024)
        for true {
            n, err := resp.Body.Read(buf)
            if err != nil {
                if err == io.EOF{
                    break
                }else {
                    fmt.Println("resp.Body.Read err = ", err)
                    break
                }
            }
            result += string(buf[:n])
        }
        return
    }

Rastrear páginas web como archivos .html

 
  1. func spiderPage(url string) string {

        fmt.Println("正在爬取", url)
        //爬,将所有的网页内容爬取下来
        result, err := HttpGet(url)
        if err != nil {
            fmt.Println(err)
        }
        //把内容写入到文件
        filename := strconv.Itoa(rand.Int()) + ".html"
        f, err1 := os.Create(filename)
        if err1 != nil{
            fmt.Println(err1)
        }
        //写内容
        f.WriteString(result)
        //关闭文件
        f.Close()
        return url + " 抓取成功"

    }

Terminé de escribir el método de rastreo y luego llegué a la parte importante

Definir una función de trabajador

 
  1. func doWork(start, end int,wg *sync.WaitGroup) {
        fmt.Printf("正在爬取第%d页到%d页\n", start, end)
        //因为很有可能爬虫还没有结束下面的循环就已经结束了,所以这里就需要且到通道
        page := make(chan string,100)
        results := make(chan string,100)


        go sendResult(results,start,end)

        go func() {

            for i := 0; i <= 20; i++ {
                wg.Add(1)
                go asyn_worker(page, results, wg)
            }
        }()

        for i := start; i <= end; i++ {
                url := "https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
                page <- url
                println("加入" + url + "到page")
            }
            println("关闭通道")
            close(page)

        wg.Wait()
        //time.Sleep(time.Second * 5)
        println(" Main 退出 。。。。。")
    }

Obtener datos del canal

 
  1. func asyn_worker(page chan string, results chan string,wg *sync.WaitGroup){

        defer wg.Done()  //defer wg.Done()必须放在go并发函数内

        for{
            v, ok := <- page //显示的调用close方法关闭通道。
            if !ok{
                fmt.Println("已经读取了所有的数据,", ok)
                break
            }
            //fmt.Println("取出数据:",v, ok)
            results <- spiderPage(v)
        }


        //for n := range page {
        //  results <- spiderPage(n)
        //}
    }

Enviar resultados de rastreo

 
  1. func sendResult(results chan string,start,end int)  {

        //for i := start; i <= end; i++ {
        //  fmt.Println(<-results)
        //}

        // 发送抓取结果
        for{
            v, ok := <- results
            if !ok{
                fmt.Println("已经读取了所有的数据,", ok)
                break
            }
            fmt.Println(v)

        }
    }

La idea general es esta:

Se puede ver que he definido dos canales, uno se usa para almacenar la URL y el otro se usa para almacenar el resultado del rastreo. El espacio del búfer es 100.
En el método doWork, sendResult bloqueará la espera de la salida del canal de resultados, anónimo La función es esperar la salida del canal de la página.

Inmediatamente después es escribir 200 URL en el canal de la página. La función anónima obtiene el resultado de la página y ejecuta la función asyn_worker, que es la función de rastrear html (almacenarlo en el canal de resultados)

Luego, la función sendResult obtiene la salida del canal de resultados e imprime el resultado

Puede ver que tengo 20 grupos simultáneos en la función anónima, y ​​el grupo de espera de sincronización está habilitado como parámetro. En teoría, el número de concurrencia se puede definir de acuerdo con el rendimiento de la máquina

función principal

 
  1. func main() {
        start_time := time.Now().UnixNano()

        var wg sync.WaitGroup

        doWork(1,200, &wg)
        //输出执行时间,单位为毫秒。
        fmt.Printf("执行时间: %ds",(time.Now().UnixNano() - start_time) / 1000)

    }

Ejecute el rastreador y calcule el tiempo de ejecución, este tiempo varía de una máquina a otra, pero no debería ser muy diferente

Código completo

 
  1. package main

    import (
        "fmt"
        "io"
        "sync"
        "math/rand"
        "net/http"
        "os"
        "strconv"
        "time"
    )



    func HttpGet(url string) (result string, err error) {
        resp, err1 := http.Get(url)
        if err != nil {
            err = err1
            return
        }
        defer resp.Body.Close()
        //读取网页的body内容
        buf := make([]byte, 4*1024)
        for true {
            n, err := resp.Body.Read(buf)
            if err != nil {
                if err == io.EOF{
                    break
                }else {
                    fmt.Println("resp.Body.Read err = ", err)
                    break
                }
            }
            result += string(buf[:n])
        }
        return
    }


    //爬取网页
    func spiderPage(url string) string {

        fmt.Println("正在爬取", url)
        //爬,将所有的网页内容爬取下来
        result, err := HttpGet(url)
        if err != nil {
            fmt.Println(err)
        }
        //把内容写入到文件
        filename := strconv.Itoa(rand.Int()) + ".html"
        f, err1 := os.Create(filename)
        if err1 != nil{
            fmt.Println(err1)
        }
        //写内容
        f.WriteString(result)
        //关闭文件
        f.Close()
        return url + " 抓取成功"

    }

    func asyn_worker(page chan string, results chan string,wg *sync.WaitGroup){

        defer wg.Done()  //defer wg.Done()必须放在go并发函数内

        for{
            v, ok := <- page //显示的调用close方法关闭通道。
            if !ok{
                fmt.Println("已经读取了所有的数据,", ok)
                break
            }
            //fmt.Println("取出数据:",v, ok)
            results <- spiderPage(v)
        }

        //for n := range page {
        //  results <- spiderPage(n)
        //}
    }

    func doWork(start, end int,wg *sync.WaitGroup) {
        fmt.Printf("正在爬取第%d页到%d页\n", start, end)
        //因为很有可能爬虫还没有结束下面的循环就已经结束了,所以这里就需要且到通道
        page := make(chan string,100)
        results := make(chan string,100)


        go sendResult(results,start,end)

        go func() {

            for i := 0; i <= 20; i++ {
                wg.Add(1)
                go asyn_worker(page, results, wg)
            }
        }()


        for i := start; i <= end; i++ {
                url := "https://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
                page <- url
                println("加入" + url + "到page")
            }
            println("关闭通道")
            close(page)

        wg.Wait()
        //time.Sleep(time.Second * 5)
        println(" Main 退出 。。。。。")
    }


    func sendResult(results chan string,start,end int)  {

        //for i := start; i <= end; i++ {
        //  fmt.Println(<-results)
        //}

        // 发送抓取结果
        for{
            v, ok := <- results
            if !ok{
                fmt.Println("已经读取了所有的数据,", ok)
                break
            }
            fmt.Println(v)

        }
    }

    func main() {
        start_time := time.Now().UnixNano()

        var wg sync.WaitGroup

        doWork(1,200, &wg)
        //输出执行时间,单位为毫秒。
        fmt.Printf("执行时间: %ds",(time.Now().UnixNano() - start_time) / 1000)

    }

En general, este script es para aclarar el principio de concurrencia y el canal del lenguaje Go, el uso básico del grupo de espera de sincronización, o solo usar el bloqueo del idioma go, el propósito es evitar el problema de seguridad de los recursos críticos.

Con el canal y la rutina, la programación concurrente de Go se ha vuelto extremadamente fácil y segura, permitiendo a los programadores centrar su atención en los negocios y mejorar la eficiencia del desarrollo.

Vaya a https://gzky.live/article/Golang%E9%80%9A%E9%81%93%E5%90%8C%E6%AD%A5%E7%AD%89%E5%BE%85% E7% BB% 84% 20% E5% B9% B6% E5% 8F% 91% E7% 88% AC% E8% 99% AB

Publicado 23 artículos originales · ganado elogios 2 · Vistas 5236

Supongo que te gusta

Origin blog.csdn.net/bianlitongcn/article/details/105367100
Recomendado
Clasificación