← Назад к вопросам

Какие знаешь проблемы при нарушении Structured concurrency?

2.7 Senior🔥 71 комментариев
#Архитектура и паттерны#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какие знаешь проблемы при нарушении Structured concurrency?

Structured concurrency — это принцип, который требует, чтобы жизненный цикл корутин соответствовал структуре программы. Нарушение этого принципа приводит к серьёзным проблемам. Рассмотрю самые критичные из них.

1. Утечка памяти из-за зависших корутин

Проблема:

Если корутина создана, но её жизненный цикл не связан с областью видимости компонента, она может остаться в памяти после уничтожения компонента:

class BadViewModel : ViewModel() {
    init {
        // ПЛОХО: создаём корутину без структуры
        GlobalScope.launch {
            while (true) {
                delay(1000)
                println("Работаю")
            }
        }
    }
}

// ViewModel удаляется, но корутина продолжает работать!

Корутина продолжит выполняться в памяти, даже если ViewModel давно уничтожена.

Решение:

class GoodViewModel : ViewModel() {
    init {
        viewModelScope.launch {
            while (true) {
                delay(1000)
                println("Работаю")
            }
        }
        // Корутина автоматически отменяется при уничтожении ViewModel
    }
}

2. Потеря исключений

Проблема:

Без структурированного контекста исключения могут просто потеряться:

GlobalScope.launch {
    throw Exception("Ошибка")
    // Кто поймает эту ошибку? Никто!
}

println("Продолжаю работу")
// Программа не упадёт, но ошибка затеряется

Это затрудняет отладку и скрывает баги.

Решение:

val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    println("Поймали: ${exception.message}")
}

viewModelScope.launch(exceptionHandler) {
    throw Exception("Ошибка")
    // Будет перехвачена обработчиком
}

3. Race conditions и состояние гонки

Проблема:

Без правильной синхронизации нескольких корутин возникают гонки данных:

var counter = 0

repeat(1000) {
    GlobalScope.launch {
        counter++  // data race!
    }
}

delay(2000)
println(counter)  // Случайное число < 1000

Данные повреждаются, так как нет гарантии безопасности при параллельном доступе.

Решение:

val mutex = Mutex()
var counter = 0

repeat(1000) {
    viewModelScope.launch {
        mutex.withLock {
            counter++  // Безопасно
        }
    }
}

4. Неконтролируемое создание потоков

Проблема:

Без структуры легко случайно создать тысячи потоков:

repeat(10000) {
    thread {
        // Плохо: создаём 10000 потоков!
        Thread.sleep(10000)
    }
}
// OutOfMemoryError!

Это быстро приводит к исчерпанию ресурсов.

Решение:

repeat(10000) {
    viewModelScope.launch(Dispatchers.Default) {
        delay(10000)
        // Корутины используют пул потоков, не создавая новые потоки
    }
}

5. Отмена не распространяется

Проблема:

Если нет иерархии Job, отмена не влияет на дочерние корутины:

val parentJob = Job()
val scope = CoroutineScope(parentJob)

scope.launch {
    launch {
        println("Дочерняя 1")
    }
    launch {
        println("Дочерняя 2")
    }
}

parentJob.cancel() // Отменяются обе дочерние корутины

Но если использовать GlobalScope:

GlobalScope.launch {
    GlobalScope.launch { println("Зависла") }  // Не может быть отменена
}

6. Неправильный жизненный цикл UI

Проблема:

Если корутина не привязана к жизненному циклу Activity/Fragment, она может обновлять UI после уничтожения:

class BadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        GlobalScope.launch {
            val data = fetchData()
            // Activity может быть уничтожена, а мы всё ещё обновляем UI!
            textView.text = data
        }
    }
}

Это приводит к краху приложения.

Решение:

class GoodActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.data.collect { data ->
                    textView.text = data
                }
            }
        }
    }
}

7. Deadlocks и ожидание

Проблема:

Без структуры легко создать deadlock:

val job = GlobalScope.launch {
    delay(1000)
}

runBlocking { // Блокирует главный поток!
    job.join()
}
// Если это главный поток Android, приложение зависает

Сравнение подходов

ПроблемаGlobalScopeviewModelScopelifecycleScope
Утечка памятиДаНетНет
Потеря исключенийДаМожноМожно
Race conditionsДаВозможныВозможны
Отмена при уничтоженииНетДаДа
Жизненный цикл UIНетХорошоИдеально

Заключение

Structured concurrency обеспечивает безопасность и предсказуемость асинхронного кода. Нарушение этого принципа приводит к утечкам памяти, потере ошибок, race conditions и сложным в отладке багам. Всегда привязывай корутины к структуре программы через viewModelScope, lifecycleScope или другие структурированные контексты.

Какие знаешь проблемы при нарушении Structured concurrency? | PrepBro