Todo el mundo conoce bien el patrón singleton y se define de la siguiente manera: una clase solo puede crear un solo objeto (o instancia), entonces esta clase es una clase singleton. Este patrón de diseño se llama patrón de diseño singleton o singleton patrón para abreviar.
Aunque el modo singleton es relativamente sencillo de entender, hay muchos detalles que deben tenerse en cuenta cuando se implementa realmente. Las consideraciones generales son las siguientes:
-
El constructor debe tener derechos de acceso privados, para evitar la creación externa de instancias a través de nuevos
-
Considere los problemas de seguridad de los subprocesos al crear objetos
-
Considere si es compatible con la carga diferida
-
Considere si el rendimiento de getInstance () es alto (ya sea para bloquear o no)
Código
La gramática es diferente, el grado de atención a estos puntos también es diferente. Para el idioma Go, aquí hay una forma de escribir, usando sync.Once.Do. El propósito de esta función es ejecutarla solo una vez.
Entonces podemos escribir:
type Single struct {
}
var (
once sync.Once
single *Single
)
func GetSingleInstance() *Single {
once.Do(func() {
single = &Single{
}
})
return single
}
No importa cuántas solicitudes, solo habrá una instancia de Single.
prueba
Ahora pensemos en un escenario: si 100 solicitudes de repente solicitan la interfaz GetSingleInstance al mismo tiempo, ¿estas solicitudes esperarán a que Do se ejecute o simplemente devolverán una sola, independientemente de Do?
Teóricamente, debe esperar a que se complete la ejecución de Do; de lo contrario, el single devuelto está vacío, lo que provocará un error grave. Aunque creas que sí, hagamos una prueba.
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once start")
time.Sleep(time.Second * 5)
fmt.Println("Only once end")
}
for i := 0; i < 5; i++ {
j := i
go func(int) {
fmt.Println(j)
once.Do(onceBody)
fmt.Println("lll" + strconv.Itoa(j))
}(j)
}
fmt.Println("finished")
time.Sleep(time.Second * 10)
}
El plan de prueba es muy simple, inicie 5 goroutines, llame a Do al mismo tiempo, una vez que Body se ponga en reposo durante 5 segundos, solo verifique la salida, puede juzgar si se bloqueará.
Los resultados de la ejecución son los siguientes:
➜ myproject ir a ejecutar main.go
terminado
0
Solo una vez inicio
2
4
3
1
Solo una vez final
lll2
lll4
lll0
lll1
lll3
Se puede ver que todas las goroutines se generarán solo después de que se ejecute Do, lo que demuestra que se llamará a Do, pero solo una se ejecutará realmente. Antes de que se complete la ejecución real, se bloquearán otras goroutines.
De hecho, existe un riesgo oculto: si la función ejecutada por Do consume mucho tiempo, provocará la acumulación de una gran cantidad de goroutines, lo que debe tenerse en cuenta a la hora de programar.
Implementación
¿Cómo se implementa la función Do? Echemos un vistazo al código fuente:
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
Cuando varias goroutines comprueban que el valor hecho es 0, ingrese doSlow, solo una goroutine obtendrá el bloqueo y otras goroutines no se bloquearán.
Las prácticas son tecnologías más convencionales, principalmente cerraduras de exclusión mutua, semáforos y aplazamiento, pero el diseño sigue siendo muy inteligente. Esto también es una ventaja de Go. El uso de bloqueos para resolver conflictos es rápido, seguro y conveniente, pero se deben considerar los problemas de rendimiento.
Por fin
Si te gusta mi artículo, puedes seguir mi cuenta oficial (Programador Mala Tang)
Mi blog personal es: https://shidawuhen.github.io/
Revisión de artículos anteriores:
tecnología
- Ir patrón de diseño (4) -escritura de código
- Ir patrón de diseño (3) -principios de diseño
- Ir al patrón de diseño (2) -análisis y diseño orientado a objetos
- Problemas generales de acceso a los pagos
- Tutorial básico de HTTP2.0
- Ir patrón de diseño (1) -gramática
- Especificación de desarrollo de MySQL
- Combate de configuración HTTPS
- Principios de la implementación del canal Go
- Ir al principio de implementación del temporizador
- Proceso de conexión HTTPS
- Realización del límite de corriente 2
- Sistema de picos
- Sistema distribuido y protocolo de consenso
- Marco de servicio y registro de microservicios
- Uso del marco de Beego
- Hablando de microservicios
- Optimización del rendimiento de TCP
- Realización del límite de corriente 1
- Redis implementa bloqueos distribuidos
- Seguimiento de errores del código fuente de Golang
- El principio de realización de la atomicidad, la coherencia y la durabilidad de las transacciones.
- Proceso de solicitud de CDN detallado
- Técnicas comunes de almacenamiento en caché
- Cómo conectarse de manera eficiente con pagos de terceros
- Versión concisa del marco de gin
- Un breve análisis de los bloqueos y transacciones de InnoDB
- Resumen del algoritmo
notas de estudio
- en principio
- Zi Zhi Tong Jian
- Revolución ágil
- Cómo ejercitar tu memoria
- Lógica simple después de la lectura
- Aire caliente después de la lectura
- Las Analectas-Pensamientos después de la lectura
- El arte de la guerra de Sun Tzu: pensamientos después de leer
Pensando
- Contra el liberalismo
- Teoría de la práctica
- Evaluar los propios estándares
- Plan de servicio de vacaciones del equipo servidor
- Gestión de procesos de proyectos
- Algunas opiniones sobre la gestión de proyectos
- Algunas reflexiones sobre los gerentes de producto
- Reflexiones sobre el desarrollo profesional de los programadores
- Pensando en la revisión del código
- Recomendación del editor de Markdown-typora