Comprensión profunda de la programación concurrente en lenguaje Go [29] [operación atómica (paquete atómico), seguridad de concurrencia]


Operaciones atómicas (paquete atómico)

operación atómica

La operación de bloqueo en el código requiere mucho tiempo y es costosa porque implica un cambio de contexto en el estado del kernel. Para los tipos de datos básicos, también podemos usar operaciones atómicas para garantizar la seguridad de la concurrencia, porque las operaciones atómicas son métodos proporcionados por el lenguaje Go, que se pueden completar en modo usuario, por lo que el rendimiento es mejor que las operaciones de bloqueo. Las operaciones atómicas en el lenguaje Go son proporcionadas por la biblioteca estándar incorporada sync/atomic.

paquete atómico

http://doc.golang.ltd/
método explicar
func LoadInt32(dirección *int32) (valor int32) func LoadInt64(dirección *int64) (valor int64) func LoadUint32(dirección) (valor *uint32uint32) func LoadUint64(dirección *uint64) (valor uint64) func LoadUintptr(dirección *uintptr) (valor uintptr) func LoadPointer( addr *unsafe.Pointer) (valor no seguro.Puntero) operación de lectura
func StoreInt32(dirección *int32, valor int32) func StoreInt64(dirección *int64, valor int64) func StoreUint32(dirección, valor *uint32uint32) func StoreUint64(dirección *uint64, valor uint64) func StoreUintptr(dirección *uintptr, valor uintptr) func StorePointer(dirección *unsafe.Pointer, valor inseguro.Pointer) operación de escritura
func AddInt32(addr *int32, delta int32) (nuevo int32) func AddInt64(addr *int64, delta int64) (nuevo int64) func AddUint32(addr *uint32, delta uint32) (nuevo uint32) func AddUint64(addr *uint64, delta uint64) (nuevo uint64) func AddUintptr (dirección *uintptr, delta uintptr) (nuevo uintptr) modificar operación
func SwapInt32(dirección *int32, nuevo int32) (antiguo int32) func SwapInt64(dirección *int64, nuevo int64) (antiguo int64) func SwapUint32(dirección *uint32, nuevo uint32) (antiguo uint32) func SwapUint64(dirección *uint64, nuevo uint64) (antiguo uint64) func SwapUintptr (dirección *uintptr, nuevo uintptr) (antiguo uintptr) func SwapPointer(dirección *unsafe.Pointer, nuevo puntero inseguro) (antiguo puntero inseguro) operación de cambio
func CompareAndSwapInt32(addr *int32, old, new int32) (booleano intercambiado) func CompareAndSwapInt64(addr *int64, old, new int64) (bool intercambiado) func CompareAndSwapUint32(addr *uint32, old, new uint32) (bool intercambiado) func CompareAndSwapUint64(addr *uint64, old, new uint64) (bool intercambiado) func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (bool intercambiado) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (bool intercambiado) operación de comparación e intercambio

ejemplo

Completemos un ejemplo para comparar el rendimiento de las operaciones atómicas y mutex.

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))
}

El paquete atómico proporciona operaciones de memoria de nivel atómico de bajo nivel, que son útiles para la implementación de algoritmos de sincronización. Se debe tener cuidado para garantizar el uso adecuado de estas funciones. Excepto por algunas aplicaciones especiales de bajo nivel, es mejor usar canales o funciones/tipos del paquete de sincronización para lograr la sincronización.

seguridad de concurrencia

  Múltiples corrutinas modifican el mismo bloque de memoria al mismo tiempo, lo que genera una competencia de recursos. Agregue el parámetro -race para verificar la competencia de recursos cuando vaya a ejecutar o construir.
  n++ no es una operación atómica y habrá escrituras sucias durante la ejecución simultánea. n++ se divide en 3 pasos: quitar n, sumar 1 y asignar el resultado a n. Durante la prueba, se deben abrir 1000 corrutinas simultáneas para observar escrituras sucias.

inserte la descripción de la imagen aquí

func atomic.AddInt32(addr *int32, delta int32) (new int32)
func atomic.LoadInt32(addr *int32) (val int32)

  Encapsule n++ en operaciones atómicas para eliminar la competencia de recursos y evitar escrituras sucias.

var lock sync.RWMutex		//声明读写锁,无需初始化
lock.Lock() lock.Unlock()	//加写锁和释放写锁
lock.RLock() lock.RUnlock()	//加读锁和释放读锁

  Solo se puede agregar un bloqueo de escritura en cualquier momento y no se puede agregar ningún bloqueo de lectura. Cuando no se agrega ningún bloqueo de escritura, se pueden agregar múltiples bloqueos de lectura al mismo tiempo y no se puede agregar ningún bloqueo de escritura después de agregar el bloqueo de lectura.

package main

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

var n int32 = 0
var lock sync.RWMutex

func inc1() {
    
    
	n++ //n++不是原子操作,它分为3步:取出n,加1,结果赋给n
}

func inc2() {
    
    
	atomic.AddInt32(&n, 1) //封装成原子操作
}

func inc3() {
    
    
	lock.Lock()   //加写锁
	n++           //任一时刻,只有一个协程能进入临界区域
	lock.Unlock() //释放写锁
}

func main() {
    
    
	const P = 1000 //开大量协程才能把脏写问题测出来
	wg := sync.WaitGroup{
    
    }
	wg.Add(P)
	for i := 0; i < P; i++ {
    
    
		go func() {
    
    
			defer wg.Done()
			inc1()
		}()
	}
	wg.Wait()
	fmt.Printf("finally n=%d\n", n) //多运行几次,n经常不等于1000
	fmt.Println("===========================")
	n = 0 //重置n
	wg = sync.WaitGroup{
    
    }
	wg.Add(P)
	for i := 0; i < P; i++ {
    
    
		go func() {
    
    
			defer wg.Done()
			inc2()
		}()
	}
	wg.Wait()
	fmt.Printf("finally n=%d\n", atomic.LoadInt32(&n))
	fmt.Println("===========================")
	n = 0 //重置n
	wg = sync.WaitGroup{
    
    }
	wg.Add(P)
	for i := 0; i < P; i++ {
    
    
		go func() {
    
    
			defer wg.Done()
			inc3()
		}()
	}
	wg.Wait()
	lock.RLock() //加读锁。当写锁被其他协程持有时,加读锁操作将被阻塞;否则,如果其他协程持有读锁,加读锁操作不会被阻塞
	fmt.Printf("finally n=%d\n", n)
	lock.RUnlock() //释放读锁
	fmt.Println("===========================")
}

  Las matrices, los sectores y las estructuras permiten la modificación concurrente (posiblemente escritura sucia), y la modificación concurrente del mapa a veces genera pánico. Si necesita modificar el mapa al mismo tiempo, utilice sync.Map.

package main

import (
	"fmt"
	"sync"
)

type Student struct {
    
    
	Name string
	Age  int32
}

var arr = [10]int{
    
    }
var m = sync.Map{
    
    }

func main() {
    
    
	wg := sync.WaitGroup{
    
    }
	wg.Add(2)
	go func() {
    
     //写偶数位
		defer wg.Done()
		for i := 0; i < len(arr); i += 2 {
    
    
			arr[i] = 0
		}
	}()
	go func() {
    
     //写奇数位
		defer wg.Done()
		for i := 1; i < len(arr); i += 2 {
    
    
			arr[i] = 1
		}
	}()
	wg.Wait()
	fmt.Println(arr) //输出[0 1 0 1 0 1 0 1 0 1]
	fmt.Println("=======================")
	wg.Add(2)
	var stu Student
	go func() {
    
    
		defer wg.Done()
		stu.Name = "Fred"
	}()
	go func() {
    
    
		defer wg.Done()
		stu.Age = 20
	}()
	wg.Wait()
	fmt.Printf("%s %d\n", stu.Name, stu.Age)//Fred 20
	fmt.Println("=======================")
	wg.Add(2)
	go func() {
    
    
		defer wg.Done()
		m.Store("k1", "v1")// 往map写数据
	}()
	go func() {
    
    
		defer wg.Done()
		m.Store("k1", "v2")
	}()
	wg.Wait()
	fmt.Println(m.Load("k1"))//从map读数据--》v1 true
}

Supongo que te gusta

Origin blog.csdn.net/m0_52896752/article/details/129797905
Recomendado
Clasificación