Complete Guide to Threading in Lucee
Complete Guide to Threading in Lucee
Threads allow you to execute code in parallel, improving performance for tasks like database queries, HTTP requests, or any long-running operations.
Lucee offers several approaches, from simple high-level functions to full manual control:
- Collection Parallel Processing - Simplest approach using
.each()with parallel flag - Function Listeners - Modern async syntax for function calls (Lucee 6.1+)
- Basic Thread Blocks - Simple cfthread usage for fire-and-forget operations
- Advanced Thread Management - Full control with thread joining, monitoring, and coordination
Collection Parallel Processing
The simplest way to execute code in parallel. Use .each() with the parallel flag and thread management is automatic.
Parallel Array Processing
// Process array items in parallel
items = ["item1", "item2", "item3", "item4", "item5"];
items.each(
function(value, index) {
// Simulate some work
sleep(1000);
systemOutput("Processed: #value# at #now()#", true);
},
true, // parallel execution
3 // maxThreads: limit to 3 concurrent threads
);
dump("All items processed");
Large Dataset with Thread Control
// Process large dataset with controlled thread count
largeDataset = [];
loop from=1 to=100 index="i" {
arrayAppend(largeDataset, "task_#i#");
}
largeDataset.each(
function(task, index) {
sleep(100);
systemOutput("Completed: #task#", true);
},
true, // parallel execution
10 // maxThreads: prevent system overload
);
dump("100 tasks completed with max 10 threads");
Parallel Struct Processing
// Process struct values in parallel with thread limit
userData = {
"user1": "john@example.com",
"user2": "jane@example.com",
"user3": "bob@example.com",
"user4": "alice@example.com",
"user5": "charlie@example.com"
};
userData.each(
function(email, userId) {
// Simulate sending email
sleep(500);
systemOutput("Email sent to #email# for user #userId#", true);
},
true, // parallel execution
2 // maxThreads: process max 2 emails simultaneously
);
Parallel Query Processing
// Process query rows in parallel
users = query(
'id': [1, 2, 3, 4, 5, 6, 7, 8],
'name': ['John', 'Jane', 'Bob', 'Alice', 'Charlie', 'David', 'Eve', 'Frank'],
'email': ['john@test.com', 'jane@test.com', 'bob@test.com', 'alice@test.com',
'charlie@test.com', 'david@test.com', 'eve@test.com', 'frank@test.com']
);
users.each(
function(row, rowNumber) {
// Process each user record
sleep(200);
systemOutput("Processed user: #row.name# (#row.email#)", true);
},
true, // parallel execution
4 // maxThreads: process max 4 users concurrently
);
Thread Limit Control
Collection functions (.each(), .every(), .filter(), .map(), .some()) all support maxThreads:
// Syntax: collection.function(function, parallel, maxThreads)
// Default maxThreads = 20 when parallel = true
tasks = [];
loop from=1 to=50 index="i" {
arrayAppend(tasks, "heavy_task_#i#");
}
tasks.each(
function(task) {
// CPU/memory intensive operation
sleep(randRange(100, 500));
systemOutput("Completed: #task#", true);
},
true, // parallel = true
5 // maxThreads = 5 (overrides default of 20)
);
// Without maxThreads limit, this would use default of 20 threads!
// With maxThreads=5, only 5 threads run at once, others queue
Parallel Functions
Arrays, structs, and queries support parallel execution:
.each()- Execute code for every element (side effects, logging, processing).filter()- Create new collection with elements matching criteria.map()- Transform every element and create new collection.every()- Test if all elements meet a condition (returns boolean).some()- Test if any element meets a condition (returns boolean)
// Array processing with .each()
items = ["item1", "item2", "item3", "item4", "item5"];
items.each(
function(value, index, array) {
sleep(200); // Simulate work
systemOutput("Processed: #value# at index #index#", true);
},
true, // parallel
3 // maxThreads
);
// Struct processing with .each()
userData = {
"john": "john@example.com",
"jane": "jane@example.com",
"bob": "bob@example.com",
"alice": "alice@example.com"
};
userData.each(
function(key, value, struct) {
sleep(300); // Simulate email sending
systemOutput("Email sent to #value# for user #key#", true);
},
true, // parallel
2 // maxThreads
);
// Query processing with .each()
employees = query(
'id': [1, 2, 3, 4],
'name': ['John', 'Jane', 'Bob', 'Alice'],
'department': ['IT', 'HR', 'Finance', 'Marketing']
);
employees.each(
function(row, rowNumber, query) {
sleep(150); // Simulate processing
systemOutput("Processed employee: #row.name# in #row.department#", true);
},
true, // parallel
2 // maxThreads
);
Parameters:
parallel(boolean) - Enable parallel executionmaxThreads(number, optional) - Maximum concurrent threads (alias:maxThreadCount)- When
parallel=false,maxThreadsis ignored
Function Listeners (Lucee 6.1+)
Modern promise-like syntax for async execution. See the function-listeners recipe for full details.
Simple Async Function Execution
function fetchUserData(userId) {
sleep(1000);
return {"id": userId, "name": "User #userId#"};
}
// Execute function asynchronously with listener
fetchUserData(123):function(result, error) {
request.userData = result;
};
dump("Function called asynchronously");
Joining Function Listener Threads
function processData(data) {
sleep(1000);
return data.len();
}
// Get thread name for joining
threadName = processData([1,2,3,4,5]):function(result, error) {
thread.result = result;
};
// Do other work...
dump("Processing started");
// Wait for completion
threadJoin(threadName);
dump("Result: #cfthread[threadName].result#");
Error Handling with Function Listeners
function riskyOperation() {
if (randRange(1,2) == 1) {
throw "Random failure occurred!";
}
return "Success!";
}
riskyOperation():{
onSuccess: function(result) {
systemOutput("Operation succeeded: #result#", true);
},
onFail: function(error) {
systemOutput("Operation failed: #error.message#", true);
}
};
Basic Thread Blocks
For fire-and-forget operations or when you need more control than collection processing.
Fire-and-Forget Operations
function logActivity(action) {
thread {
// This runs in background - doesn't block main execution
sleep(500); // Simulate slow logging operation
systemOutput("Logged: #action# at #now()#", true);
}
}
start = getTickCount();
logActivity("User login");
logActivity("Page view");
logActivity("Button click");
dump("Main execution completed in #getTickCount()-start#ms");
// Logging continues in background
Background Data Processing
function processInBackground(data) {
thread {
// Heavy processing that shouldn't block the user
sleep(3000);
thread.result = "Processed #data.len()# items";
thread.completedAt = now();
}
}
userData = ["item1", "item2", "item3"];
processInBackground(userData);
dump("Processing started in background");
Advanced Thread Management
Explicit thread management with joining, monitoring, and coordination.
Multiple Coordinated Threads
function fetchDataFromSource(sourceName, delay) {
thread name="fetch_#sourceName#" {
thread.startTime = now();
sleep(delay);
thread.data = "Data from #sourceName#";
thread.endTime = now();
thread.duration = dateDiff("s", thread.startTime, thread.endTime);
}
}
start = getTickCount();
// Start multiple data fetching operations
fetchDataFromSource("database", 1000);
fetchDataFromSource("api", 1500);
fetchDataFromSource("cache", 500);
fetchDataFromSource("file", 800);
// Show all threads status before joining
dump(var: cfthread, label: "Threads Status Before Join");
// Wait for all threads to complete
thread action="join" name=cfthread.keyList();
// Show results
dump(var: cfthread, label: "All Threads Completed");
dump("Total execution time: #getTickCount()-start#ms");
Thread Pool Management
function processWorkItem(workId) {
thread {
thread.workId = workId;
thread.startTime = now();
// Simulate varying work complexity
delay = randRange(500, 2000);
sleep(delay);
thread.result = "Work #workId# completed";
thread.endTime = now();
}
}
// Process multiple work items
workItems = [1,2,3,4,5,6,7,8,9,10];
start = getTickCount();
// Start all work items (Lucee manages thread pool automatically)
for (workId in workItems) {
processWorkItem(workId);
}
// Monitor progress
activeThreads = cfthread.keyArray();
dump("Started #activeThreads.len()# threads");
// Wait for completion in batches
batchSize = 3;
completedCount = 0;
while (completedCount < workItems.len()) {
sleep(100); // Check every 100ms
for (threadName in activeThreads) {
if (cfthread[threadName].status == "COMPLETED") {
completedCount++;
dump("Thread #threadName# completed: #cfthread[threadName].result#");
activeThreads.deleteAt(activeThreads.find(threadName));
}
}
}
dump("All work completed in #getTickCount()-start#ms");
Thread Communication and Shared Data
// Shared data structure for thread communication
request.sharedCounter = 0;
request.results = [];
function workerThread(threadId) {
thread name="worker_#threadId#" {
loop from=1 to=5 index="i" {
// Simulate work
sleep(randRange(100, 300));
// Update shared counter (be careful with race conditions in real apps)
request.sharedCounter++;
// Add result
arrayAppend(request.results, "Thread #threadId# completed task #i#");
}
thread.completed = true;
}
}
// Start worker threads
loop from=1 to=3 index="i" {
workerThread(i);
}
// Monitor progress
while (request.sharedCounter < 15) { // 3 threads × 5 tasks each
dump("Progress: #request.sharedCounter#/15 tasks completed");
sleep(200);
}
// Wait for all threads to finish
thread action="join" name="worker_1,worker_2,worker_3";
dump("Final results:");
dump(request.results);
dump("All threads completed");
Best Practices
.each()with parallel flag - simple collection processing- Function Listeners - modern async function calls with result handling
- Thread blocks - fire-and-forget background operations
- Advanced thread management - when you need precise control
Additional Resources
- function-listeners - Complete guide to async function execution
- Lucee Threads Video Tutorial