Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Использование может добавить накладные расходы (overhead) на выполнение вашего приложения. Это связано с тем, что сбор и обработка данных для профилирования требуют дополнительных ресурсов процессора и памяти.
Профилирование CPU может добавлять нагрузку, так как профайлер должен периодически собирать информацию о состоянии выполняемого кода. Профилирование включает периодические прерывания и сбор информации о стеке вызовов, что может замедлить выполнение программы.
Профилирование памяти (heap профилирование) требует отслеживания выделения и освобождения памяти, что может увеличить потребление памяти. Дополнительные структуры данных для хранения информации о профилях также требуют памяти.
Влияние на производительность может варьироваться в зависимости от сложности приложения и частоты сбора данных. В высоконагруженных системах накладные расходы могут быть более заметными.
Включайте профилирование только тогда, когда это действительно необходимо. Например, можно включать профилирование на ограниченный период для сбора данных, а затем отключать его.
Для профилирования CPU можно настроить частоту сбора данных. По умолчанию, pprof собирает данные каждые 10 миллисекунд. Вы можете уменьшить эту частоту.
import (
"runtime"
"runtime/pprof"
"os"
"time"
)
func main() {
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Ваш код здесь
time.Sleep(30 * time.Second) // Выполнение в течение 30 секунд
}
Логируйте только важную информацию и избегайте чрезмерного логирования, чтобы минимизировать накладные расходы.
Используйте конкретные профили (например, heap, goroutine, block) только для тех аспектов, которые вы хотите анализировать. Это уменьшит накладные расходы по сравнению с полным профилированием.
package main
import (
"log"
"net/http"
"os"
"runtime/pprof"
"time"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Включаем профилирование CPU
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Ваше основное приложение
runApplication()
}
func runApplication() {
// Основная логика вашего приложения
time.Sleep(30 * time.Second) // Симуляция работы приложения
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это структуры данных, которые обеспечивают синхронизацию и взаимодействие между потоками (горутинами) без использования традиционных блокировок (мьютексов). Вместо блокировок они полагаются на атомарные операции, такие как Compare-And-Swap (CAS), для обеспечения корректного и последовательного доступа к данным.
Они не используют мьютексы или другие блокирующие механизмы.
Они уменьшают задержки и повышают производительность за счет минимизации времени ожидания потоков.
Так как нет блокировок, невозможно возникновение взаимных блокировок.
Атомарные счетчики:
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var counter int32
atomic.AddInt32(&counter, 1)
fmt.Println(atomic.LoadInt32(&counter)) // Вывод: 1
}
Lock-Free очередь (псевдокод):
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Node struct {
value int
next unsafe.Pointer
}
type LockFreeQueue struct {
head unsafe.Pointer
tail unsafe.Pointer
}
func NewLockFreeQueue() *LockFreeQueue {
node := unsafe.Pointer(&Node{})
return &LockFreeQueue{head: node, tail: node}
}
func (q *LockFreeQueue) Enqueue(value int) {
node := &Node{value: value}
for {
tail := atomic.LoadPointer(&q.tail)
next := atomic.LoadPointer(&(*Node)(tail).next)
if next == nil {
if atomic.CompareAndSwapPointer(&(*Node)(tail).next, next, unsafe.Pointer(node)) {
atomic.CompareAndSwapPointer(&q.tail, tail, unsafe.Pointer(node))
return
}
} else {
atomic.CompareAndSwapPointer(&q.tail, tail, next)
}
}
}
func (q *LockFreeQueue) Dequeue() (int, bool) {
for {
head := atomic.LoadPointer(&q.head)
tail := atomic.LoadPointer(&q.tail)
next := atomic.LoadPointer(&(*Node)(head).next)
if head == tail {
if next == nil {
return 0, false // очередь пуста
}
atomic.CompareAndSwapPointer(&q.tail, tail, next)
} else {
value := (*Node)(next).value
if atomic.CompareAndSwapPointer(&q.head, head, next) {
return value, true
}
}
}
}
func main() {
q := NewLockFreeQueue()
q.Enqueue(1)
q.Enqueue(2)
value, ok := q.Dequeue()
if ok {
fmt.Println("Dequeued:", value) // Вывод: Dequeued: 1
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Механизм отложенных вызовов (
defer
) и восстановления (recover
) используется для обработки паник, что позволяет безопасно завершить выполнение программы или выполнить необходимые действия при возникновении ошибок. Вызовы, обернутые в
defer
, выполняются в обратном порядке по завершении функции, в которой они объявлены, даже если возникла паника.recover
перехватывает панику, если она вызывается внутри функции, отложенной с помощью defer
. Если recover
вызывается вне контекста паники, он возвращает nil
.package main
import (
"fmt"
)
func main() {
fmt.Println("Starting the program...")
safeFunction()
fmt.Println("Program finished successfully.")
}
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
riskyFunction()
}
func riskyFunction() {
fmt.Println("About to cause a panic...")
panic("Something went wrong!")
fmt.Println("This line will not be executed.")
}
Запускает программу и вызывает
safeFunction
.Печатает сообщение "Starting the program...".
Содержит отложенную анонимную функцию, которая вызывает
recover
для обработки возможной паники.Вызывает
riskyFunction
.Печатает сообщение "About to cause a panic...".
Вызывает панику с сообщением "Something went wrong!". Когда
riskyFunction
вызывает панику, управление передается отложенной функции в safeFunction
, которая вызывает recover
. recover
перехватывает панику, и программа продолжает выполнение.Starting the program...
About to cause a panic...
Recovered from panic: Something went wrong!
Program finished successfully.
Можно использовать
defer
и recover
для обеспечения выполнения необходимых действий перед завершением программы, даже если возникла паника. Например, закрытие файлов, освобождение ресурсов и т.д.package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
file.Close()
fmt.Println("File closed")
}()
// Работа с файлом
panic("Something went wrong during file processing")
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Может быть как безопасным, так и небезопасным, в зависимости от контекста и конкретного использования.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := make([]byte, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
buffer[i] = byte(i) // Несколько горутин одновременно модифицируют буфер
}(i)
}
wg.Wait()
fmt.Println("Buffer:", buffer)
}
Если несколько горутин только читают из буфера, это безопасно. Пример безопасного использования:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")
for i := 0; i < len(buffer); i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("buffer[%d]: %c\n", i, buffer[i])
}(i)
}
wg.Wait()
}
Для синхронизации доступа к буферу используйте мьютексы (
sync.Mutex
). Это гарантирует, что только одна горутина в данный момент модифицирует буфер.package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
buffer := make([]byte, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
mu.Lock()
buffer[i] = byte(i)
mu.Unlock()
}(i)
}
wg.Wait()
fmt.Println("Buffer:", buffer)
}
Если каждая горутина должна работать с независимой копией буфера, создавайте копии для каждой горутины.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
localBuffer := make([]byte, len(buffer))
copy(localBuffer, buffer)
localBuffer[i] = byte(i)
fmt.Printf("Goroutine %d: %s\n", i, localBuffer)
}(i)
}
wg.Wait()
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Используется для обеспечения эксклюзивного доступа к общему ресурсу. Когда мьютекс заблокирован одной горутиной, все другие горутины, пытающиеся заблокировать этот же мьютекс, будут заблокированы до тех пор, пока первый мьютекс не будет разблокирован.
Блокирует мьютекс. Если мьютекс уже заблокирован, текущая горутина блокируется до тех пор, пока мьютекс не будет освобожден.
Разблокирует мьютекс. Если есть другие горутины, ожидающие блокировки этого мьютекса, одна из них будет разблокирована и сможет продолжить выполнение.
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var counter int
func increment() {
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}
sync.RWMutex
(мьютекс для чтения и записи) предоставляет два типа блокировок:Для чтения (
RLock
): Позволяет нескольким горутинам одновременно читать данные.Для записи (
Lock
): Обеспечивает эксклюзивный доступ для записи, блокируя все другие операции чтения и записи.RLock()
Блокирует мьютекс для чтения. Если мьютекс уже заблокирован для записи, текущая горутина блокируется до тех пор, пока мьютекс не будет освобожден.
RUnlock()
Разблокирует мьютекс для чтения.
Lock()
Блокирует мьютекс для записи. Если мьютекс заблокирован для чтения или записи, текущая горутина блокируется до тех пор, пока мьютекс не будет освобожден.
Unlock()
Разблокирует мьютекс для записи.
package main
import (
"fmt"
"sync"
)
var rwMu sync.RWMutex
var counter int
func readCounter() int {
rwMu.RLock()
defer rwMu.RUnlock()
return counter
}
func increment() {
rwMu.Lock()
counter++
rwMu.Unlock()
}
func main() {
var wg sync.WaitGroup
// Запуск нескольких горутин для чтения
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Read Counter:", readCounter())
}()
}
// Запуск нескольких горутин для записи
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final Counter:", counter)
}
sync.Mutex
: Блокирует доступ ко всем операциям (чтение и запись).sync.RWMutex
: Позволяет одновременное чтение, но блокирует доступ при записи.sync.Mutex
: Подходит для сценариев с частыми операциями записи или равномерным распределением чтений и записей.sync.RWMutex
: Подходит для сценариев с частыми операциями чтения и редкими операциями записи.sync.Mutex
: Используйте, когда необходимо обеспечить эксклюзивный доступ к ресурсу без различий между операциями чтения и записи.sync.RWMutex
: Используйте, когда операции чтения преобладают над операциями записи, чтобы повысить производительность за счет одновременного чтения.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Они способствуют созданию кода, который является более надежным, поддерживаемым и расширяемым.
Написание тестов помогает обнаружить ошибки и дефекты на ранних стадиях разработки. Регулярное тестирование кода обеспечивает его корректное функционирование и уменьшает количество ошибок.
Тесты служат документацией, показывая, как должен работать код. Это облегчает понимание кода новым разработчикам и помогает избежать регрессий при внесении изменений.
Для упрощения тестирования разработчики разбивают код на небольшие, легко тестируемые модули и функции. Это приводит к более чистому и структурированному коду.
Для облегчения тестирования функции и модули должны иметь четко определенные интерфейсы и зависимости. Это приводит к лучшему дизайну API и модулей.
Тесты помогают выявить излишне сложные и трудно тестируемые участки кода, что стимулирует рефакторинг и упрощение кода.
Это методология разработки, при которой тесты пишутся перед написанием функционального кода. Этот подход оказывает дополнительное влияние на организацию кода:
TDD способствует разработке кода малыми итерациями. Каждый цикл включает написание теста, написание кода для его прохождения и рефакторинг. Это улучшает качество кода и позволяет быстро обнаруживать ошибки
Пишутся только те функции, которые необходимы для прохождения тестов, что помогает сосредоточиться на текущих требованиях и избежать излишней функциональности.
TDD стимулирует создание кода с хорошим дизайном, так как разработчики изначально проектируют код таким образом, чтобы он был легко тестируемым.
Тесты, написанные в рамках TDD, служат живой документацией, описывающей, как должен работать код. Это облегчает понимание кода другими разработчиками и упрощает процесс внесения изменений.
package main
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, but got %d", result)
}
}
package main
func Add(a, b int) int {
return a + b
}
В данном случае рефакторинг не требуется, так как код уже является простым и понятным.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Алгоритмы обеспечивают доступ к общим ресурсам в многопоточной среде без использования традиционных блокирующих механизмов, таких как мьютексы. Основная идея заключается в том, что хотя один или несколько потоков могут быть временно приостановлены, по крайней мере один поток всегда будет прогрессировать. Это достигается с помощью атомарных операций, которые гарантируют целостность данных и предотвращают состояния гонки.
Выполняются полностью или не выполняются вообще, без промежуточных состояний. Они обеспечивают безопасность и целостность данных в многопоточной среде.
Compare-And-Swap (CAS)Одна из самых распространенных атомарных операций, которая сравнивает текущее значение переменной с ожидаемым и, если они совпадают, обновляет переменную новым значением.
Fetch-And-Add: Атомарно увеличивает значение переменной и возвращает старое значение.
Lock-free алгоритмы стараются минимизировать критические секции, то есть участки кода, где потоки взаимодействуют с общими данными, чтобы уменьшить вероятность блокировок и состояний гонки.
В lock-free алгоритмах не используются традиционные механизмы блокировки, такие как мьютексы, что исключает вероятность взаимных блокировок (deadlocks).
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Node struct {
value int
next *Node
}
type Stack struct {
head *Node
}
func (s *Stack) Push(value int) {
newNode := &Node{value: value}
for {
oldHead := s.head
newNode.next = oldHead
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&s.head)), unsafe.Pointer(oldHead), unsafe.Pointer(newNode)) {
break
}
}
}
func (s *Stack) Pop() (int, bool) {
for {
oldHead := s.head
if oldHead == nil {
return 0, false
}
newHead := oldHead.next
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&s.head)), unsafe.Pointer(oldHead), unsafe.Pointer(newHead)) {
return oldHead.value, true
}
}
}
func main() {
stack := &Stack{}
stack.Push(1)
stack.Push(2)
value, ok := stack.Pop()
if ok {
fmt.Println("Popped:", value)
} else {
fmt.Println("Stack is empty")
}
}
Потоки не блокируют друг друга, что исключает проблему deadlock.
Параллельный доступ к данным без блокировок улучшает производительность, особенно на многоядерных системах.
Избегание блокировок снижает накладные расходы, связанные с контекстными переключениями и синхронизацией.
Lock-free алгоритмы сложны в реализации и отладке из-за необходимости работы с атомарными операциями и контроля состояний гонки.
Не все языки и платформы предоставляют полную поддержку атомарных операций.
Частые повторные попытки атомарных операций могут ухудшить масштабируемость в некоторых сценариях.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Этот подход обеспечивает ряд преимуществ, которые способствуют улучшению качества кода, его поддерживаемости и соответствию требованиям.
Написание тестов перед кодом помогает разработчикам четко определить требования к функции или модулю. Тесты выступают в роли спецификации, описывающей, как должен работать код. Это помогает избежать недоразумений и гарантирует, что все требования учтены с самого начала.
TDD заставляет разработчиков сосредоточиться на минимальной функциональности, необходимой для прохождения тестов. Это предотвращает избыточное проектирование и разработку ненужных функций. Каждый тест фокусируется на одном аспекте функциональности, что помогает создавать чистый и целенаправленный код.
Написание тестов перед кодом стимулирует создание модульного и хорошо структурированного кода. Поскольку тесты проверяют небольшие и изолированные части кода, это побуждает разработчиков разделять большие задачи на более мелкие и управляемые модули, что улучшает дизайн и архитектуру системы.
Тесты, написанные перед кодом, обеспечивают немедленную обратную связь. Если код не проходит тест, это означает, что он не соответствует требованиям. Это позволяет разработчикам быстро обнаруживать и исправлять ошибки, повышая уверенность в корректности кода.
Тесты служат живой документацией кода. Они показывают, как должен работать код и какие случаи использования поддерживаются. Это облегчает понимание кода другим разработчикам и упрощает его поддержку в будущем.
Постоянное написание тестов и их прохождение помогает поддерживать высокое качество кода. Тесты выявляют дефекты на ранних стадиях разработки и обеспечивают, что новые изменения не нарушают существующую функциональность.
package main
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, but got %d", result)
}
}
На этом этапе тест не пройдет, так как функция
Add
еще не реализована.package main
func Add(a, b int) int {
return a + b
}
Теперь тест пройдет, так как функция
Add
реализована корректно.При необходимости выполняется рефакторинг кода для улучшения его структуры, при этом тесты продолжают проходить, что гарантирует сохранение функциональности.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Модули сильно зависят друг от друга. Изменения в одном модуле требуют изменений в других.
Модуль выполняет несколько различных задач. Трудно поддерживать и изменять код.
Содержат слишком много логики. Длинные и сложные для понимания.
Один и тот же код повторяется в нескольких местах.
Код не покрыт тестами. Трудно проверять корректность.
Имена не отражают назначение. Трудно понять код.
Логика кода трудна для понимания. Много условных операторов.
Нет комментариев и документации.
Высокое сцепление:
type UserService struct {
userRepository UserRepository
emailService EmailService
}
Низкая связанность:
type UserService struct {
db Database
businessLogic BusinessLogic
notificationService NotificationService
}
Большие функции и классы:
func ProcessData() {
// Много строк кода
}
Дублирование кода:
func CalculateSum(a, b int) int {
return a + b
}
func AddNumbers(x, y int) int {
return x + y
}
Неразборчивые имена:
func DoSomething(x int) int {
return x * 2
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Включает различные методы, позволяющие процессам обмениваться данными и сигналами.
Однонаправленные каналы между родительским и дочерним процессами. Обмен данными между произвольными процессами.
Обмен сообщениями через общую очередь. System V, POSIX message queues.
Совместное использование памяти для обмена данными. POSIX (
shm_open
), System V shared memory.Синхронизация доступа к ресурсам. POSIX (
sem_open
), System V semaphores.Обмен данными через сетевые соединения. Unix domain sockets, TCP/UDP.
Отправка и обработка событий.
kill()
, signal()
в Unix.Обмен данными через файлы или базы данных. Файлы, SQLite.
Каналы (Pipes)
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
fd := make([]int, 2)
if err := syscall.Pipe(fd); err != nil {
fmt.Println("Ошибка создания канала:", err)
os.Exit(1)
}
pid, err := syscall.ForkExec("", nil, &syscall.ProcAttr{Files: []uintptr{uintptr(fd[0]), uintptr(fd[1]), uintptr(os.Stderr.Fd())}})
if err != nil {
fmt.Println("Ошибка создания процесса:", err)
os.Exit(1)
}
if pid == 0 { // Child process
syscall.Close(fd[0])
_, err := syscall.Write(fd[1], []byte("Hello"))
if err != nil {
fmt.Println("Ошибка записи в канал:", err)
}
syscall.Close(fd[1])
} else { // Parent process
buffer := make([]byte, 5)
syscall.Close(fd[1])
_, err := syscall.Read(fd[0], buffer)
if err != nil {
fmt.Println("Ошибка чтения из канала:", err)
}
syscall.Close(fd[0])
fmt.Printf("Получено: %s\n", buffer)
}
}
Общая память (Shared Memory)
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// Создание сегмента разделяемой памяти
segmentID, _, err := syscall.Syscall(syscall.SYS_SHMGET, uintptr(syscall.IPC_PRIVATE), uintptr(4), uintptr(syscall.S_IRUSR|syscall.S_IWUSR))
if err != 0 {
fmt.Println("Ошибка создания сегмента разделяемой памяти:", err)
os.Exit(1)
}
// Присоединение к сегменту
sharedMemory, _, err := syscall.Syscall(syscall.SYS_SHMAT, segmentID, 0, 0)
if err != 0 {
fmt.Println("Ошибка присоединения к разделяемой пам
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM