|
Post by troycheek on Jul 4, 2015 5:32:26 GMT 1
I've only recently realized that GB32 can create threads to use in parallel processing. For those who don't know, the command is threadHandle = CreateThread(0, 0, ProcAddr(ThreadTwo), 0, 0, V:threadID) where threadHandle and threadID are 32 bit integers and ThreadTwo is the name of the procedure containing the thread you want to create. CreateThread isn't in the help file as a GFA BASIC 32 command because it's really a call to the Win32 API. Anyway, I've been having trouble monitoring and controlling the threads. I tried using some global variables to exchange information between the threads as shown in an example program I found. t2status$="busy", t2kill?=true, that sort of thing. That worked most of the time, but about 1 out of 20 times the program crashes with an Access Violation or Array Out of Bounds. I swear that my program works fine when not using threads, and I've since read that the program can crash if one thread is reading a variable while another is changing it. There exists a Win32 API command PostThreadMessage which can send a message (an integer value) to a particular thread, and that thread can read incoming messages with PeekMessage or GetMessage. These do appear to exist in GB32 but I haven't quite figured out how to use them yet. PeekMessage and GetMessage don't return the message, but rather a data structure (called Type in GB32) containing the message, what thread it was sent from, system time at the time it was sent, etc. Too much information for me to figure out. Plus, they don't seem to actually "hear" the posted thread message. Or I'm doing something wrong. As a debugging measure, I've made sure that each thread I create has its own window from which it can give me a running account of what it's doing. If I don't need to see the window, I still open it but use the Hidden option. There exist commands to send messages from one window to another through the Windows message queue, SendMessage and PostMessage. The help file also suggests reading these messages with PeekMessage and GetMessage, so no help there. However, messages sent from one window to another are also called events, and I did find some interesting things in the help file about events. _Mess, _hWnd, _lParam, _wParam, _WinID are simple versions of that data structure I was complaining about, filled with the appropriate values after every GetEvent or PeekEvent (also DoEvents or Sleep). In thread 1 running in window 1, I can use ~PostMessage(Win_2.hWnd, 9000, 0, 0) to post the message 9000 to thread 2 over in window 2. Meanwhile, in thread 2, I check the messages every so often with DoEvents:msg%=_Mess which sticks the latest message in the variable msg%, in this case the number 9000. After thread 2 reacts appropriately, it can post a message back in a similar fashion. SendMessage works similarly, except it waits for a response back from the target window, which isn't always what we want when we're multitasking. Also, I haven't figured out how to send a response yet. My problem is... That I'm probably using these commands in ways that Frank never intended, but that's besides the point. My problem is that DoEvents:msg%=_Mess returns only the latest message in the queue. If multiple messages are sent from multiple threads, or if Windows itself sends some messages because the mouse is moving over the window, then the particular message the thread is waiting for gets lost in the shuffle. I don't know how or if it is even possible to grab the entire message queue to search it for earlier messages. Now, there is Sub Form_Message(hWnd%, Mess%, wParam%, lParam%) which if I use a Form of, say, Win_2, seems custom designed to deal with posted messages to the windows used by my threads. But I haven't quite figured out how to use it. Also, I'd be dealing with messages outside of the procedures that are my threads, and to get information to the threads is the question we're working on. The solution I've come up with so far is the hammer method. I keep posting the message over and over until I get an expected response. From thread 1 running in window 1 I use Repeat : ~PostMessage(Win_2.hWnd, 9000, 0, 0) : Sleep : msg% = _Mess : Until msg% = 9001 Or msg% = 9002 So, if thread 2 in window 2 doesn't hear the message the first (or 20th) time, or if thread 1 in window 1 doesn't hear the response the first time, thread 1 will keep trying. This has worked for me so far, but I've got the sneaking suspicion that I'm doing this wrong and that there's an easier solution that I'm not seeing. Second problem is the values of the messages I'm posting between windows. Windows has values of its own that it sends around, like the constant WM_CLOSE (16) which I think stands for Windows Message to Close this window or this window has been closed. I'm not sure what all these values are, but they appear to be relatively small positive integers. Negative numbers don't seem to work. The highest I've seen is 1024. I've been using values over 9000 and also not powers of 2. I'm still afraid some errant mouse click or resize event is going to generate exactly the value I've chosen for "dump all data you've been calculating for the last hour." I've been thinking something along these lines for thread communication: THREAD_STATUS | 9000 | request status report
| THREAD_BUSY | 9001 | thread working
| THREAD_READY | 9002 | not working
| THREAD_EXIT | 9003 | thread has exited
| THREAD_KILL
| 9999 | order thread to exit
|
Plus maybe some commands to pause and resume work. Now, I know there exist API calls like TerminateThread, SuspendThread, and ResumeThread which can be used to control thread execution, but what I've read implies that you're much better off letting the threads handle these things themselves gracefully. The thread might be in the middle of writing some data to disk when you terminate it, for example, leaving the file unfinished.
|
|
|
Post by dragonjim on Jul 4, 2015 13:00:26 GMT 1
There's a lot of questions in your last post, and I hope the example below will answer most of them...
Type MSG hWnd As Handle - Int32 message, wParam, lParam, time pt As POINT EndType
Type POINT - Int32 x, y EndType
Local Int32 n, thread2Handle, thread2Id : Local recd?, t1msg As MSG // Open Window 1 for output for Thread 1 (the current thread) OpenW 1, 10, 10, 200, 200 : Win_1.Caption = "Thread One" // Create Thread 2 thread2Handle = CreateThread(0, 0, ProcAddr(ThreadTwo), 4, 0, V:thread2Id) // Count to 300,000; DoEvents prevents Window 1 (which loses focus to Window 2) from becoing inactive // A dummy ~PostThreadMessage() sent as GetMessage waits for a message For n = 1 To 300000 : Text 1, 1, n : DoEvents : ~PostThreadMessage(thread2Id, WM_APP, 0, 0) : Next n // Send a message to Thread 2 with the number reached and a return thread Id address for this thread ~PostThreadMessage(thread2Id, WM_APP + 1, n, GetCurrentThreadId()) // Wait for a response with Thread 2's count; recd? is used as this GetMessage() loop will only end otherwise with a WM_QUIT message While GetMessage(t1msg, 0, 0, 0) And Not recd? If t1msg.message = WM_APP + 1 // Message received Text 1, 15, "Thread 2 counted to" & t1msg.wParam ~DispatchMessage(t1msg) : recd? = True EndIf Wend Text 1, 29, "Closing Thread Two..." // Close Thread 2 - you can use TerminateThread(Thread2Handle,0) here as well ~PostThreadMessage(thread2Id, WM_QUIT, 0, 0) Text 1, 43, "Close window to end" Do : Sleep : Until Win_1 Is Nothing
Procedure ThreadTwo(np%) Local ct%, n%, np2%, t2Msg As MSG // Open Window 2 for output for Thread 2 (this thread) OpenW 2, 220, 10, 200, 200 : Win_2.Caption = "Thread Two" // Put all work inside a GetMessage loop which will only end when a WM_QUIT message is received While GetMessage(t2Msg, 0, 0, 0) // Do whatever this thread needs to do Inc ct% : Text 10, 10, ct% & " " // Look for pertinent messages If t2Msg.message = WM_APP + 1 // If number sent is not 0 then display that instead of original number sent to the procedure np2% = Iif(t2Msg.wParam = 0, np%, t2Msg.wParam) ~MsgBox("Number Passed:" & np2%, MB_OK, "From ThreadTwo") ~DispatchMessage(t2Msg) // Reply with the current count in this thread ~PostThreadMessage(t2Msg.lParam, WM_APP + 1, ct%, 0) EndIf Wend EndProcedure
General rules: 1. Use GetMessage() rather then PeekMessage() as it waits for the message; the down side is that it waits for a message...so you need to keep sending it dummy messages to keep the thread doing what you want it to do. (I seem to recall there is a way round this but, for the life of me, I can't remember it at the moment). 2. Don't leave GetMessage() waiting too long; if you do, Windows will post a Ghost Window (one of those with 'not responding' in the caption bar) which tends to upset GB32 and will eventually cause a crash. 3. You can close the thread using either PostThreadMessage() or TerminateThread(); the first is cleaner and more in keeping with the program structure; the second forces a termination no matter what. Briefly, concerning your comments about not using TerminateThread() (or SuspendThread(), etc): Use them - they do no harm by themselves - but use them with caution. You mention in one of your posts that it can prevent a file being saved correctly: in that instance, when you start saving the file, send a message to the master thread (e.g. PostThreadMessage(MasterThreadId,WM_xxx,3,2) where 3 refers to the thread no and 2 tells it not to terminate/suspend) and when you have finished, send another message to say so (e.g. PostThreadMessage(MasterThreadId,WM_xxx,3,1 where the 1 tells the master thread that it may once again terminate/suspend as required). The moral: when someone says 'Don't use' on a forum, they generally mean 'Use with Care' or 'There's an easier/better way' or, sometimes, 'It's complicated and I don't have time/want to explain'. EDIT: The means of getting round the fact that GetMessage waits for a message is to query GetQueueStatus($100). When the return value is not 0, there is a message that has been posted to this thread from another thread which you can get by using either PeekMessage or GetMessage.
|
|
|
Post by troycheek on Jul 5, 2015 16:18:31 GMT 1
Just for the record, I got somewhere in the ballpark of getting this messaging thing working all by myself. I had the data structure (type) figured out except for the pointer, though yours is neater. I was almost using the proper syntax for the thread messaging commands before giving up and using the windows messaging equivalents. My test code looks similar to your example, right up to creating little dueling windows. (Great. Now I have that banjo music stuck in my head.) Thank you for your sample code, because it saved me a lot of trial and error.
I agree with everything you say about terminating threads. I just feel that in my particular application it would be better to send a request rather than forcing termination or suspension. This may be more philosophical than technical. Too many ways to skin a cat.
As for the messages themselves, it seems there are a bunch of predefined Windows message constants like WM_QUIT which can be sent to threads or windows, or are created by user interaction with windows, or are generated by Windows itself. And WM_APP+X can be used to define custom message numbers for a particular application which won't conflict with those predefined constants and aren't reserved for other uses. And then there are wParam and lParam which can be passed along with the message number. I've read enough that I think these were originally designed to be of particular types and may have specific purposes for particular Windows messages, but in a Win32 API environment like GB32 running custom application messages, they are basically a pair of Int32 variables that I can use for any purpose. In your example above, I think you're using .wparam to send the counter value. You're using .lparam to identify which thread ID the message was sent from which the receiving thread can then use to send a message back. The .message is a message number you're sending which the receiving thread checks for and sends back as kind of a handshake so the original sender knows which message is being replied to. There's no particular question in there. I'm just stating all that so that if I've got something wrong, you can correct me.
Passing the thread ID of the sending thread is a good idea and is more robust than my assumption in my test code that the main thread would initiate all communications and the other threads would only send replies back to the main thread.
In my test code posting messages to windows, I have on rare occasion observed cases where it appears that a thread responds to a message sent to a different window. The main thread posts a status request to thread 3 in window #3 and thread 2 also sends a reply back to main. The main thread posts a shutdown request to thread 2 in window #2 and thread 3 also shuts down. It is my understanding that Send/PostMessage(window #2, blah, blah, blah) should send a message that ONLY appears in window #2's message queue and should be invisible to window #3. It shouldn't be possible to put code in window #3 that receives messages intended for window #2, unless I am completely misunderstanding something. I think I'll use unique WM_APP+X message numbers for each window/thread.
What is the use of DispatchMessage compared to SendThreadMessage or PostThreadMessage?
Anyway, thanks for the code. I hope to do some more experimenting later today and will post my own code once it is less embarrassing.
|
|
|
Post by dragonjim on Jul 5, 2015 18:25:00 GMT 1
Sorry, just a quick response today
Terminating Threads: I don't mean to harp on about this, but ~TerminateThread() is actually more important than it first appears. Within the GB32 IDE environment, the program tries - and generally succeeds - in tidying up any running auxiliary threads when the main program ends. However, if you compile such an example, you may well see the auxiliary threads carrying on their own merry way once the main program has finished, unless you explicitly end them using ~TerminateThread(). That was something I learnt the hard way the other day while trying out a few examples.
The Message Type: Your deductions are correct.
Windows & Threads: The relationship between windows and threads can be slightly confusing; purely as a personal preference, if I am running multiple threads, I will allocated specific windows to each thread. If those windows need closing, I will send a message to the relevant thread to close the window rather than doing it directly from a different one. It is rather long winded but saves potential conflicts as well as keeping everything tidy and avoiding confusion. But, as I said, that's only a personal preference.
DispatchMessage(): As far as I understand it, GetMessage() looks at the message at the head of the queue but does not pass it on for action (rather like PeekMessage(,,,,PM_NOREMOVE)) so, once you've done what you are going to do with the message (including ignore it), DispatchMessage() removes it and dispatches it to the thread.
I look forward to seeing example of your code. I am going to be slightly busy over the next few days so, if I do not reply, I will do when things calm down again.
|
|
|
Post by troycheek on Jul 6, 2015 0:39:31 GMT 1
Terminating Threads: What I've been doing is that when I'm finished with a thread, I send a termination request from the main program and loop until the thread ends. For safety's sake I probably need to add a timeout and TerminateThread if necessary. Windows & Threads: I've been using one window for each thread as well. And letting the thread close its own window just before it terminates. In fact, that's how I monitor from main to see if a thread has exited. I loop until Win_X is Nothing. DispatchMessage(): Something to keep in mind for later. I started with sending window messages instead of thread messages, so I'm still playing with that method trying to work out all the kinks. Kink #1: You can't use Sleep or DoEvents if you want to receive all messages sent to a window, because these commands handle messages and clear queues on their own. Use GetEvent or PeekEvent instead. That was my problem early on when I had to jackhammer messages at a window to make sure they made it through. Kink #2: The main program window can intercept messages sent to windows belonging to threads. If you PostMessage to a thread's window and then immediately start a loop of PeekEvent looking for a response, you may eat the post before the thread receives it. Add a short delay before looking for a response. Or figure out how to use SendMessage which always waits for a reply. The attached program uses windows messages to send information to and from threads. I've managed to run it several times in a row now without losing a message or crashing. If the program appears to hang for several seconds, mouse click inside the main window to skip to the next part. Attachments:Threaded4.G32 (3.95 KB)
|
|
|
Post by troycheek on Jul 7, 2015 9:12:52 GMT 1
I rewrote Threaded4 to post messages to the thread instead of the window.
Kink #2: Main thread no longer accidentally eats messages intended for other threads.
Kink #1: DoEvents and Sleep still eat messages. If I can't use something like DoEvents or Sleep, then how do I let the program respond to regular windows events (move window, minimize, etc) and, perhaps more importantly, let Windows know that the program is not Not Responding?
It's almost enough to make me go back to using global variables. I managed to run my silly little picture generator program 26 times in a row before it crashed. Of course, now that GB32 program won't be able to create any more threads until I reboot. Just that program, mind you. I can create threads with other programs just fine. I still haven't figured that one out. I think there's a phantom version of that thread still running in the system somewhere that I can't find.
tl;dr; This is no longer fun. I'm going to try something else.
Crazy idea #27: files. Create a file called "thread_is_busy.txt" and lock it when the thread is busy. Or maybe let the thread create the file when the thread starts to get busy and delete the file when the thread is finished. The thread's job is converting all the files in a folder (while new files are sometimes being created by the main program) then deleting the originals, and all I really need to do is make sure the main program doesn't kill the thread before the thread is finished with that task. I could just scan the folder for the presence of files it hasn't deleted yet.
|
|
|
Post by dragonjim on Jul 7, 2015 11:39:27 GMT 1
Threaded4 seems to work well - you have certainly built in a large amount of redundancy (double checks) which is important with multithreading. And, as you have discovered, sending messages is not always instantaneous and is reliant upon what else is going on in the background - and if it is instantaneous, the receiving program might not be quick enough to pick it up first time. It is the same when you try and control a separate application - you have to allow for time lags and be prepared to send multiple messages.
However, if, as you say, you are simply looking to monitor whether the threads are running or are finished, you could use a file as you said - I find that a useful way to pass information between two different applications - or in the Threaded4 example, how about just getting the main thread to monitor the status of the Windows allocated to the threads as in the quickly cobbled together code below:
Global Int32 t2h, t2id OpenW 1, 10, 10, 200, 200 : Win_1.Caption = "Main Thread" t2h = CreateThread(0, 0, ProcAddr(ThreadTwo), 0, 0, t2id) Local t# = Timer While IsNothing(Win_2) : Sleep 10 : Wend Do Output = Win_1 Text 10, 10, Int(Timer - t#) & " seconds" Sleep 20 : DoEvents Until IsNothing(Win_2) ~TerminateThread(t2h, 0) Text 10, 40, "ThreadTwo finished and closed" Text 10, 55, "You may now close this window" Do : Sleep : Until Win_1 Is Nothing Procedure ThreadTwo Local t# = Timer OpenW 2, 220, 10, 200, 200 : Win_2.Caption = "Thread Two" Text 10, 10, "Doing something..." Do Win_2.ForeColor = RGB(Rnd * 256, Rnd * 256, Rnd * 256) PBox 10, 30, 173, 155 Pause 5 Until Timer - t# > 5 CloseW 2 EndProcedure
Multithreading is frustrating at the best of times but don't give up just yet - you've got a lot further than many people before you.
|
|
|
Post by troycheek on Jul 7, 2015 17:35:19 GMT 1
Consider this the short and stupid version of the long, insightful message I was typing when the power flickered again this morning. Really must get a new UPS. I'm considering testing global variables again with try/catch contraptions around all references. This might prevent the program bombing out when one thread tries to write a variable at the exact same femtosecond another thread is reading it, which is what I think is causing my "1 run out of 20" errors. It would help if I knew which thread was causing the error, or if the error was caused by the thread reading the variable or the thread writing the variable. I have a sneaking suspicion that sometimes the read operation causes the error, sometimes the write, sometimes one thread, sometimes the other, since I don't get the exact same error message every time. Checking to see if the thread's window has closed is a great way to see if the thread has exited. I always make sure that the last thing the thread does before exiting is close the window it opened, just to be neat. I've adopted "Win_X Is Nothing" as my official way to see if thread X has responded to a "please terminate yourself" request. In my silly image generating program, it won't work because there will be times when the thread is finished with available input and is simply waiting to see if the main program will generate more. Main needs to know when thread is finished before killing thread and ending itself. When I mentioned this no longer being fun, I was referring to window and thread messaging. I still find threads fascinating and intend to continue experimenting. Okay, for experimenting with using files as simple flags, I need to bone up on TempFileName (and of course KillTempFile), FreeFile instead of me just picking random prime numbers, and double check that Exist doesn't cause any collisions if used by different threads to look for the same file at the same time. If I use the file contents to send information (as opposed to just the file's existence), I need a try/catch so that trying to open a file currently in use by another thread doesn't cause an error. Or I could open all files with the Share option, but then I'd have to check the data being read was complete and not in the middle of being written. Edit: I never realized before, but TempFileName actually creates an empty file with the name it generates. I always thought it just generated and returned a unique file name. Somebody needs to test that and update the help files. I guess I'll try using TempDir and a file name of my own creation.
|
|
|
Post by dragonjim on Jul 7, 2015 19:18:48 GMT 1
Quick reply again, sorry. Passing info using files: As I commented in a previous post, I like this method of passing information between applications so why not between threads? However, I wouldn't use a randomly generated temporary file as you'll have to pass the filename to the thread. You could simply use filenames like Thread2In.dat for information sent from Main to Thread 2 and Thread2Out.dat for information returned by Thread 2, and the different threads to check for a specific file and read them a bit like in this theoretical design below: 1. If Exist ( incoming data file) ...then Try to open ...If can't then skip to 2 ...Otherwise retrieve information and act accordingly Endif 2. Do one or more cycles of whatever the thread has to do ...then loop back to 1 to check again TempFileName: Thanks for spotting that deliberate mistake ; I've posted a correction and it will in the next release which should be out in a week or so. A précis: the function will try and create a temporary file but will only succeed if that filename is unique... Keep up the good work.
|
|
|
Post by troycheek on Jul 7, 2015 19:51:24 GMT 1
Mwahahahaha!Well, that didn't take long. Converting my project from global variables to files was almost trivial. For future reference (any future googlers who find this page or my future self after I've not used this for a while), first generate some "unique" temporary file names: Global t2busy$, t2kill$ t2busy$ = TempDir + "colorstatus.tmp" t2kill$ = TempDir + "colorkiller.tmp" If Exist(t2busy$) Then Kill t2busy$ If Exist(t2kill$) Then Kill t2kill$ And delete them if they already exist from an earlier run of the program. I use "color" in the name because the silly image generator I'm using is called Color Wars. I use the TempDir because I have all the Windows temporary directories redirected to a RAMdisk. It's not really necessary to use the temp directory but these are temporary files and that's where such files are supposed to go. I think it's relatively safe to use this method even with regular temp directories because the files should be cached in memory for the duration of a program and will possibly not even touch the disk before being deleted. (I have what are probably irrational concerns about speed issues with mechanical drives and wear issues with solid state drives, hence my use of RAMdisk for temporary files.) (Crap. Now I'm worried about RAM wear issues. I have issues.) The thread (number 2 in this case) can indicate that it's busy and doesn't want to be disturbed (i.e. terminated) with this: If Not Exist(t2busy$) Then Open t2busy$ for Output As # 17 : Close # 17 and when no longer busy use this: If Exist(t2busy$) Then Kill t2busy$ I really need to find an unused file number with FreeFile instead of just picking one. The main thread can check the busy status by checking Exist(t2busy$) and can wait until the thread is not busy by using this: Do : Sleep : Until Not Exist(t2busy$) To request the thread terminate itself, it's a matter of the main program using this: If Not Exist(t2kill$) Then Open t2kill$ for Output As # 19 : Close # 19 Do : Sleep : Until Win_2 Is Nothing If Exist(t2kill$) Then Kill t2kill$ Yes, another use of FreeFile. And have the thread check for the existence of t2kill$ in its main loop, something like this: While Not Exist(t2kill$) Rem Do important time-consuming stuff Wend And be polite and clean up the temp files when finished with this: If Exist(t2busy$) Then Kill t2busy$ If Exist(t2kill$) Then Kill t2kill$ The next step is to test passing information back and forth in a temporary file. I'll have to write some test cases because my current project doesn't require this. I'm using a lot of what are probably unnecessary If Exist() Then statements to 1) cut down on unneeded file creations, and 2) avoid having to put Try/Catch around every file deletion command to avoid error messages from trying to delete nonexistent files. It's probably unnecessary to check for the existence of a file I know I just created, but in a more complicated program or multiple programs working on the same files, it's probably a good idea. Of course, in those cases, you probably need to use Try/Catch anyway just in case some other program moves a file during that exact femtosecond between when you check the file's existence and the try to do something with it in the next program line. I must have had too much sugar and/or caffeine today, because I'm absolutely wired about this! Edit: So, I was writing this reply while you were working on your last. Concerning random file names, I meant more like unique in that only this program would use the names. Passing it to the thread is easy: global variables. Should be safe to set them once in the init of the main program and then only read by main or threads, so no annoying Access Violation errors.
|
|
|
Post by dragonjim on Jul 8, 2015 21:41:55 GMT 1
Looks like we both had the same thought. Keep up the caffeine - it seems to be working!
|
|
|
Post by dragonjim on Jul 9, 2015 23:04:16 GMT 1
An interesting development or two... TerminateThread: According to GFABasic documentation (which was in German, so I may have read this wrong), it would appear that it is best practice to use ~ExitThread(0) at the end of your thread as this definitely clears out the address area allocated to the thread, whereas TerminateThread MAY NOT do this, which MAY end in a 'GFABasic is not responding' error. The capitalisation is included as the documentation was written for Win95/98 and NT4.0 (Windows XP was more or less NT5.1, Vista is 6.0, Win7 is NT6.1, etc) and my poor understanding of German. However, to be on the safe side, use both... Global Variables: Despite all that is written about 'do not share global variables', according to the GFABasic documentation (barring one 'maybe' type warning) this should not be a problem as this little example goes to show... Test3.G32 (1.65 KB) It also shows how more efficient multi-threading is than single thread operation. Debug: Although the GFA documentation states that Debug should work fine with multi-threading, it categorically does not. The above example only stopped bugging out once I removed Debug statements from the threads saying they had closed properly. Maybe they meant from the main thread only...? Just thought I'd keep you updated...
|
|
|
Post by troycheek on Jul 10, 2015 0:35:11 GMT 1
Still working on communication because I keep running into other mysteries that need solving. The example I posted before created two threads which each opened a window. Once in a great while, one or the other window would not appear. I think the missing window existed but was invisible or positioned off the screen or something, because prints and graphic output from that thread was not redirected to another window (like would happen if I accidentally closed a thread's window while it was still running). If I'd thought, I'd have let the test run and see if messages posted to the missing windows arrived, but I'd panic and exit the program every time I saw it happen. Mystery #1: Invisible windows. My latest test program creates eight (8) threads, or however many CPU/core/hyper-thread combinations it thinks the computer has. The first time I run the program in the GB32 IDE, most/all the windows will open. On successive runs, fewer windows will open. The threads are still running, based on the disk output they create. (I've unfortunately removed the window/thread message stuff in preparation for file messaging, so I don't know if they're still receiving window messages.) This is just in the IDE, though. If I compile and execute the program, all the windows open. Mystery #2: Procedures into threads. I've already solved this one. For some reason I had the idea that every thread needed to be its own procedure. If I wanted to create multiple mostly identical threads, I was cutting and pasting the code. I saw reference somewhere to creating multiple threads from a single procedure and wondered why GB32 couldn't do that. It turns out GB32 can do that, I'd just assumed it couldn't. However, thread 1 needs to know it's thread 1 and to do thread 1's share of the processing. It's simple enough to set a global variable with the thread number before creating the thread, and have the thread as the first thing it does copy that global variable's value to a local variable. Files created to carry messages from one thread to another can contain that thread number for identification. Mystery #3: CPU usage. Part of the reason I've gotten into multi-threading is that I've got 8 cores and I want to use all the CPU. I almost gave up on multi-threading because in my first tests all the threads seemed to use just one core. Later tests proved different. This latest test shows that 8 threads running on 8 cores uses approximately... 1/4 the CPU. Usage varies from as little as 25% to as much as 40%, depending. As a test, I used Set Thread Affinity Mask to assign each thread its own core. 1 thread uses all of core 1 for a total of about 13% total CPU usage. 2 threads use all of cores 1 and 2 for 25%. 3 threads use most of cores 1-2-3 for about 30%. More threads use less and less of their cores until at 8 threads I'm still around 30%. However, first run in the IDE after a clean boot and I might get upwards of 90%. Unless I reboot just to make it happen, in which case it doesn't. Compile and execute does the same thing, unless I hide the windows each thread creates (open the windows with the hidden option), then compiled programs with 8 threads use >90% of the CPU. Same effect without using Affinity Mask and letting Windows decide how to divide up the CPU. So, does having a visible window cut down on the amount of CPU time a thread has available, as window events take focus away from the thread? Very irritating, as I like to have a window open for each thread for debugging reasons. Mystery #4: Has slipped my mind. I'm sure I'll remember it as soon as I save this post. Attachments:Threaded6.G32 (2.37 KB)
|
|
|
Post by troycheek on Jul 10, 2015 1:11:35 GMT 1
Once again, you sneak in a reply while I'm typing.
~ExitThread(0) sounds like something worth remembering. I'll add it to my programs.
I got the idea of using global variables from some sample program I found somewhere. Using them didn't seem to cause a problem in that sample, doesn't seem to be causing a problem in your test program, but I swear I started getting random errors in my image program as soon as I added global variables for thread communication and those stopped as soon as I switched to using files.
No problem with your test program using all the CPU. I guess my problems must be caused by having a window open for each thread, a window that needs to be updated, listen for events, etc. I guess I can put all the debug information in one window. Grumble grumble.
As brilliant as that test program is, I have a problem when I run it. On my system, it claims that the single-threaded version is much faster than the multi-threaded one. Or rather that the multi-threaded version is for example -250% faster (that's negative).
|
|
|
Post by dragonjim on Jul 10, 2015 1:33:35 GMT 1
Invisible Windows and Slow Updates: Invisible Windows and slow screen updates are due mainly to running through the IDE (as far as I can determine). Always test your projects as a .exe and all should be well.
One Procedure and Many Threads: As well as opening threads based upon the same procedure, you can also send the procedure a parameter value (see the example in my last post). The parameter can be of any type BUT must match that declared in the procedure header. In addition, if you have an array of values you wish to send, you can send the array pointer OR pass them through the parameter as a concatenated string. This is so much easier than trying to send messages after the event.
CPU Usage: CPU usage can be restricted by waiting times such as file saving/loading, message receipt, etc. Sleep, GetMessage() and other such commands and functions can also cause cores to be less active, as well as time delay commands such as Delay and Pause. However, at the end of the day, I suppose if the workload isn't enough to max out a core, it won't. Every system has something inside it which limits reaching 100% across the board (as in CPU usage, memory access, disk access et al).
Just a comment about your posted code: You are closing the window of the main thread and then closing the thread itself before some of the auxiliary threads have time to register the window closure (the trigger you are using to get them to close themselves) meaning that the IDE is occasionally throwing an error. A suggestion would be to use a global counter variable: close window one, wait until the global counter counts down to zero (decremented by each open thread as it closes) and then only finish the main thread when the counter is at zero.
Oh, and once more about ~ExitThread(0). Many examples on the net - and many coders who use C++ - will advice against using it as C++ tidies up a thread after the function it used to run has closed and been exited; GFA doesn't always - or doesn't specifically, I should say - unless ~ExitThread(0) is used.
Hope some of that lot helps....
Edit This answers queries in your last but one post...
|
|
|
Post by dragonjim on Jul 10, 2015 1:39:01 GMT 1
...and this is the reply to your last one...
So my example is working faster on one core than on eight? I get a 28-35% speed increase on mine. How many threads are you running? And how many background processes have you got running? It sounds like you're doing a lot of registry swapping which is slowing down the multithreading...
Also, try compiling and running it. I get much better results ranging from 30 to 70%.
|
|
|
Post by troycheek on Jul 10, 2015 9:17:50 GMT 1
Something that just bugs me: Consider four different cases. Run program within the GB32 IDE, Compile and Execute within IDE, Run the EXE outside the IDE but with IDE still loaded, and Run the EXE with no IDE loaded. I swear I've detected subtle differences in behavior or run time in each case with various programs I've tried over the last few days. Differences that are very hard to track down once I actually start looking for them. I know the IDE basically automatically and invisibly compiles the code for you before each Run, and I think I read somewhere that it puts some debugging and breakpoint code in there in the IDE Run that it doesn't when you straight out Compile. Past that, the differences are a mystery and perhaps imaginary. Closing window as trigger: That was an experiment I was trying out. Since the same procedure running as multiple threads wouldn't have a set window number, I couldn't use for example Win_7 Is Nothing to see if a thread's window had been closed, so I decided to check to see if the main window had been closed and key off of that. (I think I could use something like Form(w%) Is Nothing with w% being the number of the window that the thread opened. I haven't tested that yet.) Good catch on noticing that sometimes the main program was ending before all the threads had time to exit. Adding a short Delay at the end of the main program cleaned up some errors and the mysterious invisible windows when running within the IDE. I think the program couldn't open windows of the same number because the old ones still existed somewhere in the system because they were never closed properly. These would get cleaned up with the IDE exited. Naturally not a problem for the compiled version. I wonder if there's an equivalent of FreeFile for window numbers. Quick test: Rather than use the counter you suggested, I tried checking for the existence of the windows I had the threads create (window number is 100 + thread number in this case), closing its own window being the last thing each thread does before it exits. For i% = 1 To Proz% : Repeat : Sleep : Until Form(100 + i%) Is Nothing : Next i% It's worked so far and has given me another of those mad scientist evil laugh moments. Or I could just terminate the threads, but I might put a check like this afterwards just to make sure. Passing parameters to procedures running as threads: I had wondered about that, but somehow I had just assumed it wasn't possible. I believe in your Test that you are passing the thread its number n%. Checking the Microsoft Create Thread function page, I see that we can also create the thread in a suspended state and have it wait until it's told to resume. I'm not sure what use that would be, but now I feel compelled to use that in some way. Speed and Power of the test program: I've tried it with a few as 2 threads and as many as 16 (twice as many threads as I have cores). I have very little running in the background and am usually about 3% CPU usage just idling at the desktop. I've tried all four of the above mentioned cases. If anything, the single-threaded run gets faster compared to the multi-threaded one if the program is compiled and run outside of the IDE. My most recent test with 4 threads took 8.5 seconds while the single thread took 1.5, which tells me that multi-threading is -467% quicker. Maybe it's a quirk of Windows 7 64 Home Premium or my AMD processor. The really funny thing is that sometimes one or two threads will complete in less than a second while the others take multiple seconds to complete. If all of them finished that fast, they would indeed beat out the single thread handily. I assume that's what happens on your system. Curiously enough, in my image generation program, I really do see a massive increase in speed by creating them in one thread and converting them in another. The program runs almost as fast as the version without the conversion part. German: Yes, we need someone who reads both German and English and is a computer programmer. I used to know such an animal back in my college days but I haven't seen or communicated with her in 20 years. Sjouke Hamstra has unfortunately come down with a case of "real life" if I read his last email correctly. Automated translations are often useless because Germans like to create new computer terms by stringing together three or four words, sometimes from unrelated fields or other languages. I used to freak them out at work by referring to printouts as druckerlisten, refer to Dr. Farber as Dr. Colors, and even my non-tech brother now uses the mock German blinkenlichten to refer to the activity lights on the router.
|
|
|
Post by dragonjim on Jul 10, 2015 12:14:43 GMT 1
Differences in Operation: You’re right that the IDE-run compiled version is generally slower (due to error catching, etc) although you can remove some of the differences by using the $Step, $For.. and $Obj directives. Also, if GFA is in memory (running) when you running a different program, whether the program you are running be a compiled GFA .exe. or a completely unrelated program, then there is a good chance it will effect performance as GFA will have a hook on some of the resources and a passing interest in a core or two as it waits for you to do something.
FreeFile for Windows? Not to my knowledge – the best way of checking is using something like: If IsNothing(Win_1) Then OpenW 1, similar to the product of your mad scientist moment with Form(). A few words of caution when using Form(): firstly, it only handles window numbers higher than 32; the second is that, if you’ve used any window numbers below 31, then try opening one above 31, you can get an Access Violation error (it’s unpredictable but generally happens once the program gets longer and can sometimes be cured by either pressing ‘Run’ again or turning off GFA and restarting – and sometimes it can’t).
Suspended Threads: The main uses for these (from the off at least) is that you may not want a memory and/or CPU heavy thread starting immediately before you have finished what you are doing in the main thread; in addition, if you are opening the thread in preparation for a future event when it will be needed. Some programs leave threads running throughout their operation and just use SuspendThread() and ResumeThread() to activate them when they are needed.
Stuttering Display/Output: My last reply to this is a stark warning to all why you should not reply ‘from the hip’ last thing at night when you’re ready to drop. The reason why you way be getting stuttering display is that Windows requires a thread (or two) to compile and send rendering instructions to the graphics card (or display device if you have ‘in-CPU’ integrated graphic potential) and if you’re using all of your cores, those threads will have to wait their turn – and you get the stutter. Why is this worse running from within GFA? Well, Debug and Error handling has its own thread while the IDE itself uses another (I think) to monitor if and when the program ends – hence more threads grabbing for the in-demand cores – and more stutter.
As for German speakers? There are numerous still using GFA...but no one who seems willing (or able) to do any translating (my apologies to any reading if I have misunderstood).
With regard to the multithreading document, it seems to deal in basics and all its examples are based around the MMI Timer function – no message passing, a vague mention of Critical Section (the CRITICAL_SECTION object type in the appendix is wrong) – it doesn’t even use CreateThread(). It is a beginner’s guide from which you then graduate onto MSDN, much of which I shall try and cram onto what looks like it will be the multiple help pages for Multithreading.
Finally, with regard to the fact that you are getting a negative performance increase (or should that be decrease) with that short routine I sent you...most unusual. Try opening Task Manager and seeing if there is activity on all cores as it sounds as if it’s channelling most of the work through one. Are you running it with your program in the background (I’m thinking of your using SetAffinityMask())?
|
|
|
Post by troycheek on Jul 10, 2015 16:44:30 GMT 1
Form(): Reading the help file, I got the impression that while Form() was the only way to access windows greater than 31, however it did not seem to me that it was saying that Form() worked only for windows greater than 31. The following program seems to indicate that Form() works for any window number.
Local w% = 1 OpenW # w% If Form(w%) Is Nothing Then Print "Nothing there!" Else Print "Found you! "; Form(w%).Name EndIf Do : Sleep : Until Me Is Nothing CloseW # w%
Negative Performance: I checked Windows Task Manager, though I normally use this cute little graphic sidebar gadget called All CPU Meter. There's nothing big running in the background and CPU usage at idle is 1-3%. When I run the Test3 program all the cores light up to some degree or another, and the total CPU usage is an expected amount. If running 6 threads on 8 cores the total CPU usage is 75%, and 1 thread uses 12%. I also played with the Affinity Masks, giving each thread its own core, and each core was utilized near 100%, and final times were the same. As soon as my laptop finishes recharging, I'm copying my GFA BASIC folder over and trying it there. I've got another desktop system and two more laptops in the house to test when time allows.
I agree that the multi-threaded section should be faster on any computer with more than one core. I'm working on what I hope will be a better "real world" test case.
By the way, that Iif statement was genius. I couldn't figure out how the rankings were being displayed like that and must have scanned by that line three times before I realized what it was doing.
|
|
|
Post by dragonjim on Jul 10, 2015 23:00:27 GMT 1
Interesting discovery about the Form() array...another help page rewrite coming up I see!
Just for info, talking to Sjouke many months ago, he seemed to think that the Win_x functions were handled in a different place to the Form() functions; interestingly, if you use CloseW for a window that is not open then it is ignored, whereas if you used Win_x.Close (or Form().Close), an error is generated, which hints that CloseW is handled by the compiler and the .Close methods may be handled in the OCX (or at least in a different part of the program). Ramble over.
|
|
|
Post by troycheek on Jul 11, 2015 20:21:14 GMT 1
I tested your Test3 program on every computer in the house. There was a combination of 2, 4, and 8 core machines, running Windows Vista 32, 7 32, and 7 64, with AMD and Intel processors. The consensus was that AMD machines run 50-400% slower when multi-threading. Intel machines run 25-50% faster when multi-threading, even when torture-tested by running twice as many threads as cores (the rest of the system staggered to a halt until the test program was finished, of course). This confuses me because I've been an AMD man since the K5 days and I've never had any problem with multi-threading applications like FFmpeg. My somewhat closer to real world application creating pretty pictures with one thread and converting them to PNG files with another has no problem on my AMD processor, so I'm thinking it's a problem with your code. :-) I'm trying to create my own test program to show AMD superiority, but I'm easily distracted. For example, I'm supposed to be creating a test program that sends messages between threads with shared files.
|
|
|
Post by dragonjim on Jul 12, 2015 0:16:09 GMT 1
I will try and get some other people to test my little routine on their machines and get back to you... I can't believe you would think it was my programming
|
|
|
Post by troycheek on Jul 12, 2015 3:12:03 GMT 1
I finally managed to throw together a test routine. When it doesn't crash or lock up, with 2 threads I see roughly 30% better performance than single-threaded. For 3 or more threads, see "crash or lock up" above. My plan was to create 64 or so "work units" which I'd cycle through a maximum X number of threads. A thread would increment a thread counter when it started, do the assigned work unit, decrement the thread counter, then exit itself. If I had more work to do, I'd wait until one or more threads exited and the thread counter dropped below the maximum, then create another thread assigning it another work unit. It works fine the first few times, but sooner or later the program would crash or just stop working. I think rapidly creating threads one after the other as fast as they exit turns out to be a bad idea. My next attempt will create X threads which will keep running and monitor for messages from the main program waiting for work assignments, process the work unit, then report when they are ready for the next assignment.
|
|
|
Post by dragonjim on Jul 13, 2015 15:21:44 GMT 1
Just a quick thought...rather than starting and ending threads, then re-creating them later on to do the same task (if this is what you are doing), use SuspendThread() and ResumeThread() instead. It may help with the error count.
|
|
|
Post by dragonjim on Jul 14, 2015 15:10:26 GMT 1
With reference to multithreading, the help file now has an article on it (use the link under Build 6...). I'd be obliged if you'd read it through and see if anything else needs to be added.
|
|
|
Post by troycheek on Jul 15, 2015 21:04:07 GMT 1
Okay, so I want to create multiple copies of the same thread to do work on similar but of course not identical work sets. I've tried creating and re-creating threads, tried suspending/resuming them, tried just letting them loop idle, and the consistent results I've observed are as follows. If I'm using the threads to do something useless like counting to an arbitrary large number N or draw N random lines or fill in N random colored boxes, everything works flawlessly. If I'm using the threads to do some "real" work like sorting a list of N numbers or reversing the characters in a N length string, the program errors out after a few re-uses of the threads or the threads appear to just stop executing after a while. I'm not sure how GFA knows when I'm trying to do "real" work, but it detects it with unerring accuracy.
I begin to suspect that creating multiple threads from the same procedure is the source of the problem. I need to re-write one of my test programs with multiple identical copies of the same procedure and see if there's a difference in behavior.
The only success I've really had with multi-threading is my silly image generating program which has the main thread creating images and a second thread to convert those images from one format to another, the latest version of which communicates between threads with files. The attempts using global variables would crash unexpectedly, and the attempts using window or thread messages had to be so full of redundancies and re-sending of messages that I got bored trying to program it.
As for the help file, I did not see any errors in the small book you've written on multi-threading. The code you use for counting the number of cores and logical processors is smaller and more complete than what I'd been using. I've not tried all the other example code presented, but what I've tried worked without error. You step off into areas I've not experimented with yet like Critical Sections, so I can't comment on those. All in all, I feel this is the type of article that would have helped me back when I first started experimenting in multi-threading.
|
|
|
Post by dragonjim on Jul 15, 2015 22:44:22 GMT 1
Thanks for the comments about the help page.
As for your problems with multithreading and 'real' work, send me an example and I'll see if I can spot something. Some of the errors I got while researching multithreading were so obscure - as documented in the help file, one happened when I tried changing the font in a window which was opened by a different thread, although changing the text colour was allowed...
|
|
|
Post by troycheek on Jul 18, 2015 18:34:21 GMT 1
Here are some attempts to simply reverse a set of fairly long strings. This was of course before I realized there was a Mirror$ command, but I was just trying to find something very simple but time-consuming to show the value of multi-threading. These programs may attempt to create files in your TEMP directory, so if you're a neat freak like me you may have to delete them manually when you're finished. Sometimes, running the multi part with single thread will work, while more threads is a quick trip to Crashville. I have theories, of course. One is that using the same procedure to create multiple threads is somehow causing a problem (untested). Another is that building very long strings within a procedure/thread is using too much memory or conflicting memory locations (untested). Another is that I've made some simple syntax error, but if that's the case, how does it ever work at all? I mean, work or don't work I understand, but working for 32 of the 64 test cases? Working with two multi-tasking threads but not for four? What gives? Attachments:Threaded.zip (4.06 KB)
|
|
|
Post by dragonjim on Jul 18, 2015 21:27:25 GMT 1
Had a quick look and ThreadTest1 is a bit of a nightmare (not a criticism of you, just of what the program is trying to get done with multithreading in GB32). 1. In the main thread, use DoEvents rather than Sleep as DoEvents carries out checks whether there are any events waiting, does them if there is, then lets the program continue, whereas Sleep waits for an event, does it when one turns up, and only then lets the program continue. This makes the multithreading slightly faster then the single thread 2. In the auxiliary threads, removing the DoEvents stopped the crashing. Why? I can only assume that, as there was no window attached to the thread, it was trying to DoEvents for the only open window and causing a conflict... I think. 3. Crashes on multiple threads caused by more than one thread trying to save at the same time; this was fixed by creating a Critcal Section which forced the threads to wait their turn which stopped the crashes...but doesn't speed up the program as it forces the threads to run concurrently. And... 4. ...here we run into the bugbear, as you suspected, with string concatenation which also needs to be protected by a Mutex or Critical Section if the string variable or array is global. This is probably because, as the string gets longer, GFA recreates it by freeing up the old memory used and recreating a new contiguous memory block and thus changing the address of that and some or all succeeding elements. Re-write this section to work like ThreadTest2 (with two local strings) and this seems to work better. I did all these changes and it seemed to work OK except for the fact that it stopped decreasing ThreadCount at the end of the thread which caused it to freeze at either thread 5 or thread 64 (the If Threadcount% = Threads% line should read If Threadcount% >= Threads% to catch more than one thread being created before the program gets round to checking again) - this can sometimes be caused by a corruption in the GFA program. Hence, ThreadTest1 then needed to be written from scratch which, unfortunately, I haven't got time to do at the moment. However, have a go with some of the suggestions above - they work for ThreadTest2 as well - and get back to me if you have any problems. Final comment on ThreadTest1 - split the string concatenation from the file saving and only use the Critical Section to restrict the file save; this should give a significant increase in performance for multithreading. I'll have another look at your examples over the next day or so and get back to you. A few hours later: Recommendation: Don't try and save files from a thread...the program seems to crash if you do not allow a certain time (any time between .02 and .05 for 2 to 4 threads) after the file has closed for the system to flush the cache. I think it comes back to the fact that many of the old commands and functions were designed for a linear not a multithreading environment; some will work really well in multithreading, others not. VB6 had the same problem by all accounts. Oddly enough, I didn't seem to run into problems saving with your first example, but after the save things went a bit peculiar (the program completely ignored the Dec threadcount% command). I suppose you could use the Windows APIs CreateFile() and WriteFile() within the threads which use handles rather than channels and are better optimised, but that is a topic for another day... And another hour later: Or you could just open all the files you want first, write to them when you need and close them all at the end. Here is a reworking of ThreadTest1 which is stable but not a great advert for multithreading full stop: ThreadTest1a.G32 (2.04 KB)
|
|
|
Post by troycheek on Jul 19, 2015 2:52:49 GMT 1
I went between Sleep and DoEvents and nothing several times in various locations in different iterations of the test programs. I went between saving data to files, keeping it in global variables, copying it to local variables, and I forget what else. Basically, if something didn't work (hang or program not responding or crash), I'd try something different. I'm not sure which versions I uploaded because I suck at version control. Your recommendations are appreciated but my gut feeling is that you're mostly suggesting things that I already tried. Do X and not Y, but the only reason I'm doing Y is because X didn't work in the first place. (ThreadTest1 is using a global variable and writing directly to file because the earlier version which copied the work to a local variable, concatenated the results to another local variable, and didn't even bother trying to report the results back to the main thread, was crashing sooner.) Good catch on Threadcount% >= threads%, though, which would explain why adding a delay between thread creations made the program run better. Slower, but better. In general, I could get any of the test programs to run better if I reduced the number of threads and put more delays here and there, but that kind of kills the whole idea of multi-threading to begin with. Sure, the multi-threaded versions ran faster, but that was just because there were 8 threads counting down a bunch of Delay 5 commands at the same time instead of a single thread counting them down one after another. I was of course hoping that you would spot one little error that would fix every problem, but as you say it's more of a nightmare requiring a re-write from the ground up. I read something somewhere about FreeImage that amounted to "safe for multi-threading." Until reading that, it never occurred to me that a program or part of it could ever not be safe. And until you just mentioned it, it never occurred to me that some GB32 commands or functions could possibly not work well while multi-threading. Sigh. When I first started this experiment, having no idea how threading worked, I created an actual separate program to do the multi-tasking. (I got this idea from a conversation with Sjouke Hamstra, though he was actually suggesting not to do it this way.) The main program would check to see if this helper program was running (using the Windows command tasklist) and start it if it wasn't. And the only real success I've had with multi-threading was to copy this helper program wholesale into a thread procedure. The next time I have a "real" project, I might try this method again. Or just hire you to write the code.
|
|