Параллелдик паттерндер: агым пулдары жана "өндүрүүчү-керектөөчү" маселеси
Жөнөкөй агымдардан кайра колдонулуучу, кулпуланбаган конструкцияларга чейин
std::thread ишке киргизүүнү, std::mutex менен жалпы маалыматты коргоону жана атомдук операциялар аркылуу эстек моделин түшүнүүнү билесиз. Булар примитивдер. Чыныгы системалар сейрек гана ар бир тапшырмага бир агым түзөт жана ар бир чакырууда синхрондоштурууну кол менен жазбайт. Анын ордуна алар бир нече кайра колдонулуучу паттерндерди чогултушат: белгиленген жумушчу агымдар топтомуна ээ агым пулу, алардын ортосунда ишти өткөрүп берген агым-коопсуз кезек, жана std::condition_variable үстүнө курулган өндүрүүчү-керектөөчү кол алышуу. Ушул үчөөнү өздөштүрсөңүз, агымдарды жоготпой, активдүү күтпөй жана жалпы маалыматка кагылышуу түзбөй, дээрлик каалаган параллелдик конвейерди кура аласыз.
- эмне үчүн ар бир тапшырмага бир агым масштабдалбай турганын жана агым пулу муну кантип чечерин түшүндүрүү;
std::mutexжанаstd::condition_variableменен чектелген агым-коопсуз кезек куруу;- туура кулпулоо жана ойготуу менен "өндүрүүчү-керектөөчү" паттернин ишке ашыруу;
- тапшырмаларды кабыл алып,
std::futureкайтарган кичине агым пулун чогултуу; - классикалык коркунучтарды таануу жана алдын алуу: жоготулган жана жалган ойготуулар, өз ара кулпулоолор жана маалымат кагылышуулары.
1. Эмне үчүн ар бир тапшырмага бир агым эмес?
Параллелизмге карата жөнөкөй ыкма мындай: ар бир иш бирдиги үчүн std::thread(doWork) чакыруу. Аны жазуу оңой жана масштабдаганда дээрлик дайыма туура эмес. Агымдар акысыз эмес:
- Түзүү наркы: ОС агымын пайда кылуу микросекунддарды жана өзүнчө стекти талап кылат (көбүнчө 1 МБ резервделет). Ар бир суроого агым түзгөн иштетүүчү муну кайра-кайра төлөйт.
- Ашыкча жазылуу (oversubscription): эгер 8 ядролуу машинада 10 000 тапшырма ар бири бир агым түзсө, планировщик ядролордон алда канча көп агымдардын ортосунда контекстти которуштуруп такалат, жана өткөрүмдүүлүк төмөндөйт.
- Чектелбеген ресурс колдонуу: агымдардын санын эч нерсе чектебейт, ошондуктан иштин күтүлбөгөн көбөйүшү эстекти же ОС дескрипторлорун түгөтүшү мүмкүн.
Агым пулу бул байланышты тескери айландырат. Ал бир жолу белгиленген сандагы узак жашаган жумушчу агымдарды түзөт (адатта std::thread::hardware_concurrency()), андан кийин аларга жалпы кезек аркылуу кичине тапшырмалардын агымын берет. Жумушчу агымдар циклде айланат: тапшырманы алат, аткарат, кайталайт. Агымдар кайра колдонулат, алардын саны чектелген, ал эми кезек табигый түрдө күтүлбөгөн көбөйүүлөрдү жумшартат. Бул дээрлик ар бир сервердин, агымга негизделген асинхрондук чөйрөнүн жана параллелдик алгоритм китепканасынын паттерни.