Page 2 of 2

Re: Thread Parameters?

Posted: Sat Apr 20, 2024 2:01 pm
by AZJIO
BarryG wrote: Sat Apr 20, 2024 11:00 am The Semaphore approach isn't going to work for me, because "Global Semaphore.i = CreateSemaphore()" is global and my threads are going to be called more than once, so a global semaphore for a thread is going to send the signal to the wrong thread callers, right?
Semaphore is like a flag, you can create them separately for each function and activate your own semaphore within each thread. Don't you see inside the thread the activation of the semaphore signal specified by a specific variable.
Create an array of semaphores and count the number of thread calls and for each instance check its semaphore.

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 4:10 am
by BarryG
Okay, this seems to be working for me in the way I need it. I can't see anything wrong with this approach? The goal is to show the time and date, from different threads, each time the button is clicked. Having one single global semaphore doesn't seem to be a problem? I'm not overlooking anything? Seems to work as expected when I keep clicking the button, because the "T1" and "T2" lines in the debug output are always toggling, ie. there's no T1 twice in a row or T2 twice in a row.

Code: Select all

; Enable thread-safety in Compiler Options.

Global param$
Global semaphore=CreateSemaphore()

Procedure MyThread_One(param)
  time$=PeekS(param)
  SignalSemaphore(semaphore)
  Debug "T1 -> "+time$ ; Should show the time.
  Sleep_(60000)
EndProcedure

Procedure MyThread_Two(param)
  date$=PeekS(param)
  SignalSemaphore(semaphore)
  Debug "T2 -> "+date$ ; Should show the date.
  Sleep_(60000)
EndProcedure

OpenWindow(0,400,200,170,80,"test",#PB_Window_SystemMenu)
ButtonGadget(0,20,20,130,30,"Click this repeatedly")

Repeat

  ev=WaitWindowEvent()

  If ev=#PB_Event_Gadget

    p1$=FormatDate("%hh:%ii:%ss",Date())
    CreateThread(@MyThread_One(),@p1$)
    WaitSemaphore(semaphore)
    p1$="" ; Required here.

    p2$=FormatDate("%yyyy/%mm/%dd",Date())
    CreateThread(@MyThread_Two(),@p2$)
    WaitSemaphore(semaphore)
    p2$="" ; Required here.

  EndIf

Until ev=#PB_Event_CloseWindow

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 10:56 am
by spikey
BarryG wrote: Sun Apr 21, 2024 4:10 am Sleep_(60000)
Just out of curiosity - why are you feeling the need to pause the threads for so long before exiting?

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 11:12 am
by BarryG
The sleep is just for this example. In my real app, the threads do some processing that takes a while, so the sleep was used here to stop the thread exiting too early and to simulate the further processing.

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 11:40 am
by spikey
There's a pitfall that you should be aware of. A slightly different scenario, where the work takes place before the SignalSemaphone, could lock up the main event loop. I guess that's the sort of thing you're trying to avoid:

Code: Select all

Procedure MyThread_Two(param)
  date$=PeekS(param)
  Sleep_(60000)
  SignalSemaphore(semaphore)
  Debug "T2 -> "+date$ ; Should show the date.
EndProcedure

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 12:21 pm
by Michael Vogel
@Berry - your solution doesn't work, just press the button multiple time and you'll see it...

There are many possibilities to deal with threads, I also would think about something like a 'main' thread otherwise you'd block all events in the main loop. Just a quick and dirty start which also would stop a running thread (AbortThread) if a new one gets initiated...

Code: Select all

; Enable thread-safety in Compiler Options.

Structure ParameterType
	p1.s
	p2.s
EndStructure

Global Main.ParameterType
Global Locker,AbortThread

#Timeout=1000

Macro Unlock(Lock)

	Lock=#Null

EndMacro
Macro LockAndGo(Lock)

	If Lock
		AbortThread=#True
		While Lock
			Delay(2)
		Wend
	EndIf
	Lock=#True

EndMacro
Macro ExitIf(condition)
	If condition
		Break
	EndIf
EndMacro

Procedure MyThread_One(param.s)

	Protected t

	Debug "T1 -> "+param

	While t<#Timeout
		ExitIf(AbortThread)
		Delay(1)
		t+1
	Wend

EndProcedure

Procedure MyThread_Two(param.s)

	Protected t

	Debug "T2 -> "+param

	While t<#Timeout
		ExitIf(AbortThread)
		Delay(1)
		t+1
	Wend

EndProcedure

Procedure MasterThread(*nil.ParameterType)

	Protected Local.ParameterType

	Debug "Start Thread..."
	LockAndGo(Locker)

	With Local
		\p1=*nil\p1
		\p2=*nil\p2

		Debug "Start 1"
		MyThread_One(\p1)

		If AbortThread=#Null
			Debug "Start 2"
			MyThread_Two(\p2)
		EndIf
	EndWith
	
	Unlock(Locker)
	AbortThread=#Null

	Debug "...Done"

EndProcedure


OpenWindow(0,400,200,170,80,"test",#PB_Window_SystemMenu)
ButtonGadget(0,20,20,130,30,"Click this repeatedly")
AddWindowTimer(0,0,500)
Repeat
	Select WaitWindowEvent()
	Case #PB_Event_Gadget

		With Main
			\p1=FormatDate("%hh:%ii:%ss",Date())
			\p2="..."

			CreateThread(@MasterThread(),@Main)
		EndWith

	Case #PB_Event_Timer
		SetWindowTitle(0,"t"+Chr(105+6*t&1)+"ck")
		t+1
		
	Case #PB_Event_CloseWindow
		AbortThread=#True
		LockAndGo(Locker)
		End
	EndSelect
ForEver

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 1:08 pm
by STARGĂ…TE
BarryG wrote: Sun Apr 21, 2024 4:10 am Okay, this seems to be working for me in the way I need it. I can't see anything wrong with this approach? The goal is to show the time and date, from different threads, each time the button is clicked. Having one single global semaphore doesn't seem to be a problem? I'm not overlooking anything?
Yes, it is fine.
I can't see a problem suggested by Michael Vogel, of cause you have to enable Thread Safe.
And yes, the read out of the Parameter have to be the first step and then SignalSemaphore to unlock the main thread.

Re: Thread Parameters?

Posted: Sun Apr 21, 2024 2:18 pm
by mk-soft
Semaphores are good for synchronising threads but not good for updating the GUI. I only use semaphores with GUI if the thread also has to wait for input from the GUI.

Use PostEvent if the GUI needs to be changed by the thread.
See examples Mini Thread Control

Re: Thread Parameters?

Posted: Mon Apr 22, 2024 8:53 am
by BarryG
Michael Vogel wrote: Sun Apr 21, 2024 12:21 pm@Berry - your solution doesn't work, just press the button multiple time and you'll see it
Seems to work fine here? Here's a sample debug output when I rapidly click the button. It shows each new thread alternating (the "T1" line followed by a "T2" line) with no misses. The idea is not to stop or abort a thread, as your example seems to do. Just to create those two threads as new threads each time the button is clicked, and let them time out and exit naturally.

Code: Select all

T1 -> 17:47:59
T2 -> 2024/04/22
T1 -> 17:47:59
T2 -> 2024/04/22
T1 -> 17:47:59
T2 -> 2024/04/22
T1 -> 17:47:59
T2 -> 2024/04/22
T1 -> 17:48:00
T2 -> 2024/04/22
T1 -> 17:48:00
T2 -> 2024/04/22
T1 -> 17:48:00
T2 -> 2024/04/22
T1 -> 17:48:00
T2 -> 2024/04/22
T1 -> 17:48:00
T2 -> 2024/04/22
[...]

Re: Thread Parameters?

Posted: Mon Apr 22, 2024 1:53 pm
by AZJIO

Code: Select all

Global param$
Global semaphore = CreateSemaphore()

Procedure MyThread_One(param)
	Debug "Cook: preparing food"
	Sleep_(3000)
	SignalSemaphore(semaphore)
	Debug "Cook: the food is ready, you can eat"
EndProcedure

Procedure MyThread_Two(param)
	Debug "Client: I eat food"
	Sleep_(3000)
	SignalSemaphore(semaphore)
	Debug "Client: ate food"
EndProcedure

OpenWindow(0, 400, 200, 170, 80, "test", #PB_Window_SystemMenu)
ButtonGadget(0, 10, 20, 150, 30, "Let's start the day")

Repeat

	ev = WaitWindowEvent()

	If ev = #PB_Event_Gadget And EventGadget() = 0
		DisableGadget(0, 1)

		p1 = 1
		For i = 1 To 3
			CreateThread(@MyThread_One(), @p1)
			WaitSemaphore(semaphore)
			
			CreateThread(@MyThread_Two(), @p1)
			WaitSemaphore(semaphore)
		Next

		DisableGadget(0, 0)

	EndIf

Until ev = #PB_Event_CloseWindow

Re: Thread Parameters?

Posted: Mon Apr 22, 2024 4:39 pm
by Michael Vogel
BarryG wrote: Mon Apr 22, 2024 8:53 am Seems to work fine here?...
Tried it multiple times here before I wrote the post, most of the time the code was stable, but at least two times I got an memory access error when closing the window :oops:
Today I tested your code again 30+ times with no error, not sure if the PB IDE (or myself) was in an unstable state some days before :wink:

Aborting an thread may not be useful while the app is running but I usually have such a feature for a clean exit of a program. I'd never use 'End' to shut down all unfinished threads nor 'KillThread' (because itself is unstable). Waiting until all threads have been finished can be annoying for the user.

Nothing new here, only a counter to see how many threads get killed when ending the program:

Code: Select all

; Enable thread-safety in Compiler Options.

Global ActiveThreads

Global param$
Global semaphore=CreateSemaphore()

Procedure MyThread_One(param)
	Protected Dim a.q(99999999)
	ActiveThreads+1
	Debug "init..."
	time$=PeekS(param)
	SignalSemaphore(semaphore)
	Debug "T1 -> "+time$ ; Should show the time.
	Sleep_(60000)
	ActiveThreads-1
	Debug "done..."
EndProcedure

Procedure MyThread_Two(param)
	ActiveThreads+1
	date$=PeekS(param)
	SignalSemaphore(semaphore)
	Debug "T2 -> "+date$ ; Should show the date.
	Sleep_(60000)
	ActiveThreads-1
EndProcedure

OpenWindow(0,400,200,170,80,"test",#PB_Window_SystemMenu)
ButtonGadget(0,20,20,130,30,"Click this repeatedly")
AddKeyboardShortcut(0,#PB_Shortcut_Return,0)
AddWindowTimer(0,0,500)

Repeat

	Select WaitWindowEvent()
	Case #PB_Event_Gadget,#PB_Event_Menu

		p1$=FormatDate("%hh:%ii:%ss",Date())
		CreateThread(@MyThread_One(),@p1$)
		WaitSemaphore(semaphore)
		p1$="" ; Required here.

		p2$=FormatDate("%yyyy/%mm/%dd",Date())
		CreateThread(@MyThread_Two(),@p2$)
		WaitSemaphore(semaphore)
		p2$="" ; Required here.

	Case #PB_Event_Timer
		; SetWindowTitle(0,"t"+Chr(105+6*t&1)+"ck, "+Str(GetProcessMemoryUsage(GetCurrentProcess_()))+" MByte")
		t+1

	Case #PB_Event_CloseWindow
		Debug "Active threads: "+ActiveThreads
		End; (see notes above)
	EndSelect

ForEver
[code]