Cómo crear un grupo de subprocesos en vc
Hay muchos subprocesos creados por aplicaciones que pasan mucho tiempo inactivos, esperando que ocurran eventos. También hay algunos subprocesos que entran en estado de suspensión y se activan periódicamente para cambiar o actualizar la información de estado en modo de sondeo. Los grupos de subprocesos le permiten utilizar subprocesos de manera más eficiente al proporcionar a su aplicación un grupo de subprocesos de trabajo administrado por el sistema. Habrá al menos un subproceso para monitorear todas las operaciones en espera colocadas en el grupo de subprocesos. Cuando se complete la operación de espera, habrá un subproceso de trabajo en el grupo de subprocesos para ejecutar la función de devolución de llamada correspondiente.
También puede colocar elementos de trabajo sin operaciones de espera en el grupo de subprocesos, usar la función QueueUserWorkItem para completar este trabajo y pasar la función del elemento de trabajo que se ejecutará al grupo de subprocesos a través de un parámetro. Una vez que un elemento de trabajo se coloca en el grupo de subprocesos, no se puede cancelar.
Los temporizadores de cola y las operaciones de espera registrada también se implementan mediante grupos de subprocesos. Sus funciones de devolución de llamada también se colocan en el grupo de subprocesos. También puede utilizar la función BindIOCompletionCallback para entregar una operación de IO asincrónica en el puerto de finalización de IO, la función de devolución de llamada también la ejecuta el subproceso del grupo de subprocesos.
Cuando se llama a la función QueueUserWorkItem o a la función BindIOCompletionCallback por primera vez, el grupo de subprocesos se crea automáticamente, o cuando los temporizadores de cola de temporizador o las operaciones de espera registradas se colocan en la función de devolución de llamada, el grupo de subprocesos también puede ser creado. El grupo de subprocesos puede crear una cantidad ilimitada de subprocesos, limitados únicamente por la memoria disponible. Cada subproceso utiliza el tamaño de pila inicial predeterminado y se ejecuta con la prioridad predeterminada.
Hay dos tipos de subprocesos en el grupo de subprocesos: subprocesos IO y subprocesos que no son IO. El subproceso IO espera en estado de alarma y el elemento de trabajo se coloca en el subproceso IO como un APC. Si su proyecto de trabajo requiere la ejecución de un subproceso en un estado de alerta, debe colocarlo en el subproceso IO.
Los subprocesos de trabajo que no son IO esperan en el puerto de finalización de IO. El uso de subprocesos que no son IO es más eficiente que los subprocesos IO. En otras palabras, siempre que sea posible, intente utilizar subprocesos que no sean IO. Ni los subprocesos IO ni los subprocesos que no sean IO saldrán antes de que se complete la operación IO asincrónica. Sin embargo, no emita solicitudes de IO asincrónicas en subprocesos que no sean de IO y que tarden mucho en completarse.
La forma correcta de utilizar el grupo de subprocesos es que la función del elemento de trabajo y todas las funciones que llamará deben ser seguras para el grupo de subprocesos. Las funciones seguras no deben asumir que los hilos sean desechables o permanentes. En general, debe evitar el uso de almacenamiento local de subprocesos y la emisión de llamadas de E/S asincrónicas que requieran un subproceso persistente, como la función RegNotifyChangeKeyValue. Si necesita ejecutar dicha función en un hilo permanente, puede pasar una opción WT_EXECUTEINPERSISTENTTHREAD a QueueUserWorkItem.
Tenga en cuenta que los grupos de subprocesos no son compatibles con el modelo de apartamento de subproceso único (STA) de COM.
Para explicar más profundamente las ventajas del grupo de subprocesos implementado por el sistema operativo, primero intentamos implementar un modelo de grupo de subprocesos simple nosotros mismos.
El código es el siguiente:
/**//*************************** ***** ********************************************** /
/**//* Probar nuestro propio grupo de subprocesos */
/**//**************** ******** ******************************************* ********* /
typedef struct _THREAD_POOL
{
HANDLE QuitEvent;
HANDLE WorkItemSemaphore; p>
LONG WorkItemCount;
LIST_ENTRY WorkItemHeader;
CRITICAL_SECTION WorkItemLock;
LONG ThreadNum;
HANDLE *ThreadsArray;
}THREAD_POOL, *PTHREAD_POOL;
typedef VOID (*WORK_ITEM_PROC)(PVOID Param);
typedef struct _WORK_ITEM
{ p>
Lista LIST_ENTRY;
WORK_ITEM_PROC UserProc;
PVOID UserParam;
}WORK_ITEM, *PWORK_ITEM;
DWORD WINAPI WorkerThread(PVOID pParam)
{
PTHREAD_POOL pThreadPool = (PTHREAD_POOL)pParam;
HANDLE Eventos[2];
Eventos[ 0] = pThreadPool->QuitEvent;
Eventos[1] = pThreadPool->WorkItemSemaphore;
for(;;)
{
DWORD dwRet = WaitForMultipleObjects(2, Eventos, FALSO, INFINITO);
if(dwRet == WAIT_OBJECT_0)
break;
// p>
// ejecuta el proceso del usuario.
//
else if(dwRet == WAIT_OBJECT_0 +1)
{
PWORK_ITEM pWorkIte
m;
PLIST_ENTRY pList;
EnterCriticalSection(&pThreadPool->WorkItemLock);
_ASSERT(!IsListEmpty(&pThreadPool->WorkItemHeader));
pList = RemoveHeadList(&pThreadPool->WorkItemHeader);
LeaveCriticalSection(&pThreadPool->WorkItemLock);
pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM, List);
pWorkItem->UserProc(pWorkItem->UserParam);
InterlockedDecrement(&pThreadPool->WorkItemCount);
gratis(pWorkItem);
}
else
{
_ASSERT(0);
descanso;
}
}
devuelve 0;
}
BOOL InicializarThreadPool(PTHREAD_POOL pThreadPool, LONG ThreadNum)
{
pThreadPool->QuitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
pThreadPool->WorkItemSemaphore = CreateSemaphore(NULL, 0, 0x7FFFFFFFF, NULL);
pThreadPool-> WorkItemCount = 0;
InitializeListHead(&pThreadPool->WorkItemHeader);
InitializeCriticalSection(&pThreadPool->WorkItemLock);
pThreadPool->ThreadNum = ThreadNum; p> p>
pThreadPool->ThreadsArray = (HANDLE*)malloc(sizeof(HANDLE) * ThreadNum);
for(int i=0; i { pThreadPool-& gt;ThreadsArray[i] = CreateThread(NULL, 0, WorkerThread, pThreadPool, 0, NULL); } devuelve TRUE; } p> p> VOID DestroyThreadPool(PTHREAD_POOL pThreadPool) { SetEvent(pThreadPool->QuitEvent); for(int i=0; i< pThreadPool->ThreadNum; i++) { WaitForSingleObject(pThreadPool->ThreadsArray[i], INFINITE); CloseHandle(pThreadPool->ThreadsArray [i ]); } gratis(pThreadPool->ThreadsArray); CloseHandle(pThreadPool->QuitEvent); CloseHandle( pThreadPool->WorkItemSemaphore); DeleteCriticalSection(&pThreadPool->WorkItemLock); while(!IsListEmpty(&pThreadPool->WorkItemHeader)) { PWORK_ITEM pWorkItem; PLIST_ENTRY pList; pList = RemoveHeadList(&pThreadPool->WorkItemHeader); pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM , Lista ); free(pWorkItem); } } BOOL PostWorkItem(PTHREAD_POOL pThreadPool, WORK_ITEM_PROC UserProc, PVOID UserParam ) { PWORK_ITEM pWorkItem = (PWORK_ITEM)malloc(sizeof(WORK_ITEM)); if(pWorkItem == NULL) devolver FALSO; pWorkItem->UserProc = UserProc; pWorkItem->UserParam = UserParam; EnterCriticalSection(&pThreadPool->WorkItem Lock); InsertTailList(&pThreadPool->WorkItemHeader, &pWorkItem->List); LeaveCriticalSection(&pThreadPool->WorkItemLock); InterlockedIncrement(&pThreadPool-> WorkItemCount); ReleaseSemaphore(pThreadPool->WorkItemSemaphore, 1, NULL); devuelve TRUE; } VOID UserProc1( PVOID dwParam) { WorkItem(dwParam); } void TestSimpleThreadPool(BOOL bWaitMode, LONG ThreadNum) { THREAD_POOL ThreadPool; InitializeThreadPool(&ThreadPool, ThreadNum); CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL); BeginTime = GetTickCount(); ItemCount = 20; for(int i=0; i<20; i++) { PostWorkItem(&ThreadPool, UserProc1, (PVOID)bWaitMode); } WaitForSingleObject(CompleteEvent, INFINITE); CloseHandle (CompleteEvent); DestroyThreadPool(&ThreadPool); } Colocamos el elemento de trabajo en una cola y usamos un semáforo para notificar al grupo de subprocesos. El subproceso en el grupo saca un elemento de trabajo para su ejecución. Una vez completada la ejecución, el subproceso regresa al grupo de subprocesos y continúa esperando nuevos elementos de trabajo. El número de subprocesos en el grupo de subprocesos es fijo, creado previamente y permanente. Estos subprocesos no se destruirán hasta que se destruya el grupo de subprocesos. Las posibilidades de que los subprocesos en el grupo de subprocesos obtengan elementos de trabajo son iguales y aleatorias. No existe una forma especial de garantizar qué subproceso tiene una prioridad especial para obtener elementos de trabajo. Además, no hay límite para la cantidad de subprocesos que pueden ejecutarse simultáneamente al mismo tiempo. De hecho, en nuestro código de demostración que realiza tareas computacionales, todos los subprocesos se ejecutan al mismo tiempo. A continuación, echemos un vistazo a cómo funciona el grupo de subprocesos proporcionado por el sistema para completar la misma tarea. /**//*************************************** *************************************/ /* *//* Prueba de QueueWorkItem */ /**//******************************. ******** *************************************/ DWORD BeginTime; p> LONG ItemCount; HANDLE CompleteEvent; int compute() { srand(BeginTime); for(int i=0; i<20 *1000 * 1000; i++) rand(); return rand(); } DWORD WINAPI WorkItem(LPVOID lpParameter) { BOOL bWaitMode = (BOOL)lpParameter; if( bWaitMode) Sleep(1000); else compute(); if( InterlockedDecrement(&ItemCount) == 0) { printf("Tiempo total %d segundo.\n", GetTickCount() - BeginTime); SetEvent(CompleteEvent); } devuelve 0; } void TestWorkItem(BOOL bWaitMode, DWORD Flag) { CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL); BeginTime = GetTickCount(); ItemCount = 20; for( int i=0; i<20; i++) { QueueUserWorkItem(WorkItem, (PVOID)bWaitMode, Flag); } WaitForSingleObject(CompleteEvent, INFINITE); CloseHandle(CompleteEvent); } Bastante simple, ¿verdad? Solo necesitamos centrarnos en nuestra función de devolución de llamada. Pero en comparación con nuestra simple simulación, el grupo de subprocesos proporcionado por el sistema tiene más ventajas. En primer lugar, la cantidad de subprocesos en el grupo de subprocesos se ajusta dinámicamente. En segundo lugar, el grupo de subprocesos utiliza las características del puerto de finalización de IO, lo que puede limitar la cantidad de subprocesos que se ejecutan simultáneamente. estará limitado al número de CPU, lo que puede reducir el cambio de subprocesos. Selecciona el hilo ejecutado más recientemente y lo pone en ejecución nuevamente, evitando así cambios de hilo innecesarios.