1
0
mirror of https://github.com/loginov-dmitry/multithread.git synced 2025-02-20 07:58:22 +02:00

Добавлен пример SimpleLogger, исправление в разделе "Приоритеты потоков"

This commit is contained in:
loginov-dmitry 2020-11-23 08:47:48 +03:00
parent c9634cb1db
commit 37e3e74743
8 changed files with 391 additions and 4 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.dcu
*.local
*.identcache

161
CommonUtils/MTLogger.pas Normal file
View File

@ -0,0 +1,161 @@
unit MTLogger;
interface
uses
Windows, SysUtils, Classes, SyncObjs, MTUtils;
type
TLoggerThread = class(TThread)
private
FLogFileName: string;
Event: TEvent;
CritSect: TCriticalSection; // Äëÿ çàùèòû ñïèñêà ñòðîê
LogStrings: TStringList;
protected
procedure Execute; override;
public
constructor Create(LogFileName: string);
destructor Destroy; override;
procedure AddToLog(const Msg: string);
end;
var
DefLogger: TLoggerThread;
AllowMessageBoxIfError: Boolean = False;
procedure CreateDefLogger(DefLogFileName: string);
procedure FreeDefLogger;
// Çàïèñûâàåò çàäàííóþ ñòðîêó òåêñòà â óêàçàííûé ôàéë
procedure WriteStringToTextFile(AFileName: string; Msg: string);
implementation
procedure CreateDefLogger(DefLogFileName: string);
begin
if DefLogger = nil then
DefLogger := TLoggerThread.Create(DefLogFileName);
end;
procedure FreeDefLogger;
begin
FreeAndNil(DefLogger);
end;
procedure WriteStringToTextFile(AFileName: string; Msg: string);
var
AFile: TextFile;
begin
try
// Îòêðûâàåì ôàéë ïðè êàæäîì äîáàâëåíèè ñòðîêè! Ïðè áîëüøîì îáúåìå çàïèñè
// â ëîã ôàéë ýòî î÷åíü íåðàöèîíàëüíî!
AssignFile(AFile, AFileName);
if FileExists(AFileName) then
Append(AFile)
else
Rewrite(AFile);
Writeln(AFile, Msg);
CloseFile(AFile);
except
on E: Exception do
begin
// Âíèìàíèå!  ðåàëüíîì ïðèëîæåíèè âûäà÷à ïîëüçîâàòåëþ ñîîáùåíèÿ îá îøèáêå
// çàïèñè â ëîã-ôàéë íåäîïóñòèìà!
if AllowMessageBoxIfError then
ThreadShowMessageFmt('Îøèáêà ïðè çàïèñè â ôàéë [%s] ñòðîêè "%s": %s', [AFileName, Msg, E.Message]);
end;
end;
end;
{ TLoggerThread }
procedure TLoggerThread.AddToLog(const Msg: string);
begin
CritSect.Enter;
try
LogStrings.Add(Format('%s [P:%d T:%d] - %s', [FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', Now),
GetCurrentProcessId, GetCurrentThreadId, Msg]));
finally
CritSect.Leave;
end;
Event.SetEvent;
end;
constructor TLoggerThread.Create(LogFileName: string);
const
STATE_NONSIGNALED = FALSE;
AUTO_RESET = FALSE;
begin
inherited Create(False);
// Ñîçäà¸ì îáúåêò "Event" â ñîñòîÿíèè "nonsignaled" è ïðîñèì, ÷òîáû
// îí àâòîìàòè÷åñêè ïåðåõîäèë â ñîñòîÿíèå "nonsignaled" ïîñëå WaitFor
Event := TEvent.Create(nil, AUTO_RESET, STATE_NONSIGNALED, '', False);
// Ñîçäà¸ì ñïèñîê ñòðîê LogStrings
LogStrings := TStringList.Create;
// Ñîçäà¸ì êðèòè÷åñêóþ ñåêöèþ äëÿ çàùèòû ñïèñêà ñòðîê îò îäíîâðåìåííîãî
// äîñòóïà èç íåñêîëüêèõ ïîòîêîâ
CritSect := TCriticalSection.Create;
FLogFileName := LogFileName;
end;
destructor TLoggerThread.Destroy;
begin
// Î÷åíü âàæíî, ÷òîáû âûçîâ Terminate áûë ðàíüøå âûçîâà SetEvent!
Terminate; // 1. Âûñòàâëÿåì ôëàã Terminated
Event.SetEvent; // 2. Ïåðåâîäèì Event â ñîñòîÿíèå SIGNALED
inherited; // 3. Äîæèäàåìñÿ âûõîäà èç ìåòîäà Execute
Event.Free; // 4. Óíè÷òîæàåì îáúåêò Event
CritSect.Free;
LogStrings.Free;
end;
procedure TLoggerThread.Execute;
var
TmpList: TStringList;
begin
while True do
begin
// Çàâåðøàåì ðàáîòó ïîòîêà â òîì ñëó÷àå, åñëè ïîëüçîâàòåëü âûõîäèò èç ïðîãðàììû,
// à ïîòîê óñïåë ñêèíóòü â ëîã-ôàéë âñå ñîîáùåíèÿ èç ñïèñêà LogStrings
if Terminated and (LogStrings.Count = 0) then Exit;
// Îæèäàåì ïåðåêëþ÷åíèÿ îáúåêòà Event â ñîñòîÿíèå signaled, íî íå áîëåå 2õ ñåêóíä
// Ïðè âûçîâå ìåòîäà TLoggerThread.AddToLog âûïîëíÿåòñÿ âûçîâ Event.SetEvent,
// îäíàêî ëó÷øå ïåðåñòðàõîâàòüñÿ è íå äåëàòü îæèäàíèå áåñêîíå÷íûì
Event.WaitFor(2000);
// Ïðîâåðÿåì ñâîéñòâî Count áåç êðèòè÷åñêîé ñåêöèè (ýòî áåçîïàñíî)
if LogStrings.Count > 0 then
begin
TmpList := TStringList.Create;
try
// 1. Âõîäèì â êðèòè÷åñêóþ ñåêöèþ
CritSect.Enter;
try
// 2. Êîïèðóåì âñå ñòðîêè èç LogStrings â TmpList
TmpList.Assign(LogStrings);
// 3. Î÷èùàåì ñïèñîê LogStrings
LogStrings.Clear;
finally
// 4. Âûõîäèì èç êðèòè÷åñêîé ñåêöèè
CritSect.Leave;
end;
// Ñïèñîê TmpList ÿâëÿåòñÿ ëîêàëüíûì, ïîýòîìó åãî íå íóæíî çàùèùàòü
// êðèòè÷åñêîé ñåêöèåé.
// 5. Çà îäíî äåéñòâèå çàïèñûâàåì âñå ñòðîêè èç ñïèñêà TmpList â ëîã-ôàéë
WriteStringToTextFile(FLogFileName, Trim(TmpList.Text));
finally
TmpList.Free;
end;
end;
end;
end;
end.

View File

@ -0,0 +1,17 @@
program SimpleLogger;
uses
Forms,
SimpleLoggerUnit in 'SimpleLoggerUnit.pas' {DemoLoggerForm},
MTUtils in '..\..\..\CommonUtils\MTUtils.pas',
TimeIntervals in '..\..\..\CommonUtils\TimeIntervals.pas',
MTLogger in '..\..\..\CommonUtils\MTLogger.pas';
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDemoLoggerForm, DemoLoggerForm);
Application.Run;
end.

View File

@ -0,0 +1,39 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{7ad5f485-3983-41b2-9088-147054b7fa84}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<DCC_DCCCompiler>DCC32</DCC_DCCCompiler>
<DCC_DependencyCheckOutputName>SimpleLogger.exe</DCC_DependencyCheckOutputName>
<MainSource>SimpleLogger.dpr</MainSource>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Version>7.0</Version>
<DCC_DebugInformation>False</DCC_DebugInformation>
<DCC_LocalDebugSymbols>False</DCC_LocalDebugSymbols>
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_Define>RELEASE</DCC_Define>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<Version>7.0</Version>
<DCC_Define>DEBUG</DCC_Define>
</PropertyGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality</Borland.Personality>
<Borland.ProjectType />
<BorlandProject>
<BorlandProject><Delphi.Personality><Parameters><Parameters Name="UseLauncher">False</Parameters><Parameters Name="LoadAllSymbols">True</Parameters><Parameters Name="LoadUnspecifiedSymbols">False</Parameters></Parameters><VersionInfo><VersionInfo Name="IncludeVerInfo">False</VersionInfo><VersionInfo Name="AutoIncBuild">False</VersionInfo><VersionInfo Name="MajorVer">1</VersionInfo><VersionInfo Name="MinorVer">0</VersionInfo><VersionInfo Name="Release">0</VersionInfo><VersionInfo Name="Build">0</VersionInfo><VersionInfo Name="Debug">False</VersionInfo><VersionInfo Name="PreRelease">False</VersionInfo><VersionInfo Name="Special">False</VersionInfo><VersionInfo Name="Private">False</VersionInfo><VersionInfo Name="DLL">False</VersionInfo><VersionInfo Name="Locale">1049</VersionInfo><VersionInfo Name="CodePage">1251</VersionInfo></VersionInfo><VersionInfoKeys><VersionInfoKeys Name="CompanyName"></VersionInfoKeys><VersionInfoKeys Name="FileDescription"></VersionInfoKeys><VersionInfoKeys Name="FileVersion">1.0.0.0</VersionInfoKeys><VersionInfoKeys Name="InternalName"></VersionInfoKeys><VersionInfoKeys Name="LegalCopyright"></VersionInfoKeys><VersionInfoKeys Name="LegalTrademarks"></VersionInfoKeys><VersionInfoKeys Name="OriginalFilename"></VersionInfoKeys><VersionInfoKeys Name="ProductName"></VersionInfoKeys><VersionInfoKeys Name="ProductVersion">1.0.0.0</VersionInfoKeys><VersionInfoKeys Name="Comments"></VersionInfoKeys></VersionInfoKeys><Source><Source Name="MainSource">SimpleLogger.dpr</Source></Source></Delphi.Personality></BorlandProject></BorlandProject>
</ProjectExtensions>
<Import Project="$(MSBuildBinPath)\Borland.Delphi.Targets" />
<ItemGroup>
<DelphiCompile Include="SimpleLogger.dpr">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="..\..\..\CommonUtils\MTLogger.pas" />
<DCCReference Include="..\..\..\CommonUtils\MTUtils.pas" />
<DCCReference Include="..\..\..\CommonUtils\TimeIntervals.pas" />
<DCCReference Include="SimpleLoggerUnit.pas">
<Form>DemoLoggerForm</Form>
</DCCReference>
</ItemGroup>
</Project>

Binary file not shown.

View File

@ -0,0 +1,90 @@
object DemoLoggerForm: TDemoLoggerForm
Left = 0
Top = 0
Caption = #1044#1077#1084#1086#1085#1089#1090#1088#1072#1094#1080#1103' '#1079#1072#1087#1080#1089#1080' '#1074' '#1083#1086#1075' '#1095#1077#1088#1077#1079' '#1076#1086#1087#1086#1083#1085#1080#1090#1077#1083#1100#1085#1099#1081' '#1087#1086#1090#1086#1082
ClientHeight = 288
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 16
object Label1: TLabel
Left = 16
Top = 24
Width = 132
Height = 16
Caption = #1044#1086#1073#1072#1074#1080#1090#1100' '#1089#1086#1086#1073#1097#1077#1085#1080#1081':'
end
object Label2: TLabel
Left = 24
Top = 96
Width = 457
Height = 32
Caption =
#1054#1089#1090#1086#1088#1086#1078#1085#1086'! '#1045#1089#1083#1080' '#1042#1099' '#1080#1089#1087#1086#1083#1100#1079#1091#1077#1090#1077' SSD, '#1090#1086' '#1087#1086#1089#1090#1088#1086#1095#1085#1072#1103' '#1079#1072#1087#1080#1089#1100' 10 '#1090#1099#1089'.' +
' '#1089#1090#1088#1086#1082' '#1074#13#10#1083#1086#1075'-'#1092#1072#1081#1083' '#1084#1086#1078#1077#1090' '#1079#1072#1085#1103#1090#1100' '#1086#1082#1086#1083#1086' '#1084#1080#1085#1091#1090#1099', '#1074' '#1079#1072#1074#1080#1089#1080#1084#1086#1089#1090#1080' '#1086#1090' '#1084 +
#1086#1076#1077#1083#1080' SSD.'
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlue
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
end
object Label3: TLabel
Left = 24
Top = 226
Width = 486
Height = 32
Caption =
#1055#1088#1080' '#1079#1072#1087#1080#1089#1080' '#1074' '#1083#1086#1075' '#1095#1077#1088#1077#1079' '#1076#1086#1087'. '#1087#1086#1090#1086#1082' '#1073#1091#1076#1077#1090' '#1084#1086#1084#1077#1085#1090#1072#1083#1100#1085#1086' '#1079#1072#1087#1080#1089#1099#1074#1072#1090#1100#1089#1103 +
' '#1087#1088#1072#1082#1090#1080#1095#1077#1089#1082#1080#13#10#1083#1102#1073#1086#1081' '#1086#1073#1098#1105#1084' '#1080#1085#1092#1086#1088#1084#1072#1094#1080#1080', '#1076#1072#1078#1077' 1 '#1084#1083#1085' '#1089#1090#1088#1086#1082'!'
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlue
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
end
object Button1: TButton
Left = 16
Top = 67
Width = 425
Height = 25
Caption = #1044#1086#1073#1072#1074#1080#1090#1100' '#1089#1086#1086#1073#1097#1077#1085#1080#1103' '#1074' '#1083#1086#1075'-'#1092#1072#1081#1083' '#1073#1077#1079' '#1076#1086#1087#1086#1083#1085#1080#1090#1077#1083#1100#1085#1086#1075#1086' '#1087#1086#1090#1086#1082#1072
TabOrder = 0
OnClick = Button1Click
end
object Edit1: TEdit
Left = 154
Top = 21
Width = 87
Height = 24
TabOrder = 1
Text = '10000'
end
object Button2: TButton
Left = 16
Top = 195
Width = 425
Height = 25
Caption = #1044#1086#1073#1072#1074#1080#1090#1100' '#1089#1086#1086#1073#1097#1077#1085#1080#1103' '#1074' '#1083#1086#1075'-'#1092#1072#1081#1083' '#1095#1077#1088#1077#1079' '#1076#1086#1087#1086#1083#1085#1080#1090#1077#1083#1100#1085#1099#1081' '#1087#1086#1090#1086#1082
TabOrder = 2
OnClick = Button2Click
end
object cbCloseApp: TCheckBox
Left = 456
Top = 200
Width = 145
Height = 17
Caption = #1047#1072#1082#1088#1099#1090#1100' '#1087#1088#1086#1075#1088#1072#1084#1084#1091
TabOrder = 3
end
end

View File

@ -0,0 +1,75 @@
unit SimpleLoggerUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, SyncObjs, MTUtils, TimeIntervals, MTLogger;
type
TDemoLoggerForm = class(TForm)
Button1: TButton;
Label1: TLabel;
Edit1: TEdit;
Button2: TButton;
Label2: TLabel;
Label3: TLabel;
cbCloseApp: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
DemoLoggerForm: TDemoLoggerForm;
implementation
{$R *.dfm}
procedure TDemoLoggerForm.Button1Click(Sender: TObject);
var
ti: TTimeInterval;
I: Integer;
AFileName: string;
begin
AFileName := ExtractFilePath(Application.ExeName) + 'EventsNoThread.log';
ti.Start;
for I := 1 to StrToInt(Edit1.Text) do
WriteStringToTextFile(AFileName, Format('%s [P:%d T:%d] - Ñîáûòèå ¹%d',
[FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', Now), GetCurrentProcessId, GetCurrentThreadId, I]));
ShowMessageFmt('Âðåìÿ äîáàâëåíèÿ ñîáûòèé â ëîã-ôàéë: %d ìñ', [ti.ElapsedMilliseconds]);
end;
procedure TDemoLoggerForm.Button2Click(Sender: TObject);
var
ti: TTimeInterval;
I: Integer;
begin
ti.Start;
for I := 1 to StrToInt(Edit1.Text) do
DefLogger.AddToLog(Format('Ñîáûòèå ¹%d', [I]));
if cbCloseApp.Checked then
Close
else
ShowMessageFmt('Âðåìÿ äîáàâëåíèÿ ñîáûòèé â ëîã-ôàéë: %d ìñ', [ti.ElapsedMilliseconds]);
end;
procedure TDemoLoggerForm.FormCreate(Sender: TObject);
begin
AllowMessageBoxIfError := True; // Òîëüêî â äåìîíñòðàöèîííûõ öåëÿõ!!!
CreateDefLogger(ExtractFilePath(Application.ExeName) + 'EventsInThread.log');
end;
procedure TDemoLoggerForm.FormDestroy(Sender: TObject);
begin
FreeDefLogger;
end;
end.

View File

@ -11,6 +11,7 @@ markdown-редакторы не подошли, т.к. они либо не у
Возможные темы для дальнейшей работы над статьёй:
- необходимо проверить точность GetTickCount на современной компьютере с Windows 10 20H2
- обработка исключений в потоках
- логгирование событий, происходящих в потоках
- передача данных в доп. поток: использование очереди Windows
@ -129,7 +130,7 @@ markdown-редакторы не подошли, т.к. они либо не у
В некоторых случаях Вы можете решить не использовать дополнительные потоки и тем самым упростить свою программу. К сожалению, в таких случаях при запуске длительной операции из главного потока будет происходить подвисание программы, что может вызвать разочарование со стороны пользователей, а также сделает невозможным выполнение нескольких операций одновременно.
1. Если ваша программа при нажатии кнопки на форме выполняет обращение к базе данных (либо http-запрос, либо другую операцию, которая может длиться несколько секунд), то Вы можете перед выполнением операции выполнить код `Screen.Cursor := crSQLWait`, а после выполнения операции выполнить код `Screen.Cursor := crDefault`. При нажатии кнопки пользователь увидит, что курсор изменил свою форму (песочные часы с надписью SQL), и скорее всего не будет выполнить никаких действий, пока курсор не примет свой обычный вид. Программисты прибегают к этому приёму очень часто. Пример данного подхода находится в папке "ExNotUseThreads".
1. Если ваша программа при нажатии кнопки на форме выполняет обращение к базе данных (либо http-запрос, либо другую операцию, которая может длиться несколько секунд), то Вы можете перед выполнением операции выполнить код `Screen.Cursor := crSQLWait`, а после выполнения операции выполнить код `Screen.Cursor := crDefault`. При нажатии кнопки пользователь увидит, что курсор изменил свою форму (песочные часы с надписью SQL), и скорее всего не будет выполнять никаких действий, пока курсор не примет свой обычный вид. Программисты прибегают к этому приёму очень часто. Пример данного подхода находится в папке "ExNotUseThreads".
Но вы должны помнить, что если операция выполняется более 5 секунд и пользователь щелкнет мышкой на форме, то в заголовке формы появится надпись "не отвечает", в следствие чего могут быть глюки при работе программы, связанные с тем, что активная форма может улететь на задний план, либо может активироваться форма, которая находится позади модальной формы (примечание: в Windows 10 20H2 надпись "не отвечает" почему-то не появляется, возможно Microsoft отказалась от такого подхода и избавила пользователей от проблем с формами).
@ -2048,7 +2049,7 @@ end;
:information_source: **Частота срабатывания системного таймера сильно влияет на работу функции `Sleep`!** Если таймер срабатывает очень часто (1000 раз в секунду), то функция Sleep работает с максимальной точностью (плюс/минус 0.5 мс). Если таймер срабатывает раз в 16 мс, то и точность работы функции Sleep составит примерно 16 мс.
:information_source: Разрешение системного таймера **никак не влияет** на функции `GetTickCount` и `GetTickCount64`. Их точность соответствует максимальному интервалу системного таймера, т.е. примерно 16 мс. В связи с этим я не советую использовать эти функции для измерения интервалов времени (хотя вроде для этого они и были предназначены). Если Вас устраивает точность замеров 1 мс, то лучше используйте обычную функцию `Now`. Если требуется производить замеры в большей точностью, то используйте функцию `QueryPerformanceCounter`, либо более удобный модуль "TimeIntervals.pas", который ранее уже был рассмотрен.
:information_source: Разрешение системного таймера **никак не влияет** на функции `GetTickCount` и `GetTickCount64`. Их точность соответствует максимальному интервалу системного таймера, т.е. примерно 16 мс. В связи с этим я не советую использовать эти функции для измерения интервалов времени (хотя вроде для этого они и были предназначены). Если Вас устраивает точность замеров 1 мс, то лучше используйте обычную функцию `Now` (она возвращаёт текущее время с точностью до миллисекунды в том случае, если разрешение системного таймера выставлено в 1 мс). Если требуется производить замеры в большей точностью, то используйте функцию `QueryPerformanceCounter`, либо более удобный модуль "TimeIntervals.pas", который ранее уже был рассмотрен.
Отметим также следующие особенности планирования потоков в Windows:
1. Для каждого ядра процессора планировщик создаёт свою очередь потоков, готовых к запуску. При обращениях к планировщику, не связанных с работой системного таймера (например, при вызове функций ядра `Sleep`, `SwitchToThread`, `SetEvent`), анализ всех потоков не выполняется. Учитывается только информация, заранее подготовленная планировщиком, поэтому такой код диспетчера ядра выполняется максимально быстро.
@ -2104,9 +2105,9 @@ end;
Уровень приоритета потока влияет на выделение процессорного времени как между потоками в рамках одного процесса, так и между потоками различных процессов.
Изменять уровень приоритета потока не имеет смысла, если выполняется задача, в которой основное время уходит на ожидание какого-либо события.
Изменять уровень приоритета потока не имеет смысла, если выполняется задача, в которой основное время уходит на ожидание какого-либо события. Но если Вы производите обработку какой-либо информации и эта обработка значительно нагружает процессор, то Вы можете выставить уровень приоритета в `tpLower`. В этом случае обработка информации будет выполняться только в том случае, если у процессора имеется ядро, которое ничем не занято. Но как только это ядро потребуется потоку с более высоким приоритетом, работа Вашего низкоприоритетного потока будет приостановлена. Периодически Windows отдаёт кванты времени низкоприоритетным потокам, несмотря на то, что выполняется поток с более высоким приоритетом. Для этого Windows на короткое время (в рамках одного кванта) повышает приоритет низкоприоритетного потока.
Если в Вашей программе работают 2 потока, у одного уровень приоритета `tpNormal`, а у второго `tpLower` и оба потока привязаны к одному и тому же ядру процессора, то первому потоку гораздо чаще будет предоставляться процессорное время (например, 98%). С другой стороны, если у этих же потоков не будет привязки к одному и тому же ядру, то они получат одинаковое процессорное время. Скорее всего, оба варианта – это не то, чего Вы хотите добиться при изменении уровня приоритета потока.
Если в Вашей программе работают 2 потока (оба выполняют математические вычисления), у одного уровень приоритета `tpNormal`, а у второго `tpLower` и оба потока привязаны к одному и тому же ядру процессора, то первому потоку гораздо чаще будет предоставляться процессорное время (например, 98%). С другой стороны, если у этих же потоков не будет привязки к одному и тому же ядру, то они получат одинаковое процессорное время на разных ядрах.
Существует 2 крайних уровня приоритетов: `tpIdle` и `tpTimeCritical`. Поток с приоритетом `tpIdle` получит процессорное время только в том случае, если нет других активных потоков с более высоким приоритетом. Поток с приоритетом `tpTimeCritical` будет забирать себе всё процессорное время, т.е. потоки с более низким приоритетом не получат квант времени, если выполняется поток с приоритетом `tpTimeCritical`. Это верно для одноядерного процессора. Если процессор многоядерный (сейчас это обычная ситуация), то, скорее всего, будут выполняться одновременно и поток с приоритетом `tpIdle` и поток с приоритетом `tpTimeCritical`.