Механізми синхронізації потоків у Python

У цій статті ми розглянемо, як синхронізувати доступ до загальних ресурсів та як координувати виконання потоків.

Синхронізація доступу до загальних ресурсів

Одна важлива проблема у випадках, коли використовуютьсяпотоки, це уникнення конфліктів, коли більше одного потоку потребує отримання доступу до однієї і тієї жзмінної або ресурсу. Якщо ви будете діяти неуважно, то перекриваючі доступи або модифікації з декількох потоків можуть спричинити безліч різних проблем, і що найстрашніше, ці проблеми мають тенденцію виникати тільки при сильному завантаженні або у ваших виробничих серверах, або на більш швидкому залозі, що використовується одним із ваших користувачів. Наприклад, розглянемо програму, яка виконує певний процес та відстежує, скільки разів цей процес був виконаний:

Якщо ви викликаєте цю функцію більш ніж одного потоку, ви виявите, що підрахунок відбувається не дуже акуратно. Це працює у більшості випадків, але іноді можуть виникнути проблеми. Причина в тому, що додаткові операції виконуються у три етапи. У першому етапі інтерпретаторотримує поточне значення лічильника, після чого підраховує нове значення, і нарешті,вписує нове значення назад в змінну.

Якщоінший потік отримає контроль після того, якпоточний потік витягнув змінну, він може витягти змінну, збільшити її і вписати назад, перед тим як нинішній потік зробить те саме. І оскільки вони обидва бачать те ж вихідне значення, тільки один з потоків отримає до нього доступ. Інша проблема це доступ до неповного або неузгодженого стану, що може статися, якщо один із потоків ініціалізує або оновлює нетривіальну структуруданих, при цьому інший потік намагається прочитати структуру, поки вона оновлюється.

Атомарні операції

Найпростішийспосіб синхронізації із загальними змінними або іншими ресурсами, це покластися наатомарні операції в інтерпретаторі.Атомарна операція - це операція, яка здійснює лише один етап виконання завдання, без жодних шансів того, що інший потік отримає контроль. Загалом такий підхід працює, якщо загальний ресурс складається з одного екземпляра даних кореневого типу, типу змінного рядка, числа, словника чи списку. Ось приклад кількох безпечних операцій:

  • Читання чи заміна екземпляра атрибута;
  • Читання чи заміна глобальної змінної;
  • Вибір елемента зі списку;
  • Модифікація списку дома (іншими словами, додавання об'єкта з допомогоюappend );
  • Вибірка об'єкта у словнику;
  • Модифікація словника дома (іншими словами, додавання об'єкта чи виклик методуclear )

Як було сказано раніше, операції, які зчитують змінну або атрибут, модифікують, після чого вписують назад, не єзахищеними від потоків. Інший потік може оновити змінну після того, як вона була прочитана поточним потоком, а перед цим оновлена. Також зверніть увагу на те, що код Python може бути виконаний, коли об'єкти зруйновані, так що навіть проста, на перший погляд, операція може спровокувати інші потоки на дії, що може повести за собоюконфлікти. Якщо ви сумніваєтеся, використовуйтеблокування.

Блокування (замки)

Блокування - це фундаментальний механізм синхронізації, який наданий модулем threading Python. Замок можеутримуватись одним потоком у будь-який час, або без потоку взагалі. Якщо потік спробує утримати один замок, який вже утримується іншим потоком, виконання першого потоку буде зупинено, поки не буде знято блокування . Замки зазвичай використовуються для синхронізації доступу дозагальних ресурсів. Для кожного джерела створюється об'єктLock. Коли вам потрібно отримати доступ до ресурсу, викличтеacquire для того, щоб поставити блок, після чого викличтеrelease :