Với mô hình lập trình Luau Song song, bạn có thể chạy mã trên nhiều luồng đồng thời, điều này có thể cải thiện hiệu suất của trải nghiệm của bạn. Khi bạn mở rộng trải nghiệm của mình với nhiều nội dung hơn, bạn có thể áp dụng mô hình này để giữ hiệu suất và an toàn cho các script Luau của bạn.
Mô hình lập trình song song
Theo mặc định, các script được thực thi tuần tự. Nếu trải nghiệm của bạn có logic hoặc nội dung phức tạp, chẳng hạn như nhân vật không phải người chơi (NPC), xác thực raycasting và tạo ra quy trình, thì việc thực thi tuần tự có thể gây ra độ trễ cho người dùng của bạn. Với mô hình lập trình song song, bạn có thể chia nhỏ nhiệm vụ thành nhiều script và chạy chúng một cách song song. Điều này giúp mã trải nghiệm của bạn chạy nhanh hơn, cải thiện trải nghiệm người dùng.
Mô hình lập trình song song cũng mang lại những lợi ích về an toàn cho mã của bạn. Bằng cách chia mã thành nhiều luồng, khi bạn chỉnh sửa mã trong một luồng, điều đó không ảnh hưởng đến mã khác đang chạy song song. Điều này giảm thiểu rủi ro việc một lỗi trong mã của bạn làm hỏng toàn bộ trải nghiệm và giảm thiểu độ trễ cho người dùng trên các máy chủ đang hoạt động khi bạn đẩy một bản cập nhật.
Áp dụng mô hình lập trình song song không có nghĩa là đưa mọi thứ vào nhiều luồng. Ví dụ, xác thực raycasting phía máy chủ gán cho mỗi người dùng một sự kiện từ xa theo cách song song nhưng vẫn yêu cầu mã ban đầu chạy tuần tự để thay đổi các thuộc tính toàn cầu, đây là một mô hình thường thấy trong thực thi song song.
Hầu hết các lần bạn cần kết hợp các giai đoạn tuần tự và song song để đạt được đầu ra mong muốn, vì hiện tại có một số thao tác không được hỗ trợ trong môi trường song song có thể ngăn cản các script chạy, chẳng hạn như sửa đổi các instance trong các giai đoạn song song. Để biết thêm thông tin về mức độ sử dụng của các API trong môi trường song song, hãy xem an toàn luồng.
Chia nhỏ mã thành nhiều luồng
Để chạy các script của trải nghiệm của bạn trên nhiều luồng đồng thời, bạn cần chia chúng thành các khối logic dưới các diễn viên khác nhau trong mô hình dữ liệu. Các diễn viên được đại diện bởi các instance Actor kế thừa từ DataModel. Chúng hoạt động như các đơn vị cách ly thực thi, phân phối tải giữa nhiều lõi chạy đồng thời.
Đặt instance diễn viên
Bạn có thể đặt các diễn viên vào các container phù hợp hoặc sử dụng chúng để thay thế các loại instance cấp cao nhất của các thực thể 3D của bạn như NPC và raycaster, sau đó thêm các script tương ứng.

Trong hầu hết các tình huống, bạn không nên đặt một diễn viên làm con của một diễn viên khác trong mô hình dữ liệu. Tuy nhiên, nếu bạn quyết định đặt một script lồng bên trong nhiều diễn viên cho trường hợp sử dụng cụ thể của bạn, script đó thuộc về diễn viên tổ tiên gần nhất của nó.

Phi đồng bộ hóa luồng
Mặc dù đặt script dưới diễn viên cho phép chúng khả năng thực thi song song, theo mặc định mã vẫn chạy trên một luồng duy nhất một cách tuần tự, điều này không cải thiện hiệu suất thời gian chạy. Bạn cần gọi task.desynchronize(), một hàm có thể nhường mà tạm dừng việc thực thi coroutine hiện tại để chạy mã song song và tiếp tục nó tại cơ hội thực thi song song tiếp theo. Để chuyển một script trở lại chế độ thực thi tuần tự, gọi task.synchronize().
Ngoài ra, bạn có thể sử dụng phương thức RBXScriptSignal:ConnectParallel() khi bạn muốn lên lịch một callback tín hiệu để chạy mã của bạn ngay lập tức trong song song khi có kích hoạt. Bạn không cần gọi task.desynchronize() bên trong callback tín hiệu.
Phi đồng bộ hóa một Luồng
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Một số mã song song tính toán một cập nhật trạng thái
task.synchronize()
... -- Một số mã tuần tự thay đổi trạng thái của các instance
end)
Các script thuộc cùng một diễn viên luôn thực thi tuần tự tương ứng với nhau, vì vậy bạn cần nhiều diễn viên. Ví dụ, nếu bạn đặt tất cả các script hành vi được bật song song cho NPC của bạn vào một diễn viên, chúng vẫn chạy tuần tự trên một luồng duy nhất, nhưng nếu bạn có nhiều diễn viên cho các logic NPC khác nhau, mỗi diễn viên sẽ chạy song song trên luồng riêng của nó. Để biết thêm thông tin, hãy xem Chính sách tốt nhất.


An toàn luồng
Trong quá trình thực thi song song, bạn có thể truy cập hầu hết các instance của phân cấp DataModel như thường lệ, nhưng một số thuộc tính và hàm API không an toàn để đọc hoặc ghi. Nếu bạn sử dụng chúng trong mã song song của mình, engine Roblox có thể tự động phát hiện và ngăn chặn những truy cập này xảy ra.
Các thành phần API có mức độ an toàn luồng cho biết liệu và làm thế nào bạn có thể sử dụng chúng trong mã song song của bạn, như bảng sau đây cho thấy:
| Mức độ an toàn | Đối với thuộc tính | Đối với hàm |
|---|---|---|
| Không an toàn | Không thể đọc hoặc ghi trong song song. | Không thể gọi trong song song. |
| Đọc song song | Có thể đọc nhưng không thể ghi trong song song. | Không áp dụng |
| An toàn cục bộ | Có thể được sử dụng trong cùng một Diễn viên; có thể đọc nhưng không thể ghi bởi các Diễn viên khác trong song song. | Có thể gọi trong cùng một Diễn viên; không thể gọi bởi các Diễn viên khác trong song song. |
| An toàn | Có thể đọc và ghi. | Có thể gọi. |
Bạn có thể tìm thấy các thẻ an toàn luồng cho các thành phần API trong tham khảo API. Khi sử dụng chúng, bạn cũng nên xem xét cách các cuộc gọi API hoặc thay đổi thuộc tính có thể tương tác giữa các luồng song song. Thông thường, nó an toàn cho nhiều diễn viên đọc cùng một dữ liệu như các diễn viên khác nhưng không sửa đổi trạng thái của các diễn viên khác.
Giao tiếp qua luồng
Dưới ngữ cảnh đa luồng, bạn vẫn có thể cho phép các script trong các diễn viên khác nhau giao tiếp với nhau để trao đổi dữ liệu, phối hợp nhiệm vụ và đồng bộ hóa hoạt động. Engine hỗ trợ các cơ chế sau cho giao tiếp qua luồng:
- API nhắn tin diễn viên để gửi tin nhắn đến một diễn viên thông qua các script.
- Cấu trúc dữ liệu Bảng chia sẻ để chia sẻ hiệu quả nhiều dữ liệu giữa nhiều diễn viên trên một trạng thái chia sẻ.
- Giao tiếp trực tiếp với mô hình dữ liệu cho giao tiếp đơn giản với các hạn chế.
Bạn có thể hỗ trợ nhiều cơ chế để đáp ứng nhu cầu giao tiếp qua luồng của bạn. Ví dụ, bạn có thể gửi bảng chia sẻ thông qua API Nhắn tin Diễn viên.
Nhắn tin diễn viên
API nhắn tin diễn viên cho phép một script, ở trong ngữ cảnh tuần tự hoặc song song, gửi dữ liệu đến một diễn viên trong cùng mô hình dữ liệu. Giao tiếp thông qua API này là không đồng bộ, trong đó người gửi không chặn cho đến khi người nhận nhận được tin nhắn.
Khi gửi tin nhắn bằng cách sử dụng API này, bạn cần xác định một chủ đề để phân loại tin nhắn. Mỗi tin nhắn chỉ có thể được gửi đến một diễn viên duy nhất, nhưng diễn viên đó có thể nội bộ có nhiều callback được liên kết với một tin nhắn. Chỉ các script là con cháu của một diễn viên mới có thể nhận tin nhắn.
API có các phương thức sau:
- Actor:SendMessage() để gửi một tin nhắn đến một diễn viên.
- Actor:BindToMessage() để liên kết một callback Luau với một tin nhắn có chủ đề xác định trong ngữ cảnh tuần tự.
- Actor:BindToMessageParallel() để liên kết một callback Luau với một tin nhắn có chủ đề xác định trong ngữ cảnh song song.
Ví dụ sau cho thấy cách sử dụng Actor:SendMessage() để định nghĩa một chủ đề và gửi một tin nhắn ở đầu gửi:
Ví dụ Gửi Tin nhắnlocal Workspace = game:GetService("Workspace")-- Gửi hai tin nhắn đến diễn viên công nhân với chủ đề là "Greeting"local workerActor = Workspace.WorkerActorworkerActor:SendMessage("Greeting", "Hello World!")workerActor:SendMessage("Greeting", "Welcome")print("Đã gửi tin nhắn")
Ví dụ sau cho thấy cách sử dụng Actor:BindToMessageParallel() để liên kết một callback cho một chủ đề nhất định trong ngữ cảnh song song ở phía người nhận:
Ví dụ Nhận Tin nhắn
-- Lấy diễn viên mà script này được cha mẹ
local actor = script:GetActor()
-- Liên kết một callback cho chủ đề tin nhắn "Greeting"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Đã liên kết với các tin nhắn")
Bảng chia sẻ
SharedTable là một cấu trúc dữ liệu giống như bảng có thể truy cập từ các script chạy dưới nhiều diễn viên. Nó rất hữu ích cho các tình huống liên quan đến một lượng lớn dữ liệu và yêu cầu một trạng thái chung giữa nhiều luồng. Ví dụ, khi nhiều diễn viên làm việc trên một trạng thái thế giới chung không được lưu trữ trong mô hình dữ liệu.
Gửi một bảng chia sẻ đến một diễn viên khác không tạo ra bản sao của dữ liệu. Thay vào đó, các bảng chia sẻ cho phép cập nhật an toàn và nguyên tử bởi nhiều script đồng thời. Mỗi cập nhật đến một bảng chia sẻ bởi một diễn viên sẽ ngay lập tức có sẵn cho tất cả các diễn viên. Các bảng chia sẻ cũng có thể được nhân bản trong một quá trình tiết kiệm tài nguyên mà sử dụng chia sẻ cấu trúc thay vì sao chép dữ liệu cơ bản.
Giao tiếp trực tiếp với mô hình dữ liệu
Bạn cũng có thể tạo điều kiện giao tiếp giữa nhiều luồng trực tiếp bằng cách sử dụng mô hình dữ liệu, trong đó các diễn viên khác nhau có thể ghi và sau đó đọc các thuộc tính hoặc thuộc tính. Tuy nhiên, để duy trì an toàn luồng, các script chạy song song nói chung không thể ghi vào mô hình dữ liệu. Vì vậy, việc sử dụng trực tiếp mô hình dữ liệu để giao tiếp đi kèm với các hạn chế và có thể buộc các script phải đồng bộ hóa thường xuyên, điều này có thể ảnh hưởng đến hiệu suất của các script của bạn.
Ví dụ
Xác thực raycasting phía máy chủ
Đối với một trải nghiệm chiến đấu và trận chiến, bạn cần bật raycasting cho vũ khí của người dùng. Với client mô phỏng các vũ khí để đạt được độ trễ tốt, máy chủ phải xác nhận cú đánh, điều này liên quan đến việc thực hiện raycast và một số mức độ suy luận xác định vận tốc nhân vật kỳ vọng và xem xét hành vi trong quá khứ.
Thay vì sử dụng một script tập trung duy nhất mà kết nối đến một sự kiện từ xa mà các client sử dụng để giao tiếp thông tin cú đánh, bạn có thể chạy mỗi quy trình xác thực cú đánh ở phía máy chủ một cách song song với mỗi nhân vật người dùng có một sự kiện từ xa riêng.
Script phía máy chủ chạy dưới Actor của nhân vật đó kết nối với sự kiện từ xa này bằng cách sử dụng một kết nối song song để chạy logic liên quan đến việc xác nhận cú đánh. Nếu logic tìm thấy một xác nhận cú đánh, sát thương sẽ được trừ đi, điều này liên quan đến việc thay đổi các thuộc tính, vì vậy nó chạy tuần tự ban đầu.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Tạo sự kiện từ xa mới và đưa nó vào công cụ
remoteEvent.Name = "RemoteMouseEvent" -- Đổi tên nó để script cục bộ có thể tìm thấy
remoteEvent.Parent = tool
local remoteEventConnection -- Tạo một tham chiếu cho kết nối sự kiện từ xa
-- Hàm lắng nghe sự kiện từ xa
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- TUẦN TỰ: Thực hiện mã thiết lập theo tuần tự
local character = player.Character
-- Bỏ qua nhân vật của người dùng khi raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- SONG SONG: Thực hiện raycast trong song song
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Dùng để kéo dài ray một chút vì vị trí nhấp chuột có thể bị lệch một chút khỏi đối tượng
local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
local raycastResult = Workspace:Raycast(origin, lookDirection, params)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart and hitPart.Name == "block" then
local explosion = Instance.new("Explosion")
-- TUẦN TỰ: Mã bên dưới thay đổi trạng thái bên ngoài diễn viên
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Làm cho vụ nổ không gây chết người
explosion.Position = clickLocation.Position
-- Nhiều diễn viên có thể nhận được cùng một phần trong một raycast và quyết định phá hủy nó
-- Điều này hoàn toàn an toàn nhưng sẽ dẫn đến hai vụ nổ cùng một lúc thay vì một
-- Kiểm tra sau đây đảm bảo rằng việc thực thi đã đến phần này trước
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Phá hủy nó
end
end
end
end
-- Kết nối tín hiệu theo tuần tự ban đầu vì một số mã thiết lập không thể chạy trong song song
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Tạo địa hình quy trình phía máy chủ
Để tạo ra một thế giới rộng lớn cho trải nghiệm của bạn, bạn có thể làm phong phú thế giới một cách động. Tạo ra quy trình thường tạo ra các khối địa hình độc lập, với máy phát thực hiện các tính toán tương đối phức tạp cho việc đặt đối tượng, sử dụng vật liệu và điền voxel. Chạy mã tạo ra trong song song có thể nâng cao hiệu quả của quá trình. Mẫu mã sau đây phục vụ như một ví dụ.
-- Việc thực thi song song yêu cầu sử dụng diễn viên
-- Script này sao chép chính nó; bản gốc khởi động quá trình, trong khi các bản sao hoạt động như công nhân
local Workspace = game:GetService("Workspace")
local actor = script:GetActor()
if actor == nil then
local workers = {}
for i = 1, 32 do
local actor = Instance.new("Actor")
script:Clone().Parent = actor
table.insert(workers, actor)
end
-- Đưa tất cả các diễn viên dưới chính nó
for _, actor in workers do
actor.Parent = script
end
-- Hướng dẫn các diễn viên tạo địa hình bằng cách gửi tin nhắn
-- Trong ví dụ này, các diễn viên được chọn ngẫu nhiên
task.defer(function()
local rand = Random.new()
local seed = rand:NextNumber()
local sz = 10
for x = -sz, sz do
for y = -sz, sz do
for z = -sz, sz do
workers[rand:NextInteger(1, #workers)]:SendMessage("GenerateChunk", x, y, z, seed)
end
end
end
end)
-- Thoát khỏi script gốc; phần còn lại của mã chạy trong mỗi diễn viên
return
end
function makeNdArray(numDim, size, elemValue)
if numDim == 0 then
return elemValue
end
local result = {}
for i = 1, size do
result[i] = makeNdArray(numDim - 1, size, elemValue)
end
return result
end
function generateVoxelsWithSeed(xd, yd, zd, seed)
local matEnums = {Enum.Material.CrackedLava, Enum.Material.Basalt, Enum.Material.Asphalt}
local materials = makeNdArray(3, 4, Enum.Material.CrackedLava)
local occupancy = makeNdArray(3, 4, 1)
local rand = Random.new()
for x = 0, 3 do
for y = 0, 3 do
for z = 0, 3 do
occupancy[x + 1][y + 1][z + 1] = math.noise(xd + 0.25 * x, yd + 0.25 * y, zd + 0.25 * z)
materials[x + 1][y + 1][z + 1] = matEnums[rand:NextInteger(1, #matEnums)]
end
end
end
return {materials = materials, occupancy = occupancy}
end
-- Liên kết callback sẽ được gọi trong ngữ cảnh thực thi song song
actor:BindToMessageParallel("GenerateChunk", function(x, y, z, seed)
local voxels = generateVoxelsWithSeed(x, y, z, seed)
local corner = Vector3.new(x * 16, y * 16, z * 16)
-- Hiện tại, WriteVoxels() phải được gọi trong giai đoạn tuần tự
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
Chính sách tốt nhất
Để áp dụng tối đa lợi ích của lập trình song song, hãy tham khảo các thực hành tốt nhất sau đây khi thêm mã Luau của bạn:
Tránh Tính toán Dài — Ngay cả trong song song, các tính toán dài có thể chặn việc thực thi của các script khác và gây ra độ trễ. Tránh sử dụng lập trình song song để xử lý một lượng lớn các tính toán dài, không nhường.

Sử dụng Số lượng Diễn viên Đúng — Để có được hiệu suất tốt nhất, hãy sử dụng nhiều Diễn viên. Ngay cả khi thiết bị có ít lõi hơn Diễn viên, tính chi tiết cho phép cân bằng tải hiệu quả hơn giữa các lõi.

Điều này không có nghĩa là bạn nên sử dụng nhiều Diễn viên như có thể. Bạn vẫn nên chia mã thành các Diễn viên dựa trên các đơn vị logic thay vì tách mã với logic kết nối sang các Diễn viên khác nhau. Ví dụ, nếu bạn muốn bật xác thực raycasting trong song song, hợp lý để sử dụng 64 Diễn viên và nhiều hơn thay vì chỉ 4, ngay cả khi bạn đang nhắm đến các hệ thống 4 lõi. Điều này có giá trị cho khả năng mở rộng của hệ thống và cho phép nó phân phối công việc dựa trên khả năng của phần cứng nền tảng. Tuy nhiên, bạn cũng không nên sử dụng quá nhiều Diễn viên, điều này rất khó duy trì.