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 cùng một lúc, điều này có thể cải thiện hiệu suất 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úp duy trì hiệu suất và an toàn của các kịch bản Luau của bạn.
Mô hình lập trình song song
Mặc định, các kịch bản thực thi theo thứ 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 người chơi (NPC), xác minh raycasting và tạo thủ tục, thì thực thi tuần tự có thể gây 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ể phân chia nhiệm vụ thành nhiều kịch bản khác nhau và chạy chúng song song.Điều này làm cho 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 thêm lợi ích 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, nó không ảnh hưởng đến các mã khác đang chạy song song.Điều này giảm nguy cơ 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 thời gian trễ cho người dùng trên các máy chủ trực tiếp 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à đặt mọi thứ trong nhiều luồng.Ví dụ, xác minh raycasting bên máy chủ đặt mỗi người dùng cá nhân một sự kiện độc lập từ xa nhưng vẫn cần mã khởi động ban đầu để chạy song song để thay đổi các thuộc tính toàn cầu, đây là một mô hình phổ biến cho việc thực hiện song song.
Hầu hết các lần bạn cần kết hợp các giai đoạn seri và song song để đạt được kết quả mong muốn, bởi vì hiện tại có một số hoạt động không được hỗ trợ song song có thể ngăn chặn việc thực hiện các kịch bản, chẳng hạn như sửa đổi các thực thể trong các giai đoạn song song.Để biết thêm thông tin về cấp độ sử dụng API song song, xem an toàn luồng.
Chia mã thành nhiều luồng
Để chạy các kịch bản của trải nghiệm trong nhiều luồng cùng lúc, bạn cần phân chúng thành các khối logic dưới các người hành động khác nhau trong mô hình dữ liệu.Các diễn viên được đại diện bởi Actor các ví dụ thừa kế từ DataModel .Chúng hoạt động như các đơn vị cách ly thực thi phân phối tải trên nhiều lõi chạy cùng lúc.
Đặt các ví trí của diễn viên
Bạn có thể đặt diễn viên vào các thùng chứa thích hợp hoặc sử dụng chúng để thay thế các loại thực thể cấp cao nhất của 3D như NPC và raycasters, sau đó thêm các kịch bản tương ứng.

Đối với hầu hết các tình huống, bạn không nên đặt một diễn viên là 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 kịch bản được lồng vào nhiều diễn viên cho trường hợp sử dụng cụ thể của bạn, kịch bản được sở hữu bởi diễn viên tổ tiên gần nhất của nó.

Synchronize các luồng không đồng bộ
Mặc dù đặt các kịch bản dưới các diễn viên cho họ khả năng thực hiện song song, mặc định mã vẫn chạy trên một luồng duy nhất theo chuỗi, điều này không cải thiện hiệu lựcsuất chạy thời gian.Bạn cần gọi task.desynchronize(), một chức năng có thể trì hoãn việc thực thi của coroutine hiện tại để chạy mã song song và khởi động lại tại cơ hội thực thi song song tiếp theo.Để chuyển một kịch bản trở lại thực thi seri, gọi task.synchronize() .
Ngoài ra, bạn có thể sử dụng phương pháp RBXScriptSignal:ConnectParallel() khi bạn muốn lên lịch gọi lại tín hiệu để ngay lập tức chạy mã của bạn song song khi kích hoạt.Bạn không cần gọi task.desynchronize() bên trong cuộc gọi trả lời tín hiệu.
Huỷ đồng bộ một luồng
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Một số mã song song tính toán cập nhật trạng thái
task.synchronize()
... -- Một số mã serial thay đổi trạng thái của các instance
end)
Các kịch bản thuộc cùng một diễn viên luôn luôn thực hiện theo thứ tự 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 kịch bản hành vi được bật song song cho NPC của bạn trong một diễn viên, chúng vẫn chạy theo chùng 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 người chạy song song trên luồng riêng của mình.Để biết thêm thông tin, xem Tốt nhất thực hành.


An toàn luồng
Trong quá trình thực hiện song song, bạn có thể truy cập hầu hết các ví dụ của cấu trúc DataModel hệ thống như thường lệ, nhưng một số thuộc tính và chức năng API không an toàn để đọc hoặc viết.Nếu bạn sử dụng chúng trong mã song song của bạn, Roblox Engine có thể tự động phát hiện và ngăn chặn các truy cập này xảy ra.
Các thành viên API có một cấp độ an toàn luồng cho thấy liệu và cách bạn có thể sử dụng chúng trong mã song song của bạn, như bảng dưới đây cho thấy:
Cấp an cấp | Đối với các thuộc tính | Đối với chức năng |
---|---|---|
Không an toàn | Không thể đọc hoặc viết song song. | Không thể gọi song song. |
Đọc song song | Có thể đọc nhưng không được viết song song. | N/A |
Hộp an toàn địa phương | Có thể được sử dụng trong cùng một Nhân vật; có thể được đọc nhưng không được viết bởi người khác Actors song song. | Có thể được gọi trong cùng một Actor; không thể được gọi bởi các Actors khác song song. |
An toàn | Có thể đọc và viết. | Có thể được gọi. |
Bạn có thể tìm thẻ an toàn luồng cho thành viên API trên tham chiếu 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 tính năng 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 với các diễn viên khác nhưng không thay đổi trạng thái của các diễn viên khác.
Giao tiếp giữa các luồng
Trong bối cảnh đa luồng, bạn vẫn có thể cho phép các kịch bản 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 các hoạt động.Động cơ hỗ trợ các cơ chế sau đây cho giao tiếp giữa các luồng:
- Gửi tin nhắn diễn viên API để gửi tin nhắn cho một diễn viên bằng cách sử dụng các tập lệnh.
- Cấu trúc dữ liệu bảng chia sẻ để chia sẻ hiệu quả một lượng lớn dữ liệu giữa nhiều người tham gia trong một trạng thái chia sẻ.
- Giao tiếp mô hình dữ liệu trực tiếp cho giao tiếp đơn giản với hạn chế.
Bạn có thể hỗ trợ nhiều cơ chế để đáp ứng nhu cầu giao tiếp song song của bạn.Ví dụ, bạn có thể gửi một bảng chia sẻ thông qua API Gửi tin nhắn Nhân vật.
Gửi tin nhắn của diễn viên
API gửi tin nhắn nhân vật cho phép một kịch bản, trong một bối cảnh serial hoặc song song, gửi dữ liệu đến một nhân vật trong cùng một 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 bị khóa 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 định nghĩa một chủ đề để phân loại tin nhắn.Mỗi tin nhắn chỉ có thể được gửi cho một diễn viên duy nhất, nhưng diễn viên đó có thể nội bộ có nhiều cuộc gọi lại được gắn vào một tin nhắn.Chỉ các kịch bản là con cháu của một diễn viên có thể nhận được tin nhắn.
API có các phương pháp sau:
- Actor:SendMessage() để gửi tin nhắn cho một diễn viên.
- Actor:BindToMessage() để gắn một cuộc gọi trả lại Luau vào một tin nhắn với chủ đề được định trong một ngữ cảnh seri.
- Actor:BindToMessageParallel() để gắn một cuộc gọi trả lại Luau vào một tin nhắn với chủ đề được định trong một ngữ cảnh song song.
Ví dụ sau đây cho thấy cách sử dụng Actor:SendMessage() để định nghĩa một chủ đề và gửi một tin nhắn ở phía người gửi:
Ví dụ người gửi tin nhắn
local Workspace = game:GetService("Workspace")-- Gửi hai tin nhắn đến diễn viên lao động với chủ đề "Chào hỏi"local workerActor = Workspace.WorkerActorworkerActor:SendMessage("Greeting", "Hello World!")workerActor:SendMessage("Greeting", "Welcome")print("Sent messages")
Ví dụ sau đây cho thấy cách sử dụng Actor:BindToMessageParallel() để gắn một callback cho một chủ đề cụ thể trong một ngữ cảnh song song ở phía người nhận:
Nhận xét ví dụ Nhận xét Nhận xét
-- Nhận diễn viên mà kịch bản này được cha mẹ
local actor = script:GetActor()
-- Gắn một callback cho chủ đề tin nhắn "Chào hỏi"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
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 kịch bản chạy dưới nhiều diễn viên.Nó 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ẻ cho một diễn viên khác không tạo bản sao dữ liệu.Thay vào đó, bảng chia sẻ cho phép cập nhật an toàn và nguyên tử bởi nhiều kịch bản cùng một lúc.Mỗi cập nhật cho một bảng chia sẻ bởi một diễn viên là ngay lập tức hiển thị cho tất cả các diễn viên.Bảng chia sẻ cũng có thể được sao chép trong một quá trình hiệu quả về tài nguyên sử dụng chia sẻ cấu trúc thay vì sao chép dữ liệu cơ sở.
Giao tiếp mô hình dữ liệu trực tiếp
Bạn cũng có thể thúc đẩy 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ể viết 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 kịch bản chạy song song thường không thể viết vào mô hình dữ liệu.Vì vậy, trực tiếp sử dụng mô hình dữ liệu cho giao tiếp có những hạn chế và có thể buộc các kịch bản phải đồng bộ thường xuyên, có thể ảnh hưởng đến hiệu suất của các kịch bản của bạn.
Ví dụ
Xác minh phát bắn tia bên máy chủ
Đối với một trải nghiệm chiến đấu và chiến đấu, bạn cần bật phát bắn tia cho vũ khí của người dùng.Với khách hàng mô phỏng vũ khí để đạt được độ trễ tốt, máy chủ phải xác nhận trúng, điều này bao gồm thực hiện raycasts và một số kỹ thuật heuristic mà tính tốc độ nhân vật mong đợi và xem xét hành vi trong quá khứ.
Thay vì sử dụng một kịch bản trung tâm hóa duy nhất kết nối với một sự kiện từ xa mà khách hàng sử dụng để truyền thông thông tin hit, bạn có thể chạy mỗi quá trình xác minh hit trên phía máy 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 biệt.
Tập lệnh bên máy chủ chạy dưới sự kiện remote Actor của nhân vật đó kết nối với sự kiện remote này bằng kết nối song song để chạy logic liên quan để xác nhận lượt truy cập.Nếu logic tìm thấy xác nhận về một lần trúng, sát thương sẽ bị trừ, liên quan đến việc thay đổi các thuộc tính, vì vậy nó chạy song song ban đầu.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Tạo sự kiện remote mới và gán nó cho công cụ
remoteEvent.Name = "RemoteMouseEvent" -- Đổi tên nó để kịp lập trình địa phương có thể tìm kiếm nó
remoteEvent.Parent = tool
local remoteEventConnection -- Tạo một tham chiếu cho kết nối sự kiện từ xa
-- Chức năng lắng nghe sự kiện từ xa
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Thực hiện mã cài đặt trong serial
local character = player.Character
-- Bỏ qua nhân vật của người dùng trong lúc phát bắn tia
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLEL: Thực hiện raycast song song
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Sử dụng để mở rộng tia một chút vì vị trí nhấp có thể bị chênh lệch một chút so vớ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")
-- SERIAL: Mã dưới đây sửa lỗi trạng thái bên ngoài của diễn viên
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Làm cho nổ không 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 nó sẽ dẫn đến hai vụ nổ cùng một lúc thay vì một
-- Các kiểm tra đôi sau đây xác định rằng thực thi phải đến phần này trước tiên
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 trong chuỗi ban đầu vì một số mã cài đặt không thể chạy song song
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Tạo địa hình thủ tục bên 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ấp đầy thế giới theo cách năng động.Tạo trình tự thường tạo 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à lấp đầy voxel.Chạy mã tạo song song có thể cải thiện hiệu quả của quá trình.Ví dụ mã sau đây phục vụ như một ví dụ.
-- Thực thi song song yêu cầu sử dụng các diễn viên
-- Tập lệnh này tự nhân bản; bản gốc khởi động quá trình, trong khi các nhân bản hành độ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
-- Làm cha tất cả các diễn viên dưới chính mình
for _, actor in workers do
actor.Parent = script
end
-- Hướng dẫn các diễn viên tạo ra đị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 kịch bản ban đầu; 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
-- Gắn callback để được gọi trong bối 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 serial
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
Tốt nhất các thực hành
Để áp dụng tối đa lợi ích của lập trình song song, tham khảo các tốt nhất sau khi thêm mã Luau của bạn:
Tránh tính toán dài — Ngay cả trong song song, tính toán dài có thể chặn thực thi của các kịch bản khác và gây 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 và không thể thay đổi.
Sử dụng số lượng diễn viên phải — Để có hiệu hiệu lựctốt nhất, hãy sử dụng thêm Actors .Ngay cả khi thiết bị có ít hơn các lõi hơn Actors, độ granular 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 Actors nhất có thể.Bạn vẫn nên chia mã thành Actors dựa trên các đơn vị logic thay vì phá vỡ mã với logic kết nối với các đơn vị logic khác Actors .Ví dụ, nếu bạn muốn bật kích hoạt xác minh raycasting song song, thì hợp lý để sử dụng 64 Actors và nhiều hơn thay vì chỉ 4, ngay cả khi bạn đang mục tiêu 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 cơ sở.Tuy nhiên, bạn cũng không nên sử dụng quá nhiều Actors, rất khó duy trì.