LINUX.ORG.RU

Продожить выполнение функции

 


0

1

Привет,

есть функция, в которую я хочу добавить возможность выполнения в «пошаговом» режиме. Выглядить это должно так. Я вызываю функцию, она что-то делает, потом возвращается. Когда я вызываю эту же функцию снова, то управление возвращается в то место, где оно прервалось. Функция опять что-то сделает и снова возвращается и т.д.

Для этого я думал использовать функции getcontext/setcontext. Вот код, который я хотел использовать:

void field_connect (field_t self)
{
  if (self->cont)
    {
      self->cont = 0;
      setcontext (&self->context);
    }
  self->cont = 0;

  int i;
  for (i = 0; i < self->paths->len; i++)
    {
      path_t cur_path;
      cur_path = g_array_index (self->paths, typeof (cur_path), i);

      struct point a;
      path_get_point (cur_path, 0, &a);

      int j;
      for (j = 1; j < cur_path->len; j++)
        {
          struct point b;
          path_get_point (cur_path, j, &b);
          self->fops.init_search (self, &a, &b, 0);
          int res;
          struct cell intersection;
          do
            {
              res = self->fops.wave_step (self, &intersection);
              if (self->step_by_step)
                {
                  self->cont = 1;
                  getcontext (&self->context);
                  printf ("getcontext\n");
                  if (self->cont)
                    return;
                }
            }
          while (res == 0);
          b = a;
        }
    }
  self->cont = 0;
}

Тут переменная cont говорит, нужно ли продолжать выполнение функции с какого-то места, step_by_step говорит о том, что функция должна работать в пошаговом режиме.

Проблема в том, что при смене контекста локальные переменные не сохраняются. Может тут нужно использовать функцию makecontext для того, чтобы как-то сохранить стек? Но курение манов не помогло мне с ней разобраться, поэтому и прошу помощи.

Интересно, что заставило тебя эмулировать продолжения на неподходящем для этого языке?

Могу посоветовать как-то переписать через конечный автомат, но, честно говоря, совершенно не представляю, как такое делается. Слышал, что грядущий компилятор C# умеет вытворять такое с async/await.

dave ★★★★★ ()
Ответ на: комментарий от r2d2

Ну вот self->fops.init_search и self->fops.wave_step — это коллбэки, но как реализовать то что я хоче я не знаю пока.

DesertFox ()
Ответ на: комментарий от dimon555

Наставить мьютексов в местах, где хочу прерваться? Или как?

DesertFox ()
Ответ на: комментарий от smap

Это своя реализация getcontext/setcontext. Но мне хотелось бы использовать уже готовую библиотеку. Если конкретезировать, то моя проблема в том, что стек не сохраняется. Наверняка я упустил что-то простое.

DesertFox ()

может ещё setjump и longjump поможет

dimon555 ★★★★★ ()
Ответ на: комментарий от dimon555

Спасибо, сделал через потоки. Но все равно было бы интересно что же я не так делал, что локальные переменные не сохранялись.

DesertFox ()

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

http://en.wikipedia.org/wiki/Setcontext

а в мане пишут, что из posix 2008 это выпелено и используйте треды.

dimon555 ★★★★★ ()

Вкратце:

1) Используйте makecontext & swapcontext вместо getcontext & setcontext. 2) Подумайте, что должно произойти, когда функция дойдет до конца. 3) Подумайте, в какой контекст функция должна возвращаться в промежуточные шаги.

Хинт: п.2 и п.3 в вашем коде сделаны таким образом, что возврат происходит совсем не туда, куда должен. А решение с makecontext и swapcontext довольно простое.

dmitry_vk ★★★ ()
Ответ на: комментарий от DesertFox

вот как в венде сделано из плюсов + локальные переменные сохраняются + просто использовать винда сама предоставляет апи для смены стека и регистров вот как это используется

class Multi : public Generator<int>
{
public:
  void proceed()
  {
    // функция должна быть вечной
    // если она завершиться это приведет
    // к остановке всего потока
    for(unsigned index=0;; index+=2)
      yield(index);    
  }  
};


int main()
{

  Multi gen;
  Multi gen2;
  for(unsigned index=0; index<100; ++index)
  {
    int a = gen();
    int b = gen2();
    printf("index = %d result = %d %d \n", index, a, b);
  }

  return 0;
}

очень просто и удобно в конце функции сопрограммы можно вернуть какое то особое знаение или boost.optional если такового нет чтобы потребитель плодов сопрограммы ее больше не вызывал и мог ее прибить для этого надо просто вывести из области видимости объект Multi тогда в деструкторе разрушится стек сопрограммы и освободятся все ресурсы

а тут код реализации кому интересно взято с кывта

#define _WIN32_WINNT 0x400

#include <windows.h>

#include <stdio.h>

////////////////////////////////////////////////////////////////////////////////

// занимается превращением потока в нить и 
// гарантирует, что это будет сделано один раз
class GenBase
{
public:
  static volatile LONG tls_;

  GenBase()
  {
    DWORD newtls = ::TlsAlloc();
    DWORD oldtls = ::InterlockedCompareExchange(&tls_, newtls, 0);
    if (oldtls) ::TlsFree(newtls);

    if (::TlsGetValue(tls_)==0)
    {
      ::TlsSetValue(tls_, (LPVOID)1);
      base_fiber_=::ConvertThreadToFiber(0);
    } else
    {
      base_fiber_=::GetCurrentFiber();
    }
  }

protected:
  LPVOID base_fiber_;
};

////////////////////////////////////////////////////////////////////////////////

// где-то в cpp файле

volatile LONG GenBase::tls_ = 0;


////////////////////////////////////////////////////////////////////////////////

// собственно генератор

template<typename Result>
class Generator : public GenBase
{
public:
  Generator()
  : terminated_(false)
  {
    child_fiber_=::CreateFiber(0, 
      FiberStartProc,
      reinterpret_cast<LPVOID>(this));
  }

  ~Generator()
  {
    DeleteFiber(child_fiber_);
  }

protected:

  void yield(Result res)
  {
    result_ = res;
    SwitchToFiber(base_fiber_);
  }

private:
  static void CALLBACK FiberStartProc(LPVOID context)
  {
    reinterpret_cast<Generator*>(context)->start_fiber();
  }

  virtual void proceed() {}
  void start_fiber()
  {
    proceed();    

    terminated_=true;
    SwitchToFiber(base_fiber_);
  }

public:

  Result operator()()
  {
    // завершение функии нити фатально
    if (terminated_) 
      throw "error";

    ::SwitchToFiber(child_fiber_);
    return result_;
  }


private:
  LPVOID child_fiber_;

  Result result_;
  bool terminated_;
};


class Multi : public Generator<int>
{
public:
  void proceed()
  {
    // функция должна быть вечной
    // если она завершиться это приведет
    // к остановке всего потока
    for(unsigned index=0;; index+=2)
      yield(index);    
  }  
};


int main()
{

  Multi gen;
  Multi gen2;
  for(unsigned index=0; index<100; ++index)
  {
    int a = gen();
    int b = gen2();
    printf("index = %d result = %d %d \n", index, a, b);
  }

  return 0;
}
anonymous ()
Ответ на: комментарий от anonymous

вот так более понятно

class Multi : public Generator<int>
{
public:
  void proceed()
  {
    // функция должна завершаться не через 
    // возврат управления а вызовом yield 
    // с особым значением обозначающим что 
    // сопрограмма закончена
 
    for(/* magic */)
      yield(product);

    yield(stopcode);    
  }  
};


int main()
{

  Multi gen;
  int product = gen();
  while (stopcode != product)
  {
    printf("product = %d \n", product);
    product = gen();
  }

  return 0;
}

как видите полноценная сопрограмма со своими переменными сохраняющими значения между «вызовами» (переключениями) так как имеет свой настоящий стек выделяемый системой

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.