mirror of
https://github.com/loginov-dmitry/multithread.git
synced 2024-11-24 08:52:15 +02:00
добавлен материал о мьютексах и семафорах
This commit is contained in:
parent
37e3e74743
commit
6c1af9ee15
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,3 +2,7 @@
|
||||
*.dcu
|
||||
*.local
|
||||
*.identcache
|
||||
__history
|
||||
*.exe
|
||||
*.log
|
||||
*.zip
|
||||
|
@ -4,7 +4,7 @@ object Form1: TForm1
|
||||
Caption =
|
||||
#1055#1088#1086#1075#1088#1072#1084#1084#1072' '#1076#1083#1103' '#1086#1087#1088#1077#1076#1077#1083#1077#1085#1080#1103' '#1076#1083#1080#1090#1077#1083#1100#1085#1086#1089#1090#1080' '#1074#1099#1076#1077#1083#1103#1077#1084#1099#1093' '#1082#1074#1072#1085#1090#1086#1074' '#1074#1088#1077#1084#1077#1085 +
|
||||
#1080
|
||||
ClientHeight = 492
|
||||
ClientHeight = 490
|
||||
ClientWidth = 730
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
@ -17,7 +17,7 @@ object Form1: TForm1
|
||||
OnDestroy = FormDestroy
|
||||
DesignSize = (
|
||||
730
|
||||
492)
|
||||
490)
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 19
|
||||
object Label1: TLabel
|
||||
@ -52,7 +52,7 @@ object Form1: TForm1
|
||||
Caption = #1056#1077#1079#1091#1083#1100#1090#1072#1090#1099':'
|
||||
end
|
||||
object Label4: TLabel
|
||||
Left = 9
|
||||
Left = 3
|
||||
Top = 89
|
||||
Width = 169
|
||||
Height = 19
|
||||
@ -70,6 +70,7 @@ object Form1: TForm1
|
||||
Top = 464
|
||||
Width = 348
|
||||
Height = 19
|
||||
Anchors = [akLeft]
|
||||
Caption = #1048#1079#1084#1077#1085#1080#1090#1100' '#1088#1072#1079#1088#1077#1096#1077#1085#1080#1077' '#1089#1080#1089#1090#1077#1084#1085#1086#1075#1086' '#1090#1072#1081#1084#1077#1088#1072', '#1084#1089':'
|
||||
end
|
||||
object edThreadCount: TEdit
|
||||
@ -93,16 +94,16 @@ object Form1: TForm1
|
||||
Left = 8
|
||||
Top = 167
|
||||
Width = 714
|
||||
Height = 290
|
||||
Height = 288
|
||||
Anchors = [akLeft, akTop, akRight, akBottom]
|
||||
ScrollBars = ssVertical
|
||||
TabOrder = 2
|
||||
end
|
||||
object clbCPUList: TCheckListBox
|
||||
Left = 192
|
||||
Left = 179
|
||||
Top = 89
|
||||
Width = 81
|
||||
Height = 55
|
||||
Width = 104
|
||||
Height = 71
|
||||
ItemHeight = 19
|
||||
TabOrder = 3
|
||||
end
|
||||
@ -135,17 +136,19 @@ object Form1: TForm1
|
||||
end
|
||||
object edSysTimerInterval: TEdit
|
||||
Left = 367
|
||||
Top = 463
|
||||
Top = 461
|
||||
Width = 42
|
||||
Height = 27
|
||||
Anchors = [akLeft, akBottom]
|
||||
TabOrder = 6
|
||||
Text = '16'
|
||||
end
|
||||
object btnChangeSysTimerInterval: TButton
|
||||
Left = 424
|
||||
Top = 464
|
||||
Top = 462
|
||||
Width = 105
|
||||
Height = 25
|
||||
Anchors = [akLeft, akBottom]
|
||||
Caption = #1048#1079#1084#1077#1085#1080#1090#1100
|
||||
TabOrder = 7
|
||||
OnClick = btnChangeSysTimerIntervalClick
|
||||
|
@ -67,9 +67,10 @@ procedure TForm1.btnChangeSysTimerIntervalClick(Sender: TObject);
|
||||
var
|
||||
NewInterval, Res: Cardinal;
|
||||
begin
|
||||
// У меня не получилось изменить разрешение таймера. Видимо, другая программа
|
||||
// уже вызвала timeBeginPeriod. Windows в этом случае игнорирует
|
||||
// вызовы timeBeginPeriod от других программ, если мы указываем больший интервал.
|
||||
{Внимание! Перед изменением разрешения системного таймера проверьте его текущее
|
||||
разрешение с помощью утилиты Clockres.exe, её можно скачать с сайта Microsoft.
|
||||
Также желательно закрыть Delphi, т.к. она может принудительно выставлять
|
||||
разрешение в 1 мс. Браузеры также могут менять разрешение!}
|
||||
{
|
||||
This function affects a global Windows setting. Windows uses the lowest value
|
||||
(that is, highest resolution) requested by any process. Setting a higher
|
||||
|
14
ExSync/MonitorVsCritSect/MonitorVsCritSect.dpr
Normal file
14
ExSync/MonitorVsCritSect/MonitorVsCritSect.dpr
Normal file
@ -0,0 +1,14 @@
|
||||
program MonitorVsCritSect;
|
||||
|
||||
uses
|
||||
Vcl.Forms,
|
||||
MonitorVsCritSectUnit1 in 'MonitorVsCritSectUnit1.pas' {Form1};
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.CreateForm(TForm1, Form1);
|
||||
Application.Run;
|
||||
end.
|
904
ExSync/MonitorVsCritSect/MonitorVsCritSect.dproj
Normal file
904
ExSync/MonitorVsCritSect/MonitorVsCritSect.dproj
Normal file
@ -0,0 +1,904 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{ADC3468F-3ADC-4D4C-9676-EDDE8106DE7C}</ProjectGuid>
|
||||
<ProjectVersion>19.1</ProjectVersion>
|
||||
<FrameworkType>VCL</FrameworkType>
|
||||
<Base>True</Base>
|
||||
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||
<Platform Condition="'$(Platform)'==''">Win32</Platform>
|
||||
<TargetedPlatforms>1</TargetedPlatforms>
|
||||
<AppType>Application</AppType>
|
||||
<MainSource>MonitorVsCritSect.dpr</MainSource>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
|
||||
<Base_Win32>true</Base_Win32>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
|
||||
<Base_Win64>true</Base_Win64>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
|
||||
<Cfg_1_Win32>true</Cfg_1_Win32>
|
||||
<CfgParent>Cfg_1</CfgParent>
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
|
||||
<Cfg_2_Win32>true</Cfg_2_Win32>
|
||||
<CfgParent>Cfg_2</CfgParent>
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
|
||||
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
|
||||
<DCC_E>false</DCC_E>
|
||||
<DCC_N>false</DCC_N>
|
||||
<DCC_S>false</DCC_S>
|
||||
<DCC_F>false</DCC_F>
|
||||
<DCC_K>false</DCC_K>
|
||||
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
|
||||
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
|
||||
<UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44>
|
||||
<UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150>
|
||||
<SanitizedProjectName>MonitorVsCritSect</SanitizedProjectName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base_Win32)'!=''">
|
||||
<DCC_UsePackage>DBXSqliteDriver;RESTComponents;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;bindcompvclsmp;emsclientfiredac;tethering;svnui;DataSnapFireDAC;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;FireDACIBDriver;fmxdae;vcledge;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;emsclient;DataSnapCommon;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;CloudService;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;IndyIPCommon;bindcompdbx;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;emsserverresource;DbxClientDriver;ibxbindings;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage)</DCC_UsePackage>
|
||||
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
|
||||
<BT_BuildType>Debug</BT_BuildType>
|
||||
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
|
||||
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
|
||||
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base_Win64)'!=''">
|
||||
<DCC_UsePackage>DBXSqliteDriver;RESTComponents;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;bindcompvclsmp;emsclientfiredac;tethering;DataSnapFireDAC;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;FireDACIBDriver;fmxdae;vcledge;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;emsclient;DataSnapCommon;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;CloudService;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;IndyIPCommon;bindcompdbx;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;emsserverresource;DbxClientDriver;ibxbindings;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage)</DCC_UsePackage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1)'!=''">
|
||||
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
|
||||
<DCC_DebugDCUs>true</DCC_DebugDCUs>
|
||||
<DCC_Optimize>false</DCC_Optimize>
|
||||
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
|
||||
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
|
||||
<DCC_RemoteDebug>true</DCC_RemoteDebug>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
|
||||
<DCC_RemoteDebug>false</DCC_RemoteDebug>
|
||||
<AppEnableRuntimeThemes>true</AppEnableRuntimeThemes>
|
||||
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
|
||||
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
|
||||
<DCC_DebugInformation>0</DCC_DebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
|
||||
<AppEnableRuntimeThemes>true</AppEnableRuntimeThemes>
|
||||
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DelphiCompile Include="$(MainSource)">
|
||||
<MainSource>MainSource</MainSource>
|
||||
</DelphiCompile>
|
||||
<DCCReference Include="MonitorVsCritSectUnit1.pas">
|
||||
<Form>Form1</Form>
|
||||
<FormType>dfm</FormType>
|
||||
</DCCReference>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_1</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
</ItemGroup>
|
||||
<ProjectExtensions>
|
||||
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
|
||||
<Borland.ProjectType>Application</Borland.ProjectType>
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Source>
|
||||
<Source Name="MainSource">MonitorVsCritSect.dpr</Source>
|
||||
</Source>
|
||||
</Delphi.Personality>
|
||||
<Deployment Version="3">
|
||||
<DeployFile LocalName="Win32\Debug\MonitorVsCritSect.exe" Configuration="Debug" Class="ProjectOutput">
|
||||
<Platform Name="Win32">
|
||||
<RemoteName>MonitorVsCritSect.exe</RemoteName>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployClass Name="AdditionalDebugSymbols">
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidClassesDexFile">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>classes</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>classes</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidFileProvider">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\xml</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\xml</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidGDBServer">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidLibnativeArmeabiFile">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidLibnativeMipsFile">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\mips</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\mips</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidServiceOutput">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidServiceOutput_Android32">
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidSplashImageDef">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidSplashStyles">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidSplashStylesV21">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values-v21</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values-v21</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_Colors">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_DefaultAppIcon">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon144">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon36">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-ldpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-ldpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon48">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon72">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon96">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon24">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon36">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon48">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon72">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon96">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage426">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-small</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-small</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage470">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-normal</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-normal</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage640">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-large</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-large</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage960">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xlarge</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xlarge</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_Strings">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="DebugSymbols">
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="DependencyFramework">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.framework</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.framework</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="DependencyModule">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
<Extensions>.dll;.bpl</Extensions>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Required="true" Name="DependencyPackage">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
<Extensions>.bpl</Extensions>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="File">
|
||||
<Platform Name="Android">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iOS_AppStore1024">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_AppIcon152">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_AppIcon167">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch2x">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_LaunchDark2x">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Notification40">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Setting58">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_SpotLight80">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_AppIcon120">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_AppIcon180">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch2x">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch3x">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_LaunchDark2x">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_LaunchDark3x">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Notification40">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Notification60">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Setting58">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Setting87">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Spotlight120">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Spotlight80">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectAndroidManifest">
|
||||
<Platform Name="Android">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSDeviceDebug">
|
||||
<Platform Name="iOSDevice32">
|
||||
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSEntitlements">
|
||||
<Platform Name="iOSDevice32">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSInfoPList">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSLaunchScreen">
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
|
||||
<Operation>64</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
|
||||
<Operation>64</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSResource">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXDebug">
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXEntitlements">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXInfoPList">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXResource">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\Resources</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\Resources</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Required="true" Name="ProjectOutput">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Linux64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOutput_Android32">
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectUWPManifest">
|
||||
<Platform Name="Win32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="UWP_DelphiLogo150">
|
||||
<Platform Name="Win32">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win64">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="UWP_DelphiLogo44">
|
||||
<Platform Name="Win32">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win64">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
|
||||
</Deployment>
|
||||
<Platforms>
|
||||
<Platform value="Win32">True</Platform>
|
||||
<Platform value="Win64">False</Platform>
|
||||
</Platforms>
|
||||
</BorlandProject>
|
||||
<ProjectFileVersion>12</ProjectFileVersion>
|
||||
</ProjectExtensions>
|
||||
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
|
||||
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
|
||||
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
|
||||
</Project>
|
BIN
ExSync/MonitorVsCritSect/MonitorVsCritSect.res
Normal file
BIN
ExSync/MonitorVsCritSect/MonitorVsCritSect.res
Normal file
Binary file not shown.
79
ExSync/MonitorVsCritSect/MonitorVsCritSectUnit1.dfm
Normal file
79
ExSync/MonitorVsCritSect/MonitorVsCritSectUnit1.dfm
Normal file
@ -0,0 +1,79 @@
|
||||
object Form1: TForm1
|
||||
Left = 0
|
||||
Top = 0
|
||||
Caption = 'Form1'
|
||||
ClientHeight = 299
|
||||
ClientWidth = 635
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -13
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 16
|
||||
object Label1: TLabel
|
||||
Left = 16
|
||||
Top = 11
|
||||
Width = 99
|
||||
Height = 16
|
||||
Caption = #1063#1080#1089#1083#1086' '#1080#1090#1077#1088#1072#1094#1080#1081':'
|
||||
end
|
||||
object labTCritSecTime: TLabel
|
||||
Left = 255
|
||||
Top = 95
|
||||
Width = 18
|
||||
Height = 16
|
||||
Caption = '???'
|
||||
end
|
||||
object labRTLCritSecTime: TLabel
|
||||
Left = 255
|
||||
Top = 147
|
||||
Width = 18
|
||||
Height = 16
|
||||
Caption = '???'
|
||||
end
|
||||
object labMonitorTime: TLabel
|
||||
Left = 255
|
||||
Top = 198
|
||||
Width = 18
|
||||
Height = 16
|
||||
Caption = '???'
|
||||
end
|
||||
object Button1: TButton
|
||||
Left = 8
|
||||
Top = 88
|
||||
Width = 241
|
||||
Height = 33
|
||||
Caption = #1047#1072#1084#1077#1088' '#1089' TCriticalSection'
|
||||
TabOrder = 0
|
||||
OnClick = Button1Click
|
||||
end
|
||||
object Edit1: TEdit
|
||||
Left = 121
|
||||
Top = 8
|
||||
Width = 121
|
||||
Height = 24
|
||||
TabOrder = 1
|
||||
Text = '100000000'
|
||||
end
|
||||
object Button2: TButton
|
||||
Left = 8
|
||||
Top = 136
|
||||
Width = 241
|
||||
Height = 41
|
||||
Caption = #1047#1072#1084#1077#1088' '#1089' TRTLCriticalSection'
|
||||
TabOrder = 2
|
||||
OnClick = Button2Click
|
||||
end
|
||||
object Button3: TButton
|
||||
Left = 8
|
||||
Top = 188
|
||||
Width = 241
|
||||
Height = 38
|
||||
Caption = #1047#1072#1084#1077#1088' '#1089' TMonitor'
|
||||
TabOrder = 3
|
||||
OnClick = Button3Click
|
||||
end
|
||||
end
|
92
ExSync/MonitorVsCritSect/MonitorVsCritSectUnit1.pas
Normal file
92
ExSync/MonitorVsCritSect/MonitorVsCritSectUnit1.pas
Normal file
@ -0,0 +1,92 @@
|
||||
unit MonitorVsCritSectUnit1;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
|
||||
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, SyncObjs;
|
||||
|
||||
type
|
||||
TForm1 = class(TForm)
|
||||
Button1: TButton;
|
||||
Edit1: TEdit;
|
||||
Label1: TLabel;
|
||||
labTCritSecTime: TLabel;
|
||||
Button2: TButton;
|
||||
labRTLCritSecTime: TLabel;
|
||||
Button3: TButton;
|
||||
labMonitorTime: TLabel;
|
||||
procedure Button1Click(Sender: TObject);
|
||||
procedure Button2Click(Sender: TObject);
|
||||
procedure Button3Click(Sender: TObject);
|
||||
private
|
||||
{ Private declarations }
|
||||
public
|
||||
{ Public declarations }
|
||||
end;
|
||||
|
||||
var
|
||||
Form1: TForm1;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
procedure TForm1.Button1Click(Sender: TObject);
|
||||
var
|
||||
I: Integer;
|
||||
cs: TCriticalSection;
|
||||
tc: DWORD;
|
||||
begin
|
||||
cs := TCriticalSection.Create;
|
||||
tc := GetTickCount;
|
||||
for I := 1 to StrToInt(Edit1.Text) do
|
||||
begin
|
||||
cs.Enter;
|
||||
cs.Leave;
|
||||
end;
|
||||
tc := GetTickCount - tc;
|
||||
cs.Free;
|
||||
|
||||
labTCritSecTime.Caption := tc.ToString + ' ms';
|
||||
|
||||
end;
|
||||
|
||||
procedure TForm1.Button2Click(Sender: TObject);
|
||||
var
|
||||
cs: TRTLCriticalSection;
|
||||
I: Integer;
|
||||
tc: DWORD;
|
||||
begin
|
||||
InitializeCriticalSection(cs);
|
||||
tc := GetTickCount;
|
||||
for I := 1 to StrToInt(Edit1.Text) do
|
||||
begin
|
||||
EnterCriticalSection(cs);
|
||||
LeaveCriticalSection(cs)
|
||||
end;
|
||||
tc := GetTickCount - tc;
|
||||
DeleteCriticalSection(cs);
|
||||
labRTLCritSecTime.Caption := tc.ToString + ' ms';
|
||||
end;
|
||||
|
||||
procedure TForm1.Button3Click(Sender: TObject);
|
||||
var
|
||||
I: Integer;
|
||||
Lock: TObject;
|
||||
tc: DWORD;
|
||||
begin
|
||||
Lock := TObject.Create;
|
||||
tc := GetTickCount;
|
||||
for I := 1 to StrToInt(Edit1.Text) do
|
||||
begin
|
||||
System.TMonitor.Enter(Lock);
|
||||
System.TMonitor.Exit(Lock);
|
||||
end;
|
||||
tc := GetTickCount - tc;
|
||||
Lock.Free;
|
||||
|
||||
labMonitorTime.Caption := tc.ToString + ' ms';
|
||||
end;
|
||||
|
||||
end.
|
@ -1,8 +1,8 @@
|
||||
# Многопоточное программирование в Delphi для начинающих
|
||||
|
||||
(редакция 1.5 от 22.11.2020г)
|
||||
(редакция 1.6 от 03.01.2021г)
|
||||
Автор: Логинов Д.С.
|
||||
Пенза, 2020
|
||||
Пенза, 2021
|
||||
|
||||
<!-- Редактирование md-файла выполнялось в notepad++ версии 7.8.9.
|
||||
Подсведка синтаксиса markdown в нём включена по умолчанию.
|
||||
@ -11,9 +11,7 @@ markdown-редакторы не подошли, т.к. они либо не у
|
||||
|
||||
Возможные темы для дальнейшей работы над статьёй:
|
||||
|
||||
- необходимо проверить точность GetTickCount на современной компьютере с Windows 10 20H2
|
||||
- обработка исключений в потоках
|
||||
- логгирование событий, происходящих в потоках
|
||||
- передача данных в доп. поток: использование очереди Windows
|
||||
- передача данных в доп. поток: использование очереди RTL
|
||||
- передача данных в доп. поток: пример организации записи в лог на удалённый сервер:
|
||||
@ -94,7 +92,13 @@ markdown-редакторы не подошли, т.к. они либо не у
|
||||
|
||||
7. Основные объекты синхронизации в Windows [...](#main_sync_objects)
|
||||
|
||||
7.1 Главный поток управляет работой дополнительного потока с помощью объекта "Event" (событие)[...](#sync_obj_event)
|
||||
7.1 Главный поток управляет работой дополнительного потока с помощью объекта "Event" (событие) [...](#sync_obj_event)
|
||||
|
||||
7.2 Использование объекта "Event" для разработки потока логгирования [...](#sync_obj_event_logger)
|
||||
|
||||
7.3 Пара слов об объекте "Mutex" [...](#sync_obj_mutex)
|
||||
|
||||
7.4 Пара слов об объекте "Semaphore" [...](#sync_obj_semaphore)
|
||||
|
||||
8. Где можно и где нельзя создавать и уничтожать потоки [...](#bad_places_for_create_free_threads)
|
||||
|
||||
@ -132,7 +136,7 @@ markdown-редакторы не подошли, т.к. они либо не у
|
||||
|
||||
1. Если ваша программа при нажатии кнопки на форме выполняет обращение к базе данных (либо http-запрос, либо другую операцию, которая может длиться несколько секунд), то Вы можете перед выполнением операции выполнить код `Screen.Cursor := crSQLWait`, а после выполнения операции выполнить код `Screen.Cursor := crDefault`. При нажатии кнопки пользователь увидит, что курсор изменил свою форму (песочные часы с надписью SQL), и скорее всего не будет выполнять никаких действий, пока курсор не примет свой обычный вид. Программисты прибегают к этому приёму очень часто. Пример данного подхода находится в папке "ExNotUseThreads".
|
||||
|
||||
Но вы должны помнить, что если операция выполняется более 5 секунд и пользователь щелкнет мышкой на форме, то в заголовке формы появится надпись "не отвечает", в следствие чего могут быть глюки при работе программы, связанные с тем, что активная форма может улететь на задний план, либо может активироваться форма, которая находится позади модальной формы (примечание: в Windows 10 20H2 надпись "не отвечает" почему-то не появляется, возможно Microsoft отказалась от такого подхода и избавила пользователей от проблем с формами).
|
||||
Но вы должны помнить, что если операция выполняется более 5 секунд и пользователь щелкнет мышкой на форме, то в заголовке формы появится надпись "не отвечает", в следствие чего могут быть глюки при работе программы, связанные с тем, что активная форма может улететь на задний план, либо может активироваться форма, которая находится позади модальной формы.
|
||||
|
||||
:information_source: **Внимание!** *При создании формы всегда передавайте параметр AOwner в конструкторе формы. Например, если из формы Form1 создаётся форма Form2, то код создания может выглядеть следующим образом: `Form2 := TForm2.Create(AOwner)`, где AOwner - это Form1. В этом случае проблемы с "улетанием" формы на задний план будут происходить намного реже.*
|
||||
|
||||
@ -169,11 +173,11 @@ Delphi – это прежде всего инструмент для разра
|
||||
|
||||
В типовом приложении исполняемый код 99% времени занимается тем, что ожидает поступления каких-либо событий. Как только событие поступило, исполняемый код «просыпается» и выполняет действия по обработке этого события, после чего «засыпает» до тех пор, пока не произойдет следующее событие. Таким образом, на компьютере могут быть созданы одновременно десятки тысяч потоков, которые 99% времени находятся в спящем состоянии и не оказывают значительной нагрузки на процессор.
|
||||
|
||||
Важно знать, что в ОС присутствует системный планировщик задач (system scheduler), который отвечает за распределение процессорного времени между потоками. В том случае, если поток выполняет длительную вычислительную задачу (обрабатывает информацию), планировщик задач автоматически выполнит действия, необходимые для перевода данного потока в спящий режим, как только поток истратит выделенный ему квант времени (предположим, 32 мс). Кстати, в большинстве случаев потоки намного раньше переходят в спящий режим, т.к. выполняют свою задачу раньше, чем успевает закончиться выделенный квант времени (time slice), либо переходят в спящий режим в связи с ожиданием завершения ввода-вывода.
|
||||
Важно знать, что в ядре ОС присутствует код, который отвечает за распределение процессорного времени между потоками. Данный код часто называют "планировщик задач" (system scheduler) либо "диспетчер ядра". В том случае, если поток выполняет длительную вычислительную задачу (обрабатывает информацию), планировщик задач автоматически выполнит действия, необходимые для перевода данного потока в спящий режим, как только поток истратит выделенный ему квант времени (предположим, 32 мс). Кстати, в большинстве случаев потоки намного раньше переходят в спящий режим, т.к. выполняют свою задачу раньше, чем успевает закончиться выделенный квант времени (time slice), либо в связи с ожиданием какого-либо события.
|
||||
|
||||
Как только поток переходит в спящий режим, производится поиск следующего потока, готового в выполнению, далее осуществляется восстанавление его контекста и возобновление работы.
|
||||
|
||||
Каждый раз, когда поток переходит в спящий режим, происходит сохранение его контекста (сохраняется текущее состояние регистров процессорного ядра, на котором поток выполняется). При переводе потока в рабочий режим выполняется обратное действие – восстановление контекста потока (загрузка ранее сохраненных значений в регистры процессорного ядра). *Я даю здесь объяснение «на пальцах», а Вы, если интересно, поищите в интернете более подробную информацию.*
|
||||
Каждый раз, когда поток переходит в спящий режим, происходит сохранение его контекста (сохраняется текущее состояние регистров процессорного ядра, на котором поток выполняется). При переводе потока в рабочий режим выполняется обратное действие – восстановление контекста потока (загрузка ранее сохраненных значений в регистры процессорного ядра).
|
||||
|
||||
Кстати, операция переключения контекста по времени далеко не бесплатная, поэтому производители процессоров и разработчики ОС постоянно делают улучшения для повышения скорости данной операции. Если Вы хотите создать эффективный высокопроизводительный WEB-сервер, то будет не лишним подумать над тем, как минимизировать количество переключений контекста при исполнении Вашего кода. *Я не буду давать рекомендаций по поводу создания высокопроизводительного WEB-сервера, поскольку задача эта больше подходит для специализированных языков программирования WEB-серверов, например GoLang, а Delphi заточен в первую очередь на разработку фронтэнда, а также корпоративного программного обеспечения.*
|
||||
|
||||
@ -2064,31 +2068,6 @@ end;
|
||||
|
||||
Однако большое количество потоков, находящихся в спящем состоянии, не оказывает никакого влияния на процесс переключения контекста между активными потоками. Т.е. если поток вызвал функцию `Sleep(0)` или `SwitchToThread`, то учитываются данные, заранее подготовленые планировщиком, лишние действия не выполняются.
|
||||
|
||||
<!--# 6.2 Стоимость переключения контекста потоков <a name="context_cost"></a>
|
||||
|
||||
Переключение контекста потока имеет весьма большую стоимость (несколько тысяч тактов ядра процессора). Она мало зависит от того, на каких ядрах процессора работает поток в тот или иной момент времени. Операции сохранения и восстановления состояния регистров при переключении контекста практически не играют никакой роли. Думаю, что и операция переключения между режимами процессора "user-mode" и "kernel-mode" большой роли не играют. Скорее всего наибольшее время занимает код, исполняемый операционной системной в режиме "kernel-mode", который анализирует указания, полученные от системного планировщика состояние остальных потоков в системе, состояние объектов синхронизации ядра (в том числе мьютексов, эвентов, семафоров). Чем больше в системе запущенных потоков (работающих либо спящих) и чем больше объектов синхронизации ядра, тем больше времени будет занимать операция переключения контекста. А если к этому добавить ещё и срабатывание системного таймера 1000 раз в секунду, то код планировщика и код переключения контекста могут занять большую часть ресурсов процессора.
|
||||
-->
|
||||
|
||||
<!--# 6.2 Роль кэшей процессора <a name="cpu_cache"></a>
|
||||
|
||||
Важно знать, что ядро процессора не может работать с памятью ОЗУ напрямую (будем считать, что ОЗУ - это память, которая представлена платами ОЗУ, например DDR4). Ему доступны непосредственно только регистры и кэш первого уровня ядра L1 (иногда его называют "память ядра").
|
||||
|
||||
Если процессор обращается к ячейке памяти, а её значения нет в кэше первого уровня ядра, то кэш первого уровня запросит эти данные у кэша второго уровня ядра L2. В свою очередь, если у кэша второго уровня ядра нет этих данных, то выполняется обращение к кэшу третьего уровня процессора L3. Если и у кэша третьего уровня этих данных нет, то он берёт их из памяти ОЗУ. Если же у кэша третьего уровня эти данные имеются, но он не уверен в их актуальности, то выполняется обращение к кэшам других ядер процессора.
|
||||
|
||||
На самом деле, алгоритмы работы кэшей и механизм обеспечения согласованности (когерентности) данных между процессорами и их ядрами являются внутренней кухней процессора. Каждый производитель решает эти задачи по своему.
|
||||
|
||||
Что из этого важно знать нам:
|
||||
1. Самый быстрый вид памяти - регистровая. Скорость работы регистров определяется текущей частотой, на которой работает то или иное ядро процессора.
|
||||
2. Кэши процессора являются частью оперативной памяти. Т.е. оперативная память состоит из памяти ОЗУ и памяти кэшей процессора.
|
||||
3. Доступ к памяти кэшей процессора происходит значительно быстрее, чем к памяти ОЗУ (во многом, благодаря меньшему расстоянию от ядра до памяти кэша).
|
||||
4. Кэш первого уровня ядра - это самая быстрая часть оперативной памяти.
|
||||
5. Если код, который выполняется на ядре процессора, выполнил запись в оперативную память, это означает, что он выполнил запись в кэш первого уровня ядра. Вовсе не обязательно, что новое значение будет передано в ОЗУ. Решение об этом принимает кэш третьего уровня. Чем больше размер кэша третьего уровня, тем реже он обращается к памяти ОЗУ.
|
||||
|
||||
А при чём здесь планирование потоков? А вот причем! Каждый раз, когда планировщик решает разбудить поток, он принимает решение о том, на каком ядре процессора поток будет запущен. Если свободно ядро, на котором поток работал до перевода в спящий режим, то поток (вероятнее всего) продолжит работать на том же ядре. В этом случае операция восстановления контекста потока будет наиболее дешёвой, поскольку вся необходимая информация скорее всего осталась в кэше первого уровня ядра. Если это ядро занято, то планировщик попытается найти другое свободное ядро процессора. Но в его кэше первого уровня отсутствуют данные, необходимые для восстановления контекста потока, поэтому потребуется больше времени для возобновления работы потока.
|
||||
|
||||
В том случае, если планировщик не нашёл свободных ядер, то он анализирует приоритеты потоков. Если он обнаружит, что на одном из ядер выполняется поток с меньшим приоритетом, то он может прервать работу такого потока и запустить на этом ядре поток с большим приоритетом.
|
||||
-->
|
||||
|
||||
# 6.3 Приоритеты потоков в Windows <a name="about_priority_in_windows"></a>
|
||||
|
||||
При разработке многопоточной программы для Windows существует возможность назначать потоку уровень приоритета (свойство `TThread.Priority`). По умолчанию используется уровень приоритета `tpNormal`. Тип `TThreadPriority` объявлен следующим образом:
|
||||
@ -2128,11 +2107,11 @@ end;
|
||||
|
||||
Преимуществом объектов синхронизации уровня ядра по сравнению с примитивами синхронизации уровня пользователя, основанными на spin-блокировках, является меньшее потребление ресурсов процессора при длительных интервалах ожидания. Например, если мы решим 100 миллисекунд покрутить цикл spin-блокировки, то процессор окажется на 100% загружен всё это время, а другие потоки не получат необходимый им ресурс процессора. Если же мы будем ожидать 100 мс на объекте синхронизации ядра, то процессор на это время будет доступен для других потоков и в целом компьютер сможет выполнить больший объем полезной работы. С другой стороны, если с помощью spin-блокировки выполняется защита простого участка кода, выполняемого за пару микросекунд, то накладные расходы на spin-блокировку могут оказаться ниже, чем расходы системного планировщика на объект синхронизации.
|
||||
|
||||
В любом случае, для защиты критического участка кода от одновременного доступа из нескольких потоков, чаще всего наиболее эффективным является использование примитива синхронизации ОС "критическая секция". Критическая секция сначала пытается использовать цикл spin-блокировки, а затем, если защищаемый ресурс так и не освободился, то переходит к использованию объекта синхронизации уровня ядра (мьютекс).
|
||||
В любом случае, для защиты критического участка кода от одновременного доступа из нескольких потоков, чаще всего (в ОС Windows) наиболее эффективным является использование примитива синхронизации ОС "критическая секция". Критическая секция сначала пытается использовать цикл spin-блокировки (в режиме user-mode), а затем, если защищаемый ресурс так и не освободился, то переходит к использованию объекта синхронизации уровня ядра "мьютекс" (в режиме kernel-mode). Вместо критической секции в современных вериях Delphi Вы можете использовать `System.TMoninor` (метод `TMonitor.Enter` для начала защиты участка кода и `TMonitor.Leave` для окончания защиты. При этом для блокировки в режиме kernel-mode используется объект Event, а не Mutex).
|
||||
|
||||
# 7.1. Главный поток управляет работой дополнительного потока с помощью объекта "Event" (событие) <a name="sync_obj_event"></a>
|
||||
# 7.1 Главный поток управляет работой дополнительного потока с помощью объекта "Event" (событие) <a name="sync_obj_event"></a>
|
||||
|
||||
В ОС существует очень важный объект синхронизации - Event (событие). С его помощью можно синхронизировать работу нескольких процессов, либо работу нескольких потоков в рамках одного процесса. Очень часто объект Event используется при ожидании события завершения ввода/вывода. Например, при работе с COM-портом мы можем создать объект Event и попросить ОС, чтобы она вызвала `SetEvent` в тот момент, когда в порту появились новые данные. При этом наш код может ожидать появления новых данных с помощью функции из семейства `WaitFor`. Аналогично можно поступить при организации обмена данными по сети, либо при работе с файлами.
|
||||
В любой ОС (Windows, Unix, Linix, MacOS, Android, iOS и т.д.) существует очень важный объект синхронизации - Event (событие). С его помощью можно синхронизировать работу нескольких процессов, либо работу нескольких потоков в рамках одного процесса. Очень часто объект Event используется при ожидании события завершения ввода/вывода. Например, при работе с COM-портом мы можем создать объект Event и попросить ОС, чтобы она вызвала `SetEvent` в тот момент, когда в порту появились новые данные. При этом наш код может ожидать появления новых данных с помощью функции из семейства `WaitFor`. Аналогично можно поступить при организации обмена данными по сети, либо при работе с файлами.
|
||||
|
||||
Но я хочу продемонстрировать простой пример использования объекта Event для управления работой дополнительного потока по команде из главного потока. В данном примере (папка ExSync\ExEvent\FastStopThread) дополнительный поток периодически (каждые 5 секунд) выполняет полезную работу (вызывает метод `DoUsefullWork`). Пауза 5 секунд организована с помощью кода `Event.WaitFor(5 * 1000)`.
|
||||
|
||||
@ -2158,7 +2137,7 @@ begin
|
||||
end;
|
||||
```
|
||||
|
||||
Для работы с объектом ядра "Event" используется класс-обёртка `TEvent`. Данная обёртка является кроссплатформенной и поддерживает (вероятно) все платформы, под которые Delphi позволяет скомпилировать приложение.
|
||||
Для работы с объектом ядра "Event" используется класс-обёртка `TEvent`. Данная обёртка является кроссплатформенной и поддерживает все платформы, под которые Delphi позволяет скомпилировать приложение.
|
||||
|
||||
В конструкторе объявлены 2 констатны: `STATE_NONSIGNALED` и `AUTO_RESET`. Эти константы используются при создании объекта Event. Константа `STATE_NONSIGNALED` означает, что объект Event должен быть создан в "несигнальном" состоянии.
|
||||
|
||||
@ -2228,6 +2207,155 @@ end;
|
||||
|
||||
Мы знаем, что при уничтожении дополнительного потока из основного потока деструктор отрабатывает в контексте основного потока. Как только произойдет вызов `Event.SetEvent`, планировщик может приостановить работу основного потока и возобновить работу дополнительного потока на том же ядре процессора. Если к этому моменту не был вызван метод `Terminate`, то выход из метода `TMyThread.Execute` не произойдет и будет в очередной раз вызван метод `Event.WaitFor(5 * 1000)`. Если пауза в WaitFor больше (несколько минут), то программа подвиснет на несколько минут, пока деструктор объекта-потока не завершит свою работу. Такой глюк не всегда просто поймать, поскольку в большинстве случаев при вызове SetEvent планировщик не приостанавливает работу основного потока.
|
||||
|
||||
# 7.2 Использование объекта "Event" для разработки потока логгирования <a name="sync_obj_event_logger"></a>
|
||||
|
||||
В папке ExSync\ExEvent\SimpleLogger находится проект, в котором демонстрируется разработка простейшего потока логгирования.
|
||||
|
||||
При разработке программного обеспечения очень важно иметь возможность записи в log-файл различных событий, происходящих в программе. Чем подробнее будет информация в log-файле (конечно, в разумных пределах), тем проще разобраться с причинами проблемы, происходящей у клиента (заказчика). Существуют множество готовых систем логгирования для Delphi, в том числе LDSLogger (моей разработки), LoggerPro и многие другие.
|
||||
|
||||
Идея данного примера заключается в том, чтобы показать:
|
||||
а) пример простейшей системы логгирования;
|
||||
б) наличие проблемы с производительностью при попытке записи в log-файл без использования дополнительного потока;
|
||||
в) пример использования объекта Event для немедленного информирования потока о наличии новой информации для записи в log-файл;
|
||||
г) пример организации очереди (из строк) для передачи информации в дополнительный поток.
|
||||
|
||||
Реализация потока логгирования находится в модуле CommonUtils\MTLogger.pas. Наименование класса потока: TLoggerThread.
|
||||
|
||||
Попробуйте скомпилировать проект SimpleLogger.dpr и запустить программу. Нажмите кнопку "Добавить сообщения в лог-файл без дополнительного потока". Программа покажет на экране время, которое заняла данная операция. У меня для 10000 сообщение уходит более 20 секунд.
|
||||
Теперь нажмите кнопку "Добавить сообщения в лог-файл через дополнительный поток". У меня для 10000 сообщений уходит 20 миллисекунд. Разница в 1000 раз! Впечатляет?
|
||||
|
||||
Причины такой огромной разницы в следующем:
|
||||
1. Функция записи строки в лог файл реализована следующем образом:
|
||||
|
||||
```pascal
|
||||
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;
|
||||
```
|
||||
|
||||
Каждый раз файл открывается, а после добавления строки закрывается. У меня каждый вызов этой функции выполняется порядка 2-х миллисекунд (на быстром SSD это может происходить намного быстрее).
|
||||
|
||||
2. Без дополнительного потока данная функция вызывается 10000 раз, что по времени составляет более 20 секунд. При этом основной поток зависает на те же 20 секунд.
|
||||
|
||||
3. Наличие дополнительного потока позволяет минимизировать количество вызовов функции WriteStringToTextFile. Перед вызовом данной функции выполняется составление большой строки, состоящей из множества сообщений, накопленных в буфере (см. реализацию метода `TLoggerThread.Execute`).
|
||||
|
||||
4. При использовании потока `TLoggerThread` Функция `WriteStringToTextFile` вызывается из дополнительного потока, а не из основного. Даже если функция `WriteStringToTextFile` будет выполняться в несколько раз дольше, на работу основного потока это никак не повлияет!
|
||||
|
||||
Обратите внимание на реализацию метода `TLoggerThread.Execute`:
|
||||
|
||||
```pascal
|
||||
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;
|
||||
```
|
||||
|
||||
Здесь используется временный список TmpList, в который копируется текущее содержимое списка LogStrings, причем операция копирования и очистки списка LogStrings защищена с помощью критической секции. Благодаря временному списку минимизируется время нахождения программы в критической секции (копирование строк занимает максимум пару миллисекунд). Сохранение содержимого списка TmpList находится вне критической секции, поэтому длительность сохранения в файл никак не влияет на основной поток.
|
||||
|
||||
Метод добавления стоки для записи в log-файл реализован следующим образом:
|
||||
|
||||
```pascal
|
||||
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;
|
||||
```
|
||||
|
||||
Здесь список LogStrings также защищён с помощью критической секции, а после добавления новой строки вызывается `Event.SetEvent`. При вызове метода `Event.SetEvent` происходит прерывание метода `Event.WaitFor(2000)`, с помощью которого поток ожидает появление данных в списке LogStrings.
|
||||
|
||||
Благодаря явно заданному таймауту 2000 мс мы исключаем вероятность возникновения редкого состояния, при котором основной поток добавил новое событие и вызвал `Event.SetEvent`, а дополнительный поток параллельно был разбужен предыдущим вызовом SetEvent и проигнорировал новый вызов SetEvent (т.е. перевод объекта Event в состояние "NONSIGNALED" при прерывании метода `Event.WaitFor(2000)` происходит одновременно с новым вызовом `Event.SetEvent`, поэтому новый вызов может быть проигнорирован).
|
||||
|
||||
# 7.3 Пара слов об объекте "Mutex" <a name="sync_obj_mutex"></a>
|
||||
|
||||
Я рамках данной работы я не вижу большого смысла подробно останавливаться на объекте ядра "Mutex". Если у читателя есть желание с ним познакомиться поближе, существует бесконечный океан информации в Интернете.
|
||||
|
||||
Отмечу вкратце только основные моменты:
|
||||
|
||||
1. Для объекта Mutex применяется понятие "владение" (owned). У мьютекса может быть только один владелец. Первый поток, применивший к мьютексу операцию WaitForXXX (одна из функцию из семейства WaitFor) автоматически становится владельцем мьютекса, а мьютекс переходит в состояние "занят". Как только поток-владелец вызывает функцию ReleaseMutex, он перестаёт быть владельцем, а мьютекс переходит в состояние "свободен". При этом диспетчер ядра проверяет очередь потоков, ожидающих освобождения мьютекса на вызове функции WaitForXXX и передаёт мьютекс во владение первому потоку из очереди (если такая очередь вообще имеется) и работа очередного потока-владельца возобновляется.
|
||||
|
||||
2. В ОС Windows все операции с мьютексом выполняются в режиме ядра. Процесс блокировки критического участка кода с помощью мьютекса является весьма дорогой операцией по сравнению с критической секцией. С другой стороны, процесс ожидания освобождения мьютекса практически не использует ресурсы процессора (в отличии от этапа "цикл spin-блокировки" в критической секции).
|
||||
|
||||
3. Не следует защищать с помощью мьютекса критические участки кода, которые выполняются очень быстро (несколько микросекунд) и не связаны с операциями ввода/вывода. В таких случаях выгоднее использовать критическую секцию / TMonitor / spin-блокировку / атомарные функции.
|
||||
|
||||
4. С помощью именованного мьютекса удобно защищать ресурсы, которые могут использоваться одновременно из разных приложений: файлы (целиком или отдельные участки), реестр (если несколько значений должны гарантированно храниться как логически связанные данные), общий участок памяти (через FileMapping) и др.
|
||||
|
||||
5. Сам факт наличия созданного объекта "именованный мьютекс" в одном приложении может использоваться при принятии решений в другом приложении. Например, если первый экземпляр программы создал именованный мьютекс с именем "MyProgramStartProtect", то второй экземпляр после создания мьютекса с таким же именем получит GetLastError = ERROR_ALREADY_EXISTS, после чего можно прервать запуск второго экземпляра приложения.
|
||||
|
||||
# 7.4 Пара слов об объекте "Semaphore" <a name="sync_obj_semaphore"></a>
|
||||
|
||||
Семафор - старейший объект синхронизации режима ядра. Его реализация присутствует во всех популярных ОС. На его основе разработано множество различных алгоритмов синхронизации. Очень хорошо данная тема рассмотрена в wiki-статье [Семафор (программирование)](https://ru.wikipedia.org/wiki/%D0%A1%D0%B5%D0%BC%D0%B0%D1%84%D0%BE%D1%80_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)).
|
||||
|
||||
За многие десятилетия было опубликовано множество научных работ, в которых показано, как реализовать тот или иной алгоритм синхронизации на основе семафора. К сожалению, с практической точки зрения есть трудности: весьма сложно понять, для чего нужно применять семафор непосредственно программисту при решении прикладных задач (вероятно, в будущем у меня появятся удачные примеры использования семафоров). Понятно, что семафор позволяет ограничить количество потоков, которые одновременно работают над выполнением какой-то задачи. Но на практике получается обычно наоборот - один поток выполняет множество задач (по очереди). Пишут либо про некие абстрактные "ресурсы", доступ к которым контролируется с помощью семафора, либо знакомят с проблемой взаимоблокировок и с её решением (см. "Обедающие философы"), либо чрезмерно детально описывают решение некоторой прикладной задачи, в которой смогли удачно вписать использование семафора.
|
||||
|
||||
К сожалению, я не решал задач, которые могли бы продемонстрировать возможности семафоров при разработке прикладного ПО. К тому же я не собирался в рамках данной статьи (для начинающих) демонстрировать сложные алгоритмы синхронизации между потоками.
|
||||
|
||||
# 8. Где можно и где нельзя создавать и уничтожать потоки <a name="bad_places_for_create_free_threads"></a>
|
||||
|
||||
Данные замечания актуальны при разработке программ под Windows. Существует ряд мест, где не следует создавать либо уничтожать потоки.
|
||||
@ -2236,7 +2364,6 @@ end;
|
||||
Если Вы разрабатываете DLL-библиотеку, в которой используются дополнительные потоки, то рекомендую реализовать в ней две экспортные функции – одну для создания необходимых объектов в библиотеке (например, `CreateLibrary`), вторая для уничтожения созданных объектов (например, `DestroyLibrary`). В этом случае код создания потоков можно разместить в `CreateLibrary` (либо создавать их позже), а код уничтожения объектов потоков разместить в `DestroyLibrary`.
|
||||
|
||||
|
||||
|
||||
# 9. Немного о threadvar <a name="about_threadvar"></a>
|
||||
|
||||
При разработке многопоточного приложения в Delphi программист может использовать ключевое слово `threadvar` (локальная переменная потока) при объявлении глобальных переменных. Для каждого дополнительного потока создаётся собственная копия таких переменных. Один поток не сможет прочитать значение, которое записал в эту переменную другой поток. Переменные, объявленные в `threadvar`, создаются при создании дополнительного потока, а уничтожаются при завершении работы потока.
|
||||
|
Loading…
Reference in New Issue
Block a user