Clase de hilo en Delphi
Raptor[Estudio mental]
http://mental.mentsu.com
parte 2
El primero es el constructor:
constructor TThread.Create(CreateSuspended: booleano);
comenzar
heredado Crear;
Agregar hilo;
FSuspendida := CrearSuspendida;
FCreateSuspended := CreateSuspended;
FHandle := BeginThread(nil, 0, @ThreadPRoc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
si FHandle = 0 entonces
elevar EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
fin;
Aunque este constructor no tiene mucho código, puede considerarse como el miembro más importante porque el hilo se crea aquí.
Después de llamar a TObject.Create a través de Inherited, la primera oración es llamar a un proceso: AddThread, y su código fuente es el siguiente:
procedimiento AddThread;
comenzar
Incremento entrelazado (ThreadCount);
fin;
También hay un RemoveThread correspondiente:
procedimiento Quitar Hilo;
comenzar
Decremento entrelazado(ThreadCount);
fin;
Su función es muy simple, que es contar el número de subprocesos en el proceso aumentando o disminuyendo una variable global. Es solo que el proceso Inc/Dec comúnmente usado no se usa para aumentar o disminuir variables aquí, pero se usa el par de procesos InterlockedIncrement/InterlockedDecrement. Implementan exactamente la misma función, tanto sumando como restando uno a la variable. Pero tienen una gran diferencia: InterlockedIncrement/InterlockedDecrement es seguro para subprocesos. Es decir, pueden garantizar resultados de ejecución correctos en subprocesos múltiples, pero Inc/Dec no. O en términos de la teoría de sistemas operativos, se trata de un par de operaciones "primitivas".
Tome más uno como ejemplo para ilustrar la diferencia en los detalles de implementación entre los dos:
En términos generales, la operación de agregar uno a los datos de la memoria tiene tres pasos después de la descomposición:
1. Leer datos de la memoria
2. Datos más uno
3. Almacenar en la memoria
Ahora supongamos una situación que puede ocurrir al usar Inc para realizar una operación de incremento en una aplicación de dos subprocesos:
1. El hilo A lee datos de la memoria (se supone que son 3)
2. El hilo B lee datos de la memoria (también 3)
3. El hilo A agrega uno a los datos (ahora son 4)
4. El hilo B agrega uno a los datos (ahora también es 4)
5. El subproceso A almacena los datos en la memoria (los datos en la memoria ahora son 4)
6. El subproceso B también almacena los datos en la memoria (los datos en la memoria todavía son 4, pero ambos subprocesos le han agregado uno, que debería ser 5, por lo que hay un resultado incorrecto aquí)
No existe tal problema con el proceso InterlockIncrement, porque la llamada "primitiva" es una operación ininterrumpible, es decir, el sistema operativo puede garantizar que no se producirá el cambio de subproceso antes de que se ejecute una "primitiva". Entonces, en el ejemplo anterior, solo después de que el subproceso A termine de ejecutar y almacenar los datos en la memoria, el subproceso B puede comenzar a buscar el número y agregar uno. Esto garantiza que incluso en una situación de subprocesos múltiples, el resultado será el mismo. correcto.
El ejemplo anterior también ilustra una situación de "conflicto de acceso a subprocesos", por lo que los subprocesos deben "sincronizarse" (Sincronizar). Esto se discutirá en detalle más adelante cuando se mencione la sincronización.
Hablando de sincronización, hay una digresión: Li Ming, profesor de la Universidad de Waterloo en Canadá, una vez objetó que la palabra Sincronizar se tradujera como "sincronización" en "sincronización de subprocesos". Personalmente, creo que lo que dijo es en realidad muy. razonable. "Sincronización" en chino significa "suceder al mismo tiempo", y el propósito de la "sincronización de subprocesos" es evitar que esto "suceda al mismo tiempo". En inglés, Sincronizar tiene dos significados: uno es sincronización en el sentido tradicional (Ocurrir al mismo tiempo) y el otro es "Operar al unísono" (Operar al unísono). La palabra Sincronizar en "sincronización de subprocesos" debe referirse al último significado, es decir, "garantizar que varios subprocesos mantengan la coordinación y eviten errores al acceder a los mismos datos". Sin embargo, todavía hay muchas palabras traducidas incorrectamente como esta en la industria de TI. Dado que se ha convertido en una convención, este artículo continuará usándolas aquí, porque el desarrollo de software es un trabajo meticuloso y debe aclararse. nunca debe ser No puede ser vago.
Volvamos al constructor de TThread. Lo más importante a continuación es esta oración:
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
Aquí se utiliza la función BeginThread de Delphi RTL mencionada anteriormente. Tiene muchos parámetros, los clave son el tercer y cuarto parámetro. El tercer parámetro es la función del hilo mencionada anteriormente, es decir, la parte del código ejecutada en el hilo. El cuarto parámetro es el parámetro pasado a la función del subproceso, aquí está el objeto del subproceso creado (es decir, Self). Entre los otros parámetros, el quinto se usa para configurar el hilo para que se suspenda después de la creación y no se ejecute inmediatamente (el trabajo de iniciar el hilo se determina de acuerdo con el indicador CreateSuspended en AfterConstruction), y el sexto es para devolver la ID del hilo.
Ahora veamos el núcleo de TThread: la función de subproceso ThreadProc. Lo interesante es que el núcleo de esta clase de hilo no es un miembro del hilo, sino una función global (porque la convención de parámetros del proceso BeginThread solo puede usar funciones globales). Aquí está su código:
función ThreadProc(Thread: TThread): Entero;
var
FreeThread: booleano;
comenzar
intentar
si no es Thread.Terminate entonces
intentar
Hilo.Ejecutar;
excepto
Thread.FFatalException: = AcquireExceptionObject;
fin;
finalmente
FreeThread := Thread.FFreeOnTerminate;
Resultado := Thread.FReturnValue;
Hilo.DoTerminate;
Thread.Finished := Verdadero;
Evento de sincronización de señal;
si FreeThread entonces Thread.Free;
EndThread(Resultado);
fin;
fin;
Aunque no hay mucho código, es la parte más importante de todo TThread, porque este código es el código que realmente se ejecuta en el hilo. La siguiente es una descripción línea por línea del código:
Primero, determine el indicador Terminado de la clase de subproceso. Si no está marcado como terminado, llame al método Execute de la clase de subproceso para ejecutar el código del subproceso. Debido a que TThread es una clase abstracta y el método Execute es un método abstracto, esencialmente. ejecuta el código de ejecución en la clase derivada.
Por lo tanto, Ejecutar es la función de subproceso en la clase de subproceso. Todo el código en Ejecutar debe considerarse como código de subproceso, para evitar conflictos de acceso.
Si ocurre una excepción en Ejecutar, el objeto de excepción se obtiene a través de AcquireExceptionObject y se almacena en el miembro FFatalException de la clase de subproceso.
Finalmente, hay algunos toques finales antes de que termine el hilo. La variable local FreeThread registra la configuración del atributo FreeOnTerminate de la clase de subproceso y luego establece el valor de retorno del subproceso al valor del atributo de valor de retorno de la clase de subproceso. Luego ejecute el método DoTerminate de la clase de subproceso.
El código para el método DoTerminate es el siguiente:
procedimiento TThread.DoTerminate;
comenzar
si está asignado (FOnTerminate), entonces sincronizar (CallOnTerminate);
fin;
Es muy simple, simplemente llame al método CallOnTerminate a través de Synchronize, y el código del método CallOnTerminate es el siguiente, que consiste en simplemente llamar al evento OnTerminate:
procedimiento TThread.CallOnTerminate;
comenzar
si está asignado (FOnTerminate), entonces FOnTerminate (Self);
fin;
Debido a que el evento OnTerminate se ejecuta en Synchronize, esencialmente no es código de subproceso, sino código de subproceso principal (consulte el análisis de Synchronize más adelante para obtener más detalles).
Después de ejecutar OnTerminate, establezca el indicador FFinished de la clase de hilo en True.
A continuación se ejecuta el proceso SignalSyncEvent, cuyo código es el siguiente:
procedimiento SignalSyncEvent;
comenzar
EstablecerEvento(SincronizarEvento);
fin;
También es muy simple: simplemente configure un evento global: SyncEvent. Este artículo describirá en detalle el uso de Event más adelante y el propósito de SyncEvent se explicará en el proceso WaitFor.
Luego se decide si se libera la clase de subproceso en función de la configuración de FreeOnTerminate guardada en FreeThread. Cuando se libera la clase de subproceso, hay algunas operaciones. Consulte la siguiente implementación del destructor para obtener más detalles.
Finalmente, se llama a EndThread para finalizar el hilo y se devuelve el valor de retorno del hilo.
En este punto, el hilo ha terminado por completo.
(continuará)