Процедура передачи потока с несколькими переменными и размером стека

#c #windows #multithreading

#c #Windows #многопоточность

Вопрос:

В примерах в Интернете вызовы CreateThread обычно передают указатель на структуру для LPVOID lpParameter , и вы используете этот указатель для доступа к самой структуре.

 #include <Windows.h>
#include <stdio.h>

struct Point
{
  float x,y,z ;
} ;

DWORD WINAPI threadStartPoint( LPVOID data )
{
  Sleep( 1000 ) ;
  Point *p = (Point*)data ;
  printf( "%f %f %fn", p->x, p->y, p->z ) ;
  puts( "Thread job done" ) ;
  return 0 ;
}

// From main
int main()
{
  DWORD threadId ;
  Point p ;
  p.x=2, p.y=3, p.z=4 ;
  HANDLE handle = CreateThread( 0, 0, 
    threadStartPoint,
    (LPVOID)amp;p,
    0,  // ?? I think I should be using this parameter</b>
    amp;threadId
  ) ;

  if( !handle )
  {
    // Thread creation failed
    puts( "start failn" );
  }
  else
  {
    printf( "started on threadid=%dn", threadId ) ;
  }

  WaitForSingleObject( handle, 2000 ) ; // wait up to 2000 ms for the other thread to complete before moving on

  puts( "main thread Exiting.." ) ;
  //system( "pause" ) ;
}
  

Я нахожу это в некоторой степени неудобным, поскольку вы должны убедиться, что struct существует, и убедиться, что он должным образом уничтожен, когда поток завершает выполнение.

Я хотел бы запустить свой поток, но передаю обычные аргументы стека, т. Е. автоматические переменные, или, возможно, struct саму процедуру запуска потока:

DWORD threadStartPointFuncStyleIWant (Общий доступ к данным) ;

Итак, мои вопросы действительно:

  • Для начальной точки потока (CreateThread) ограничены ли мы функцией с прототипом формы:
DWORD validThreadFunc(LPVOID pParamStruct ) ;
  • Или мы можем запустить поток для таких функций, как
DWORD threadFunc1(int p1, int p2) ;
DWORD threadFunc2(Data d) ;

Ответ №1:

CreateThread принимает только один тип функции, тот, который принимает один LPVOID параметр. Нет способа связаться с kernel32.dll это код, который вызывает вашу потоковую функцию, что он должен вызывать ее с любым другим списком параметров. Ядро всегда вызывает функцию одним и тем же способом.

Вам просто нужно продолжать делать это так же, как и все остальные. Выделите структуру в куче, передайте указатель вашей процедуре потока, а затем освободите ее перед возвратом из вашей процедуры потока. То есть право собственности переходит от исходного потока к новому потоку. Как только новый поток получит право собственности, вы можете использовать интеллектуальный указатель, подобный shared_ptr , чтобы убедиться, что он освобождается, как бы вы ни покидали поток.

Вы можете передать другой указатель на функцию в этой структуре или передать объект с собственными методами. Тогда ваша потоковая процедура становится ничем иным, как чем-то для распаковки LPVOID параметра и отправки другому указателю на функцию или методу, где вы выполняете всю реальную работу за поток.

Ответ №2:

Выполнение того, что вы хотите, хотя и не является невозможным, потребовало бы такого интенсивного сотрудничества между компилятором / -ами и ОС, что цели проектирования ОС были бы поставлены под угрозу.

Чтобы создать поток, ОС должна выделить стек и инициализировать его так, чтобы верхняя часть фрейма стека выглядела так, как будто поток выполнялся ранее и был прерван. Чтобы запустить поток, выполняющийся в первый раз, ОС затем выполняет возврат прерывания, т.Е. потоки никогда не вызываются операционной системой, они всегда возвращаются. Чтобы предоставить формат параметра, который является переменным, операционной системе необходимо было бы знать о длине блока параметров, чтобы она могла скопировать параметры из стека вызывающего потока в стек нового потока перед запуском фрейма прерывания. Вы видите, каким беспорядком это становится? Что произойдет, если поток ctor и новый поток имеют разные соглашения о вызовах?

Проще / безопаснее просто передать один параметр указателя, который может быть «передан» в регистре. В случае OO-языков, таких как C , это было бы «this», чтобы новый поток мог получить доступ к своим собственным элементам данных и указателю vtable.

Rgds, Мартин

Ответ №3:

Вы могли бы использовать библиотеку потоков Boost в дополнение к вызову boost::bind, чтобы получить что-то вроде того, что вы хотите:

 #include <boostthread.hpp>

struct Point
{
    float x,y,z;
};

void threadStartPoint( Point p )
{
  Sleep( 1000 ) ;
  printf( "%f %f %fn", p.x, p.y, p.z ) ;
}



int main(int argc, char** argv)
{
    Point p;
    p.x = 1;
    p.y = 2;
    p.z = 3;

    // start the thread.
    // first argument to "bind" is the worker thread function.
    boost::thread t(boost::bind(threadStartPoint, p));

    // wait for thread to exit
    t.join();

    return 0;
}
  

Будьте очень осторожны, чтобы не передавать указатели, исходящие из стека, в порождающий поток.