Đồng bộ hóa chương trình bằng biến toàn cục
Do các biến toàn cục tồn tại bên ngoài các chương trình MQL, chúng hữu ích trong việc tổ chức các cờ bên ngoài để điều khiển nhiều bản sao của cùng một chương trình hoặc truyền tín hiệu giữa các chương trình khác nhau. Ví dụ đơn giản nhất là giới hạn số lượng bản sao của một chương trình có thể chạy. Điều này có thể cần thiết để ngăn chặn việc vô tình sao chép Expert Advisor trên các biểu đồ khác nhau (dẫn đến việc các lệnh giao dịch có thể bị nhân đôi), hoặc để triển khai một phiên bản demo.
Thoạt nhìn, việc kiểm tra như vậy có thể được thực hiện trong mã nguồn như sau.
void OnStart()
{
const string gv = "AlreadyRunning";
// nếu biến tồn tại, thì một phiên bản đã đang chạy
if(GlobalVariableCheck(gv)) return;
// tạo biến như một cờ báo hiệu sự hiện diện của một bản sao đang hoạt động
GlobalVariableSet(gv, 0);
while(!IsStopped())
{
// chu kỳ làm việc
}
// xóa biến trước khi thoát
GlobalVariableDel(gv);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Phiên bản đơn giản nhất được hiển thị ở đây bằng cách sử dụng script làm ví dụ. Đối với các loại chương trình MQL khác, khái niệm kiểm tra tổng quát sẽ tương tự, mặc dù vị trí của các lệnh có thể khác nhau: thay vì một chu kỳ làm việc vô tận, Expert Advisors và các chỉ báo sử dụng các trình xử lý sự kiện đặc trưng của chúng, được terminal gọi lặp đi lặp lại. Chúng ta sẽ nghiên cứu những vấn đề này sau.
Vấn đề với mã được trình bày là nó không tính đến việc thực thi song song của các chương trình MQL.
Một chương trình MQL thường chạy trong luồng riêng của nó. Đối với ba trong số bốn loại chương trình MQL, cụ thể là Expert Advisors, script và dịch vụ, hệ thống chắc chắn phân bổ các luồng riêng biệt. Đối với các chỉ báo, một luồng chung được phân bổ cho tất cả các phiên bản của chúng, hoạt động trên cùng một tổ hợp biểu tượng làm việc và khung thời gian. Nhưng các chỉ báo trên các tổ hợp khác nhau vẫn thuộc về các luồng khác nhau.
Hầu như luôn luôn, rất nhiều luồng đang chạy trong terminal — nhiều hơn số lượng lõi xử lý. Vì lý do này, mỗi luồng thỉnh thoảng bị hệ thống tạm dừng để cho phép các luồng khác hoạt động. Do tất cả các chuyển đổi giữa các luồng diễn ra rất nhanh, chúng ta, với tư cách là người dùng, không nhận thấy "tổ chức bên trong" này. Tuy nhiên, mỗi lần tạm dừng có thể ảnh hưởng đến thứ tự mà các luồng khác nhau truy cập vào các tài nguyên dùng chung. Biến toàn cục là một trong những tài nguyên như vậy.
Từ góc nhìn của chương trình, một khoảng dừng có thể xảy ra giữa bất kỳ hai lệnh liền kề nào. Nếu biết điều này và xem lại ví dụ của chúng ta, không khó để nhận ra một điểm mà logic làm việc với biến toàn cục có thể bị phá vỡ.
Thật vậy, bản sao đầu tiên (luồng) có thể thực hiện kiểm tra và không tìm thấy biến nhưng ngay lập tức bị tạm dừng. Kết quả là, trước khi nó kịp tạo biến bằng lệnh tiếp theo, ngữ cảnh thực thi chuyển sang bản sao thứ hai. Bản sao thứ hai cũng không tìm thấy biến và quyết định tiếp tục hoạt động, giống như bản sao đầu tiên. Để rõ ràng, mã nguồn giống hệt nhau của hai bản sao được hiển thị dưới đây dưới dạng hai cột lệnh theo thứ tự thực thi xen kẽ của chúng.
// Luồng 1
void OnStart()
{
const string gv = "AlreadyRunning";
if(GlobalVariableCheck(gv)) return;
// no variable
GlobalVariableSet(gv, 0);
// "I am the first and only"
while(!IsStopped())
{
;
}
GlobalVariableDel(gv);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Luồng 2
void OnStart()
{
const string gv = "AlreadyRunning";
if(GlobalVariableCheck(gv)) return;
// still no variable
GlobalVariableSet(gv, 0);
// "No, I'm the first and only one"
while(!IsStopped())
{
;
}
GlobalVariableDel(gv);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Tất nhiên, cách thức chuyển đổi giữa các luồng như vậy mang tính quy ước khá lớn. Nhưng trong trường hợp này, điều quan trọng là chính khả năng vi phạm logic của chương trình, ngay cả trong một dòng duy nhất. Khi có nhiều chương trình (luồng), xác suất xảy ra các hành động không lường trước với tài nguyên chung tăng lên. Điều này có thể đủ để khiến Expert Advisor thua lỗ vào thời điểm bất ngờ nhất hoặc làm sai lệch các ước tính phân tích kỹ thuật.
Điều khó chịu nhất về các lỗi loại này là chúng rất khó phát hiện. Trình biên dịch không thể phát hiện chúng, và chúng biểu hiện một cách ngẫu nhiên trong thời gian chạy. Nhưng nếu lỗi không lộ ra trong thời gian dài, điều đó không có nghĩa là không có lỗi.
Để giải quyết những vấn đề như vậy, cần phải đồng bộ hóa việc truy cập của tất cả các bản sao chương trình vào tài nguyên chung (trong trường hợp này là biến toàn cục).
Trong khoa học máy tính, có một khái niệm đặc biệt — mutex (loại trừ lẫn nhau) — là một đối tượng để cung cấp quyền truy cập độc quyền vào một tài nguyên chung từ các chương trình song song. Mutex ngăn ngừa dữ liệu bị mất hoặc hỏng do các thay đổi không đồng bộ. Thông thường, việc truy cập mutex đồng bộ hóa các chương trình khác nhau do thực tế chỉ một trong số chúng có thể chỉnh sửa dữ liệu được bảo vệ bằng cách chiếm mutex tại một thời điểm cụ thể, và những chương trình còn lại buộc phải đợi cho đến khi mutex được giải phóng.
Không có sẵn mutex nguyên bản trong MQL5. Nhưng đối với biến toàn cục, một hiệu ứng tương tự có thể đạt được bằng hàm sau, mà chúng ta sẽ xem xét.
bool GlobalVariableSetOnCondition(const string name, double value, double precondition)
Hàm này đặt một giá trị mới value
cho biến toàn cục hiện có name
với điều kiện giá trị hiện tại của nó bằng precondition
.
Nếu thành công, hàm trả về true
. Nếu không, nó trả về false
, và mã lỗi sẽ có sẵn trong _LastError
. Đặc biệt, nếu biến không tồn tại, hàm sẽ tạo lỗi ERR_GLOBALVARIABLE_NOT_FOUND (4501)
.
Hàm cung cấp quyền truy cập nguyên tử vào biến toàn cục, tức là nó thực hiện hai hành động một cách không thể tách rời: kiểm tra giá trị hiện tại của nó, và nếu khớp với điều kiện, gán giá trị mới value
cho biến.
Mã tương đương của hàm có thể được biểu diễn gần đúng như sau (tại sao là "gần đúng" sẽ được giải thích sau):
bool GlobalVariableZetOnCondition(const string name, double value, double precondition)
{
bool result = false;
{ /* bật bảo vệ ngắt */ }
if(GlobalVariableCheck(name) && (GlobalVariableGet(name) == precondition))
{
GlobalVariableSet(name, value);
result = true;
}
{ /* tắt bảo vệ ngắt */ }
return result;
}
2
3
4
5
6
7
8
9
10
11
12
Việc triển khai mã như thế này, hoạt động như mong muốn, là không thể vì hai lý do. Thứ nhất, không có gì để triển khai các khối bật và tắt bảo vệ ngắt trong MQL5 thuần túy (bên trong hàm tích hợp sẵn GlobalVariableSetOnCondition
, điều này được cung cấp bởi nhân hệ thống). Thứ hai, lời gọi hàm GlobalVariableGet
thay đổi thời gian sử dụng cuối cùng của biến, trong khi hàm GlobalVariableSetOnCondition
không thay đổi nó nếu điều kiện tiên quyết không được đáp ứng.
Để chứng minh cách sử dụng GlobalVariableSetOnCondition
, chúng ta sẽ chuyển sang một loại chương trình MQL mới: dịch vụ. Chúng ta sẽ nghiên cứu chi tiết về chúng trong một phần riêng. Hiện tại, cần lưu ý rằng cấu trúc của chúng rất giống với script: đối với cả hai, chỉ có một hàm chính (điểm vào), OnStart
. Sự khác biệt đáng kể duy nhất là script chạy trên biểu đồ, trong khi dịch vụ chạy độc lập (trong nền).
Nhu cầu thay thế script bằng dịch vụ được giải thích bởi thực tế ý nghĩa ứng dụng của nhiệm vụ mà chúng ta sử dụng GlobalVariableSetOnCondition
nằm ở việc đếm số lượng phiên bản đang chạy của chương trình, với khả năng đặt giới hạn. Trong trường hợp này, các xung đột với việc sửa đổi đồng thời bộ đếm chung chỉ có thể xảy ra vào thời điểm khởi chạy nhiều chương trình. Tuy nhiên, với script, khá khó để chạy nhiều bản sao của chúng trên các biểu đồ khác nhau trong một khoảng thời gian tương đối ngắn. Ngược lại, đối với dịch vụ, giao diện terminal có một cơ chế thuận tiện để khởi chạy hàng loạt (nhóm). Ngoài ra, tất cả các dịch vụ đã kích hoạt sẽ tự động khởi động khi terminal khởi động lại lần sau.
Cơ chế được đề xuất để đếm số lượng bản sao cũng sẽ được yêu cầu cho các loại chương trình MQL khác. Vì Expert Advisors và chỉ báo vẫn gắn với các biểu đồ ngay cả khi terminal tắt, lần tiếp theo khi bật lại, tất cả các chương trình đọc cài đặt và tài nguyên chung của chúng gần như đồng thời. Do đó, nếu một giới hạn về số lượng bản sao được tích hợp vào một số Expert Advisors và chỉ báo, việc đồng bộ hóa việc đếm dựa trên biến toàn cục là rất quan trọng.
Trước tiên, hãy xem xét một dịch vụ triển khai kiểm soát bản sao ở chế độ ngây thơ, không sử dụng GlobalVariableSetOnCondition
, và đảm bảo rằng vấn đề thất bại của bộ đếm là có thật. Các dịch vụ nằm trong một thư mục con riêng trong thư mục mã nguồn chung, vì vậy đây là đường dẫn mở rộng − MQL5/Services/MQL5Book/p4/GlobalsNoCondition.mq5
.
Ở đầu tệp dịch vụ, cần có một chỉ thị:
#property service
Trong dịch vụ, chúng ta sẽ cung cấp 2 biến đầu vào để đặt giới hạn về số lượng bản sao được phép chạy song song và một độ trễ để mô phỏng gián đoạn thực thi do tải lớn lên đĩa và CPU của máy tính, điều thường xảy ra khi terminal được khởi chạy. Điều này sẽ giúp dễ dàng tái hiện vấn đề mà không cần phải khởi động lại terminal nhiều lần với hy vọng xảy ra mất đồng bộ. Vì vậy, chúng ta sẽ cố gắng bắt một lỗi chỉ xảy ra ngẫu nhiên, nhưng đồng thời, nếu nó xảy ra, sẽ mang lại hậu quả nghiêm trọng.
input int limit = 1; // Giới hạn
input int startPause = 100; // Độ trễ (ms)
2
Việc mô phỏng độ trễ dựa trên hàm Sleep
.
void Delay()
{
if(startPause > 0)
{
Sleep(startPause);
}
}
2
3
4
5
6
7
Trước hết, một biến toàn cục tạm thời được khai báo bên trong hàm OnStart
. Vì nó được thiết kế để đếm các bản sao đang chạy của chương trình, không có ý nghĩa gì khi làm nó cố định: mỗi khi bạn khởi động terminal, cần đếm lại.
void OnStart()
{
PRTF(GlobalVariableTemp(__FILE__));
...
2
3
4
Để tránh trường hợp người dùng tạo trước một biến cùng tên và gán giá trị âm cho nó, chúng ta đưa vào biện pháp bảo vệ.
int count = (int)GlobalVariableGet(__FILE__);
if(count < 0)
{
Print("Phát hiện số đếm âm. Không được phép.");
return;
}
2
3
4
5
6
Tiếp theo, đoạn mã với chức năng chính bắt đầu. Nếu bộ đếm đã lớn hơn hoặc bằng số lượng tối đa cho phép, chúng ta ngắt việc khởi chạy chương trình.
if(count >= limit)
{
PrintFormat("Không thể khởi động quá %d bản sao", limit);
return;
}
2
3
4
5
Nếu không, chúng ta tăng bộ đếm lên 1 và ghi nó vào biến toàn cục. Trước đó, chúng ta mô phỏng độ trễ để gây ra tình huống mà một chương trình khác có thể can thiệp giữa việc đọc và ghi biến trong chương trình của chúng ta.
Delay();
PRTF(GlobalVariableSet(__FILE__, count + 1));
2
Nếu điều này thực sự xảy ra, bản sao của chương trình chúng ta sẽ tăng và gán một giá trị đã lỗi thời, không chính xác. Điều này sẽ dẫn đến tình huống mà ở một bản sao khác của chương trình chạy song song với chúng ta, giá trị count
tương tự đã được xử lý hoặc sẽ được xử lý lại.
Công việc hữu ích của dịch vụ được thể hiện bằng vòng lặp sau.
int loop = 0;
while(!IsStopped())
{
PrintFormat("Bản sao %d đang hoạt động [%d]...", count, loop++);
// ...
Sleep(3000);
}
2
3
4
5
6
7
Sau khi người dùng dừng dịch vụ (đối với điều này, giao diện có menu ngữ cảnh; sẽ được trình bày thêm sau), vòng lặp sẽ kết thúc, và chúng ta cần giảm bộ đếm.
int last = (int)GlobalVariableGet(__FILE__);
if(last > 0)
{
PrintFormat("Bản sao %d (trong số %d) đang dừng", count, last);
Delay();
PRTF(GlobalVariableSet(__FILE__, last - 1));
}
else
{
Print("Tràn dưới bộ đếm");
}
}
2
3
4
5
6
7
8
9
10
11
12
Các dịch vụ đã biên dịch nằm trong nhánh tương ứng của "Navigator".
Các dịch vụ trong "Navigator" và menu ngữ cảnh của chúng
Bằng cách nhấp chuột phải, chúng ta sẽ mở menu ngữ cảnh và tạo hai phiên bản của dịch vụ GlobalsNoCondition.mq5
bằng cách gọi lệnh Add service
hai lần. Trong trường hợp này, mỗi lần một hộp thoại sẽ mở ra với cài đặt dịch vụ, nơi bạn nên giữ nguyên giá trị mặc định cho các tham số.
Cần lưu ý rằng lệnh
Add service
khởi động dịch vụ vừa tạo ngay lập tức. Nhưng chúng ta không cần điều này. Do đó, ngay sau khi khởi chạy mỗi bản sao, chúng ta phải gọi lại menu ngữ cảnh và thực hiện lệnhStop
(nếu một phiên bản cụ thể được chọn), hoặcStop everything
(nếu chương trình, tức là toàn bộ nhóm các phiên bản được tạo, được chọn).
Phiên bản đầu tiên của dịch vụ sẽ mặc định có tên hoàn toàn khớp với tệp dịch vụ ("GlobalsNoCondition"), và trong tất cả các phiên bản tiếp theo, một số tăng dần sẽ được tự động thêm vào. Đặc biệt, phiên bản thứ hai được liệt kê là "GlobalsNoCondition 1". Terminal cho phép bạn đổi tên các phiên bản thành văn bản tùy ý bằng lệnh Rename
, nhưng chúng ta sẽ không làm điều đó.
Bây giờ mọi thứ đã sẵn sàng cho thí nghiệm. Hãy thử chạy hai phiên bản cùng một lúc. Để làm điều này, hãy chạy lệnh Run All
cho nhánh GlobalsNoCondition
tương ứng.
Hãy nhớ rằng giới hạn 1 phiên bản đã được đặt trong các tham số. Tuy nhiên, theo nhật ký, nó không hoạt động.
GlobalsNoCondition GlobalVariableTemp(GlobalsNoCondition.mq5)=true / ok
GlobalsNoCondition 1 GlobalVariableTemp(GlobalsNoCondition.mq5)=false / GLOBALVARIABLE_EXISTS(4502)
GlobalsNoCondition GlobalVariableSet(GlobalsNoCondition.mq5,count+1)=2021.08.31 17:47:17 / ok
GlobalsNoCondition Copy 0 is working [0]...
GlobalsNoCondition 1 GlobalVariableSet(GlobalsNoCondition.mq5,count+1)=2021.08.31 17:47:17 / ok
GlobalsNoCondition 1 Copy 0 is working [0]...
GlobalsNoCondition Copy 0 is working [1]...
GlobalsNoCondition 1 Copy 0 is working [1]...
GlobalsNoCondition Copy 0 is working [2]...
GlobalsNoCondition 1 Copy 0 is working [2]...
GlobalsNoCondition Copy 0 is working [3]...
GlobalsNoCondition 1 Copy 0 is working [3]...
GlobalsNoCondition Copy 0 (out of 1) is stopping
GlobalsNoCondition GlobalVariableSet(GlobalsNoCondition.mq5,last-1)=2021.08.31 17:47:26 / ok
GlobalsNoCondition 1 Count underflow
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Cả hai bản sao đều "nghĩ" rằng chúng là số 0 (đầu ra "Copy 0" từ vòng lặp công việc) và tổng số của chúng sai lầm bằng 1 vì đó là giá trị mà cả hai bản sao đã lưu vào biến bộ đếm.
Chính vì điều này mà khi các dịch vụ bị dừng (lệnh Stop everything
), chúng ta nhận được thông báo về trạng thái không chính xác ("Count underflow"): sau tất cả, mỗi bản sao đều cố gắng giảm bộ đếm đi 1, và kết quả là bản sao được thực thi thứ hai nhận được giá trị âm.
Để giải quyết vấn đề, bạn cần sử dụng hàm GlobalVariableSetOnCondition
. Dựa trên mã nguồn của dịch vụ trước đó, một phiên bản cải tiến GlobalsWithCondition.mq5
đã được chuẩn bị. Nhìn chung, nó tái hiện logic của phiên bản trước, nhưng có những khác biệt đáng kể.
Thay vì chỉ gọi GlobalVariableSet
để tăng bộ đếm, một cấu trúc phức tạp hơn đã phải được viết.
const int maxRetries = 5;
int retry = 0;
while(count < limit && retry < maxRetries)
{
Delay();
if(PRTF(GlobalVariableSetOnCondition(__FILE__, count + 1, count))) break;
// điều kiện không được đáp ứng (count đã lỗi thời), gán thất bại,
// hãy thử lại với điều kiện mới nếu vòng lặp không vượt quá giới hạn
count = (int)GlobalVariableGet(__FILE__);
PrintFormat("Bộ đếm đã bị thay đổi bởi phiên bản khác: %d", count);
retry++;
}
if(count == limit || retry == maxRetries)
{
PrintFormat("Khởi động thất bại: count: %d, retries: %d", count, retry);
return;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Do hàm GlobalVariableSetOnCondition
có thể không ghi giá trị bộ đếm mới nếu giá trị cũ đã lỗi thời, chúng ta đọc lại biến toàn cục trong vòng lặp và lặp lại các nỗ lực tăng nó cho đến khi vượt quá giá trị bộ đếm tối đa cho phép. Điều kiện vòng lặp cũng giới hạn số lần thử. Nếu vòng lặp kết thúc với việc vi phạm một trong các điều kiện, thì việc cập nhật bộ đếm thất bại, và chương trình không nên tiếp tục chạy.
Chiến lược đồng bộ hóa
Về lý thuyết, có một số chiến lược tiêu chuẩn để triển khai việc chiếm tài nguyên chung.
Chiến lược đầu tiên là kiểm tra nhẹ xem tài nguyên có rảnh không và sau đó khóa nó chỉ khi nó rảnh vào lúc đó. Nếu nó đang bận, thuật toán lên kế hoạch cho lần thử tiếp theo sau một khoảng thời gian nhất định, và trong thời gian này nó tham gia vào các nhiệm vụ khác (đó là lý do cách tiếp cận này được ưu tiên cho các chương trình có nhiều lĩnh vực hoạt động/trách nhiệm). Một tương tự của mô hình hành vi này trong phiên bản cho hàm
GlobalVariableSetOnCondition
là một lời gọi đơn lẻ, không có vòng lặp, thoát khỏi khối hiện tại khi thất bại. Việc thay đổi biến bị hoãn lại "cho đến thời điểm tốt hơn".Chiến lược thứ hai kiên trì hơn, và nó được áp dụng trong script của chúng ta. Đây là một vòng lặp lặp lại yêu cầu tài nguyên trong một số lần nhất định hoặc một thời gian được xác định trước (thời gian chờ cho phép đối với tài nguyên). Nếu vòng lặp hết hạn và không đạt được kết quả tích cực (gọi hàm
GlobalVariableSetOnCondition
không bao giờ trả vềtrue
), chương trình cũng thoát khỏi khối hiện tại và có lẽ lên kế hoạch thử lại sau.Cuối cùng, chiến lược thứ ba, cứng rắn nhất, liên quan đến việc yêu cầu tài nguyên "đến cùng". Nó có thể được xem như một vòng lặp vô hạn với lời gọi hàm. Cách tiếp cận này có ý nghĩa để sử dụng trong các chương trình tập trung vào một nhiệm vụ cụ thể và không thể tiếp tục hoạt động mà không có tài nguyên bị chiếm. Trong MQL5, sử dụng vòng lặp
while(!IsStopped())
cho việc này và đừng quên gọiSleep
bên trong.Điều quan trọng cần lưu ý ở đây là vấn đề tiềm ẩn với việc "cứng rắn" chiếm nhiều tài nguyên. Hãy tưởng tượng rằng một chương trình MQL sửa đổi nhiều biến toàn cục (trong lý thuyết, đây là tình huống phổ biến). Nếu một bản sao của nó chiếm một biến, và bản sao thứ hai chiếm một biến khác, và cả hai sẽ đợi giải phóng, sẽ xảy ra tình trạng khóa lẫn nhau (deadlock).
Dựa trên những điều trên, việc chia sẻ biến toàn cục và các tài nguyên khác (ví dụ, tệp) nên được thiết kế và phân tích cẩn thận về các khóa và cái gọi là "điều kiện đua", khi thực thi song song của các chương trình dẫn đến kết quả không xác định (tùy thuộc vào thứ tự hoạt động của chúng).
Sau khi hoàn thành chu kỳ làm việc trong phiên bản mới của dịch vụ, thuật toán giảm bộ đếm cũng đã được thay đổi theo cách tương tự.
retry = 0;
int last = (int)GlobalVariableGet(__FILE__);
while(last > 0 && retry < maxRetries)
{
PrintFormat("Bản sao %d (trong số %d) đang dừng", count, last);
Delay();
if(PRTF(GlobalVariableSetOnCondition(__FILE__, last - 1, last))) break;
last = (int)GlobalVariableGet(__FILE__);
retry++;
}
if(last <= 0)
{
PrintFormat("Thoát bất ngờ: %d", last);
}
else
{
PrintFormat("Dừng bản sao %d: count: %d, retries: %d", count, last, retry);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Để thử nghiệm, hãy tạo ba phiên bản cho dịch vụ mới. Trong cài đặt của mỗi phiên bản, trong tham số Limit
, chúng ta chỉ định 2 phiên bản (để tiến hành thử nghiệm dưới các điều kiện thay đổi). Hãy nhớ rằng việc tạo mỗi phiên bản ngay lập tức khởi chạy nó, điều mà chúng ta không cần, và do đó mỗi phiên bản mới tạo nên được dừng lại.
Các phiên bản sẽ nhận các tên mặc định "GlobalsWithCondition", "GlobalsWithCondition 1", và "GlobalsWithCondition 2".
Khi mọi thứ đã sẵn sàng, chúng ta chạy tất cả các phiên bản cùng một lúc và nhận được một cái gì đó như thế này trong nhật ký.
GlobalsWithCondition 2 GlobalVariableTemp(GlobalsWithCondition.mq5)=false / GLOBALVARIABLE_EXISTS(4502)
GlobalsWithCondition 1 GlobalVariableTemp(GlobalsWithCondition.mq5)=false / GLOBALVARIABLE_EXISTS(4502)
GlobalsWithCondition GlobalVariableTemp(GlobalsWithCondition.mq5)=true / ok
GlobalsWithCondition GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,count+1,count)=true / ok
GlobalsWithCondition 1 GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,count+1,count)=false / GLOBALVARIABLE_NOT_FOUND(4501)
GlobalsWithCondition 2 GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,count+1,count)=false / GLOBALVARIABLE_NOT_FOUND(4501)
GlobalsWithCondition 1 Counter is already altered by other instance: 1
GlobalsWithCondition Copy 0 is working [0]...
GlobalsWithCondition 2 Counter is already altered by other instance: 1
GlobalsWithCondition 1 GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,count+1,count)=true / ok
GlobalsWithCondition 1 Copy 1 is working [0]...
GlobalsWithCondition 2 GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,count+1,count)=false / GLOBALVARIABLE_NOT_FOUND(4501)
GlobalsWithCondition 2 Counter is already altered by other instance: 2
GlobalsWithCondition 2 Start failed: count: 2, retries: 2
GlobalsWithCondition Copy 0 is working [1]...
GlobalsWithCondition 1 Copy 1 is working [1]...
GlobalsWithCondition Copy 0 is working [2]...
GlobalsWithCondition 1 Copy 1 is working [2]...
GlobalsWithCondition Copy 0 is working [3]...
GlobalsWithCondition 1 Copy 1 is working [3]...
GlobalsWithCondition Copy 0 (out of 2) is stopping
GlobalsWithCondition GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,last-1,last)=true / ok
GlobalsWithCondition Stopped copy 0: count: 2, retries: 0
GlobalsWithCondition 1 Copy 1 (out of 1) is stopping
GlobalsWithCondition 1 GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,last-1,last)=true / ok
GlobalsWithCondition 1 Stopped copy 1: count: 1, retries: 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Trước hết, hãy chú ý đến sự ngẫu nhiên, nhưng đồng thời là minh họa trực quan về hiệu ứng chuyển đổi ngữ cảnh được mô tả cho các chương trình chạy song song. Phiên bản đầu tiên tạo biến tạm thời là "GlobalsWithCondition" không có số: điều này có thể thấy từ kết quả của hàm GlobalVariableTemp
là true
. Tuy nhiên, trong nhật ký, dòng này chỉ chiếm vị trí thứ ba, và hai dòng trước đó chứa kết quả của việc gọi cùng hàm trong các bản sao có tên với số 1 và 2; trong đó hàm GlobalVariableTemp
trả về false
. Điều này có nghĩa là các bản sao này đã kiểm tra biến sau đó, mặc dù các luồng của chúng sau đó vượt qua luồng "GlobalsWithCondition" không số và xuất hiện trong nhật ký sớm hơn.
Nhưng hãy quay lại thuật toán đếm chương trình chính của chúng ta. Phiên bản "GlobalsWithCondition" là phiên bản đầu tiên vượt qua kiểm tra và bắt đầu hoạt động dưới định danh nội bộ "Copy 0" (chúng ta không thể biết từ mã dịch vụ rằng người dùng đã đặt tên phiên bản như thế nào: không có hàm nào như vậy trong API MQL5, ít nhất là vào lúc này).
Nhờ hàm GlobalVariableSetOnCondition
, trong các phiên bản 1 và 2 ("GlobalsWithCondition 1", "GlobalsWithCondition 2"), việc sửa đổi bộ đếm đã được phát hiện: nó là 0 khi bắt đầu, nhưng "GlobalsWithCondition" đã tăng nó lên 1. Cả hai phiên bản đến muộn đều xuất thông báo "Counter is already altered by other instance: 1". Một trong các phiên bản này ("GlobalsWithCondition 1") vượt qua số 2, đã quản lý để lấy giá trị mới 1 từ biến và tăng nó lên 2. Điều này được chỉ ra bởi một lời gọi thành công GlobalVariableSetOnCondition
(nó trả về true
). Và sau đó, có một thông báo về việc nó bắt đầu hoạt động, "Copy 1 is working".
Việc giá trị của bộ đếm nội bộ trùng với số phiên bản bên ngoài là hoàn toàn ngẫu nhiên. Có thể rất tốt là "GlobalsWithCondition 2" đã bắt đầu trước "GlobalsWithCondition 1" (hoặc theo một trình tự khác, với ba bản sao). Khi đó, số bên ngoài và bên trong sẽ khác nhau. Bạn có thể lặp lại thí nghiệm bằng cách khởi động và dừng tất cả các dịch vụ nhiều lần, và trình tự mà các phiên bản tăng biến bộ đếm rất có thể sẽ khác nhau. Nhưng trong bất kỳ trường hợp nào, giới hạn về tổng số sẽ cắt bỏ một phiên bản thừa.
Khi phiên bản cuối cùng của "GlobalsWithCondition 2" được cấp quyền truy cập vào biến toàn cục, giá trị 2 đã được lưu trữ ở đó. Vì đây là giới hạn chúng ta đã đặt, chương trình không khởi động.
GlobalVariableSetOnCondition(GlobalsWithCondition.mq5,count+1,count)=false / GLOBALVARIABLE_NOT_FOUND(4501)
Counter is already altered by other instance: 2
Start failed: count: 2, retries: 2
2
3
Tiếp theo, các bản sao của "GlobalsWithCondition" và "GlobalsWithCondition 1" "quay" trong chu kỳ làm việc cho đến khi các dịch vụ bị dừng.
Bạn có thể thử dừng chỉ một phiên bản. Sau đó, sẽ có thể khởi chạy một phiên bản khác trước đó bị cấm thực thi do vượt quá hạn ngạch.
Tất nhiên, phiên bản bảo vệ được đề xuất chống lại việc sửa đổi song song chỉ hiệu quả để phối hợp hành vi của các chương trình của riêng bạn, nhưng không phải để giới hạn một bản sao duy nhất của phiên bản demo, vì người dùng có thể chỉ cần xóa biến toàn cục. Để phục vụ mục đích này, biến toàn cục có thể được sử dụng theo cách khác - liên quan đến ID biểu đồ: một chương trình MQL chỉ hoạt động miễn là biến toàn cục do nó tạo ra chứa ID đồ họa của nó. Các cách khác để kiểm soát dữ liệu chung (bộ đếm và thông tin khác) được cung cấp bởi tài nguyên và cơ sở dữ liệu.