1 Востаннє редагувалося Arete (25.09.2014 12:47:11)

Тема: Багатопотоковість, бібліотека ZThread та метод interrupt().

Всім привіт!

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

Як працює вся ця біда (в моєму розумінні).

ZThread має клас Thread, об’єкти якого створюють новий потік програми. Для цього щоб цей потік працював йому в конструктор потрібно передати посилання на об’єкт спеціального класу. "Спеціальність" класу полягає в тому що він успадкований від класу Runnable. Runnable простий асбтрактний клас лише з деструктором та одним методом - run.

Runnable declaration
  class Runnable {
  public:

    /**
     * Runnables should never throw in their destructors
     */
    virtual ~Runnable() {}

    /**
     * Task to be performed in another thread of execution
     */
    virtual void run() = 0;

  };

Створюється клас(наприклад InterruptTest) , успадкований від Runnable, і в цьому класі визначається метод void run(). В методі run(), зазвичай, пишеться цикл який виконує певну роботу. Далі, для того щоб запустити цю роботу в окремому потоці треба створити потік - об’єкт класу Thread - якому потрібно передати в конструктор посилання на об’єкт класу InterruptTest.

ZThread::Thread thread( new InterruptTest );

Потік стартує і запускає метод run() об’єкта класу InterruptTest. В методі run() крутиться собі цикл який виконує певну роботу. При виході з циклу і завершенні методу run() потік теж вважається завершеним і він знищується.


Потік типу Thread може викликати метод interrupt, який встановлює мітку переривання потоку, якось так... Тобто цю мітку можна використовувати як умову виходу з циклу метода run():

void InterruptTest::run() {
  while( !ZThread::Thread::interrupted() ) { // перевірка перервання роботи потоку
    ...
}

Але є одне але. Якщо заблокувати потік, який знаходиться в стані interrupted, але ще не завершився, то буде піднятий вийняток Interrupted_Exception, який завершить роботу методу run(). Тобто є дві "можливості" завершення потоку за допомогою методу interrupt - перевірка умови циклу та підняття вийнятку.


Якщо розкоментувати стрічку ZThread::Thread::sleep( 100 ), яка блокує потік на певний час, то під час блокування потоку підніметься вийняток Interrupted_Exception і результат буде таким -   кілька стрічок "New iteration", а потім стрічка "Exiting via Interrupted_Exception" із блоку catch.
Якщо ж цю стрічку закоментувати то результат повинен бути -  кілька стрічок "New iteration", а потім стрічка "Exiting via while() test".

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


#include <iostream>
#include <zthread/Thread.h>

using std::cout;
using std::endl;

const double PI = 3.1415965358979323846;
const double E = 2.718281824590452354;

class InterruptTest : public ZThread::Runnable {
private:
  volatile double _d;
public:
  InterruptTest() : _d( 0.0 ) {};
  void run();
};

void InterruptTest::run() {
  try {
    while( !ZThread::Thread::interrupted() ) {
      cout << "New iteration" << endl;
//       ZThread::Thread::sleep( 100 );
      for( int i = 0; i < 10000000; ++i )
        _d = _d + ( PI + E ) / static_cast< double >( i );
    }
    cout << "Exiting via while() test" << endl;
  }
  catch( ZThread::Interrupted_Exception& e ) {
    cout << "Exiting via Interrupted_Exception" << endl;
  }
}

int main( int argc, char **argv ) {

  int sleep = 1000;
  if( argc > 1 )
    sleep = std::atoi( argv[ 1 ] );

  try {
    // створюємо потік thread
    ZThread::Thread thread( new InterruptTest );
    // робимо затримку основного потоку
    ZThread::Thread::sleep( sleep );
    // "перериваємо" потік thread
    thread.interrupt();
  }
  catch( ZThread::Synchronization_Exception& e ) {
    cout << e.what() << endl;
  }
  return 0;
}

Є один важливий нюанс - метод interrupted() скидає статус переривання потоку. Тобто для кожного виклику interrupt() перевірка статусу interrupted() спрацьовує тільки один раз. "Це гарантує що бібліотека не оповістить про переривання двічі.". Можливо, проблема криється саме в цьому, але поки що це мені нічого не дало...

Подякували: Дмитро-Чебурашка1

2

Re: Багатопотоковість, бібліотека ZThread та метод interrupt().

Не знаю, але додайте thread.wait() після thread.interrupt. За логікою час існування об'єкту thread обмежено дужкою }, як далі управляється поток - я не знаю.

Подякували: Arete1

3

Re: Багатопотоковість, бібліотека ZThread та метод interrupt().

koala написав:

За логікою час існування об'єкту thread обмежено дужкою }, як далі управляється поток - я не знаю.

Так, я невірно виразився, сам об’єкт існує, але після завершення методу run() потік ігнорується і йому не надається процесорний час.

koala написав:

Не знаю, але додайте thread.wait() після thread.interrupt.

Дякую, так працює, але я не зрозумів чому :)

4

Re: Багатопотоковість, бібліотека ZThread та метод interrupt().

Якщо я правий, то тому що об'єкт Thread thread, створений в основному потоці, припиняє своє існування в ньому ж, і звертання до його властивостей (зокрема, через interrupted() ) - це UB. А thread.wait() змушує основний потік чекати на завершення thread, і тільки потім його знищує. Можете спробувати ще переписати код у вигляді

try {
  // створюємо потік thread
  ZThread::Thread *thread = new thread( new InterruptTest );
  // робимо затримку основного потоку
  ZThread::Thread::sleep( sleep );
  // "перериваємо" потік thread
  thread->interrupt();
}

це теж має спрацювати, оскільки thread тут не знищується в основному потоці.

5 Востаннє редагувалося Arete (25.09.2014 15:14:40)

Re: Багатопотоковість, бібліотека ZThread та метод interrupt().

Варіант з вказівником не працює - цикл не переривається.
Метод thread.wait() робить саме те що ви сказали  -

/**
  * Wait for the thread represented by this object to complete its task.
  * The calling thread is blocked until the thread represented by this
  * object exits.
**/

Мій код - це спрощений приклад з книжки "Thinking in C++", де interrupt використовується точно таким чином. В прикладі  пропонується погратися з часом блокування основного потоку для того щоб піймати обидва випадки виходу з потоку. Але мені не вдається це зробити ні з прикладом з книги ні з моїм спрощеним. Якщо закоментувати стрічку зі sleep то потік не переривається в обох варіантах.

Посилання на книгу, приклад називається "Checking for an interrupt" - http://www.linuxtopia.org/online_books/ … g_281.html.

В книзі нічого не сказано про недоступність потоку через об’єкт thread, натоміть в багатьох місцях він використовується як і в прикладі. Ось що написано про Thread:

When you create a Thread object, the associated thread is registered with the threading system, which keeps it alive. Even though the stack-based Thread object is lost, the thread itself lives on until its associated task completes. Although this may be counterintuitive from a C++ standpoint, the concept of threads is a departure from the norm: a thread creates a separate thread of execution that persists after the function call ends. This departure is reflected in the persistence of the underlying thread after the object vanishes.

Опис методу interrupt() класу Thread

    /*
     * Interrupts this thread, setting the interrupted status of the thread.
     * This status is cleared by one of three methods.
     *
     * If this thread is blocked when this method is called, the thread will
     * abort that blocking operation with an Interrupted_Exception.
     *
     *  - The first is by attempting an operation on a synchronization object that
     *    would normally block the calling thread; Instead of blocking the calling
     *    the calling thread, the function that would normally block will thrown an
     *    Interrupted_Exception and clear the interrupted status of the thread.
     *
     *  - The second is by calling Thread::interrupted().
     *
     *  - The third is by calling Thread::canceled().
     *
     * Threads already blocked by an operation on a synchronization object will abort
     * that operation with an Interrupted_Exception, clearing the threads interrupted
     * status as in the first case described above.
     *
     * Interrupting a thread that is no longer running will have no effect.
     */