I’m having problems with variable scopes when calling thread http calls inside a remotely access cfscript component.
I’ve managed to do this successfully as a stand alone CFM page, but I’m having trouble when running this code in a CFC.
I’m calling the CFC from an app, so it needs to be accessible through a browser, something along the lines of
site.co.uk/page.cfc?method=search&returnFormat=json&strList=item1,item2,item3
This is the code that’s causing a problem
var cnt = listLen(strList);
for(var p = 1; p <= cnt; p++) {
var tmpText = listGetAt(strList,p);
thread name='t#p#' {
ArrayAppend(arr,getPrice(tmpText));
}
}
for(var p = 1; p <= cntPlates; p++) {
threadJoin('t#p#');
writeDump(t1);
}
When I writeDump t1, my first thread, it contains all the information about the thread, but the body of the thread is just an error: variable [TMPTEXT] doesn’t exist
Do I need to create tmpText inside the thread? Or put it in a t1 or thread scope? Or something altogether different.
This question requires two answers to address completely, and they have to do with the THREAD scope in ColdFusion.
ONE
On the surface, your code suffers from a simple case of scope access. [TMPTEXT] doesn’t exist is a result of the three spawned threads attempting to access a variable that was declared outside their scope, and not explicitly passed into the thread upon instantiation.
The solution is to explicitly pass variables into the thread scope in order to work on them, a la:
Here, a copy of the
tmpTextvariable that was declared outside the thread is passed in explicitly as'myText', and referred to within the context of the thread boundary by that new var name.As for why your code worked in a plain-old CFM, but isn’t within the confines of a CFC, it is because within the context of a CFM, threads have access to all shared scopes, VARIABLES, FORM, SESSION, etc…but writing against these scopes must be done with care (ie via CFLOCK), otherwise you introduce synchronization issues (deadlocks). The CFC is an encapsulated object, and of course, only has access to certain implicit scopes (VARIABLES, for example, is different for a CFC than it is for a CFM) and most other scopes must be accessed with their appropriate scope name.
TWO
The much larger issue regarding your question and code snippet has to do with your perception of how threads work on shared data. I apologize in advance if this gets preachy, but I have to play devil’s advocate based on the limited knowledge I have of your system, all the functions and what your intention was from the start.
Based on the code you’ve supplied:
I don’t see a declaration of the ‘arr’ Array, so my initial guess is that you plan to approach this multithreaded method by creating a storage container (the array) and then fork off multiple threads to perform work against the array, gaining an overall boost in performance as you parallelize your array updates.
The problem with this approach is that the threads do not know about each other’s work; there is a fundamental lack of synchronization between them. When I try to run the code as-is, it doesn’t know about the ‘arr’ array, because it wasn’t explicitly passed in (the exact same issue as the TMPTEXT problem addressed above). However simply passing the array in to the thread is not good enough, because it is copied by value into the thread scope, meaning any work you do on that array in the thread, is lost upon the exit of the thread.
What is the correct approach? To have each thread ultimately work on its own local version of the computed value from getPrice(), and to then join the threads, finishing by building the final array from those computed values from each thread:
Here, you’ll see that in the body of the
searchfunction, each thread creates its own local variable, ‘destVal’, which is a thread-safe storage facility for the computed returned of getPrice(). Those three threads are free to call the methods and the store the result independent of one another without affect a larger, shared var (which could introduce the synchronization issues).Then, once all of your threads are joined, a final loop is performed to build that ‘arr’ array, passing in the computed value from each of the specific threads (using the CFTHREAD scope to access each individual ‘destVal’ var.
NOTE: I’ve made this example code remote so that you can continue to call it as you requested in the question description (can be consumed remotely), but bear in mind that the WriteDump calls will be ignored when called in that fashion.
For more information on how to design and develop multithreaded applications, and to gain greater insight into how threads work on local/shared data (and the issues that arise), I recommend starting with Java Concurrency in Practice.
Source: Using Thread Data (Adobe LiveDocs)
— BONUS ANSWER —
If you want the correct code for the completed CFC to work on that shared array, it would look like this (READ CAVEAT BELOW):
CAVEAT: This implementation correctly writes to the
arrvariable that lives inside the CFC (and is thus shared) but safely locks the writes to array, so that forked threads do not cause a deadlock. The downside? This design would provide no additional improvement in performance at all; by locking every single write to the array, your performance is exactly the same as if you simply looped the array in a single thread, and processed each element on its own–since every single write (which may be faster from thread-to-thread) still must wait for the locks to be lifted to complete the write.— BONUS ANSWER #2 —
I slept on this, and came up with an alternate solution. Technically, there is one other way you could have multiple threads write to a shared array without locking the array to enforce your synchronization: Size the array ahead of time, and have each thread uniquely refer to a specific index in the array.. The downside to this is that you would have to
a) Know the size of the list you’re going to process ahead of time, or
b) Size it up to a maximum value, and when you iterate over the array, waste cycles checking to see if a value actually exists at that index.
Here’s an example using a):
In this methodology, the Array is already sized appropriately prior to the spawning of the individual threads that will do work on it. And, instead of adjusting the size of the array on the fly (via ArrayAppend) which has the potential to throw synchronicity out-of-whack, each thread works on a specific index of the array. Note that we pass that index into the thread scope via the iterated ‘p’ var. This is a thread-safe way to work on the shared array without having to lock access to it. Just be aware of the limitations noted above.