Процессы, потоки и нити в ОС Windows, страница 3

Рассмотрим решение, предлагаемое операционной системой. Предположим, что вы создаете таблицу с двумя колонками. Таким образом, каждая запись таблицы имеет два поля: в первом поле содержится идентификатор ID-потока, а во втором — 32-битное число. Каждый раз при обращении потока к функции DoWork функция определяет ID текущего потока (при помощи вызова GetCurrentThreadID) и ищет его в таблице. Если запись с таким идентификатором есть, функция использует эту запись. Если такой записи нет, в таблицу вносится новая запись, содержащая ID текущего потока. В любом случае в вашем распоряжении оказывается табличная запись, принадлежащая текущему потоку. Во втором поле этой записи можно хранить счетчик обращения к функции DoWork для потока с заданным ID. Если поток завершает работу, запись с соответствующим ID удаляется из таблицы, и на ее месте можно расположить запись, принадлежащую другому потоку.

Именно по такому принципу работает локальная память потоков (Thread Local Storage, TLS). Для каждого из процессов создается набор внутренних таблиц. Windows может создать до 64 таких таблиц для каждого процесса. Таким образом, вы можете использовать 64 различные переменные TLS. Когда вы обращаетесь к TlsAlloc, Windows выделяет вам одну таблицу TLS. После этого вы можете использовать вызовы TlsSetValue и TlsGetValue для того, чтобы установить или прочитать значение из таблицы. При этом операционная система обращается именно к той записи в таблице, которая соответствует текущему потоку. Если таблица вам больше не нужна, вы можете обратиться к функции TlsFree для того, чтобы освободить ее. В каждой записи таблицы можно хранить любое 32-битное число, однако чаще всего таблица TLS используется для хранения указателей на класс или структуру, содержащую все необходимые для потока переменные.

Конечно, TLS — не единственный метод решения проблемы. В частности, вы можете разработать собственное решение, которое будет работать подобно TLS. Другой вариант предусматривает передачу функции в качестве одного из аргументов указателя на область памяти, принадлежащую потоку.

Листинг 5 демонстрирует применение обоих методов. Основная программа создает два потока (t_l и t_2). Каждый из потоков случайным образом выбирает два числа (п1 и п2) и обращается к двум разным функциям (fl и f2) количество раз, соответствующее этим числам. Функция fl хранит количество обращений в таблице TLS, которую создает функция main. Чтобы извлечь количество обращений к функции fl из таблицы TLS, в программе используется функция getflcount.

Функция f2 также следит за количеством обращений, однако эта функция хранит счетчик в локальной переменной, которая передается ей как аргумент. Этот метод приемлем в ситуации, когда основная программа имеет возможность создать локальную переменную.

Листинг 5. Использование TLS

#include <windows.h>

#include <iostream.h>

#include <process.h>

#include <stdlib.h>

#include <time.h>

volatile int t1done=0;

volatile int t2done=0;

DWORD index;

void display(char *display, int f1, int f2)

{

cout<<display<<" f1="<<f1<<" f2="<<f2<<"\n";

}

int getf1count()

{

return (int)TlsGetValue(index);

}

void f1()

{

int ct=(int)TlsGetValue(index);

TlsSetValue(index,(void *)++ct);

// здесь можете вставить полезный код

}

void f2(int &ctr)

{

ctr++;

// здесь можете вставить полезный код

}

// main thread

void t_1(void *)

{

int callf1=0;

int callf2=0;

int n1,n2;

srand( (unsigned)time( NULL ) +100);

n1=rand()%9+1;  // количество вызовов f1

n2=rand()%9+1;  // количество вызовов f2

display("T1: Вызывов",n1,n2);

while (n1--) f1();

callf1=getf1count();

while (n2--) f2(callf2);

display("T1: Результат",callf1,callf2);

t1done=1;

}

void t_2(void *)

{

int callf1=0;

int callf2=0;

int n1,n2;

srand( (unsigned)time( NULL )+245 );

n1=rand()%9+1;  // количество вызовов f1

n2=rand()%9+1;  // количество вызовов f2

display("T2: Вызывов",n1,n2);

while (n1--) f1();

callf1=getf1count();

while (n2--) f2(callf2);