Multithreading Demo

Share your advanced PureBasic knowledge/code with the community.
akj
Enthusiast
Enthusiast
Posts: 668
Joined: Mon Jun 09, 2003 10:08 pm
Location: Nottingham

Multithreading Demo

Post by akj »

Here is a very useful multithreading demo I converted from some code at http://www.codeguru.com/columns/dotnet/ ... .php/c4593 .
It is simple, but clearly demonstrates the far better responsiveness that multitasking provides.

To use the program, click the button labelled "Run Nonthreaded" and then [try to] quickly click the "Test Responsiveness" button a few times. The result is very unsatisfactory.
But now repeat the test with the "Run Multithreaded" and "Test Responsiveness" buttons and you will see a dramatic difference even though the underlying code is almost identical.

The combination of "Run Multithreaded" and "Exit" buttons is also very responsive.

Code: Select all

; Multithreaded Response
; www.codeguru.com/columns/dotnet/article.php/c4593

; When either the "Run Nonthreaded" or "Run Multithreaded" buttons are pressed
; they will be temporarily disabled and a list box will be filled with
; "Hello World" messages with a delay between each.
; The window also contains a button to test the interface responsiveness while
; the list box is being populated.
; The click events of the "Run Nonthreaded" and "Run Multithreaded" buttons
; result in the same procedure being executed. The only difference is that when
; the "Run Multithreaded" button is pressed the method is executed in a new thread.

#Program$ = "Multithreaded Response"
#Version$ = "1.0"
EnableExplicit

; Constants
Enumeration
  #winMain
  #lblResponse
  #txtResponse
  #lstHello
  #butNonthread
  #butThreaded
  #butTest
  #butExit
EndEnumeration

Declare FillList(*value)
Declare ToggleButtons(toggle)

; GUI Metrics
Define gap, lstw, lsth, butw, buth, winw, winh
Define lblw, lblh, txtw, txth
gap = 20
lblw = 100: lblh = 24
txtw = 100: txth = lblh
lstw = 500: lsth = 200
butw = 150: buth = 30
winw = lstw+gap*2: winh = txth+lsth+buth*2+gap*7

; Create GUI
Define title$, flags, x, y
title$ = " "+#Program$+"  "+#Version$
flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_TitleBar|#PB_Window_ScreenCentered
OpenWindow(#winMain, 0, 0, winw, winh, title$, flags)
; Labelled text box
y = gap: x = gap
TextGadget(#lblResponse, x, y+4, lblw, lblh, "Click Response")
x + lblw
StringGadget(#txtResponse, x, y, txtw, txth, "")
; List gadget
y + txth+ gap*2: x = gap
ListViewGadget(#lstHello, x, y, lstw, lsth)
; Buttons
y + lsth+gap*2: x = gap
ButtonGadget(#butNonthread, x, y, butw, buth, "Run Nonthreaded")
x = (winw-butw)/2
ButtonGadget(#butThreaded, x, y, butw, buth, "Run Multithreaded")
y + buth+gap: x = butw/2+gap*3/2
ButtonGadget(#butTest, x, y, butw, buth, "Test Responsiveness")
x = winw-butw-gap
ButtonGadget(#butExit, x, y, butw, buth, "E x i t")

; Event loop
Define done=#False
Repeat
  Select WaitWindowEvent()
  Case #PB_Event_Gadget, #PB_Event_Menu
    Select EventGadget() ; Or EventMenu()
    Case #butNonthread
      ; Respond to the nonThreadedButton click event
      FillList(1)
    Case #butThreaded
      ; Respond to the multiThreadedButton click event
      ; Launch a thread to do the update
      CreateThread(@FillList(), 2)
    Case #butTest
      ; Respond to the testResponseButton click event
      SetGadgetText(#txtResponse, Str(Val(GetGadgetText(#txtResponse))+1))
    Case #butExit ; Exit button
      done=#True
    EndSelect
  Case #PB_Event_CloseWindow
    done=#True
  EndSelect
Until done
End

Procedure FillList(*value)
Protected i
; Populate list box
  Debug "Value = "+Str(*value)
  ClearGadgetItems(#lstHello)
  SetGadgetText(#txtResponse, "0")
  ToggleButtons(#False)
  For i = 0 To 9
    AddGadgetItem(#lstHello, -1, "Hello World " + Str(i))
    Delay(1000)
  Next i
  ToggleButtons(#True)
EndProcedure

Procedure ToggleButtons(toggle)
; Toggle the form buttons on or off accordingly.
  DisableGadget(#butNonthread, #True-toggle)
  DisableGadget(#butThreaded, #True-toggle)
EndProcedure
Anthony Jordan
ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Re: Multithreading Demo

Post by ABBKlaus »

You should not exit the main loop while the thread is running !

BR Klaus
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Multithreading Demo

Post by idle »

you should be checking for the threads existence and waiting for it to terminate.

Code: Select all

; Multithreaded Response
; www.codeguru.com/columns/dotnet/article.php/c4593

; When either the "Run Nonthreaded" or "Run Multithreaded" buttons are pressed
; they will be temporarily disabled and a list box will be filled with
; "Hello World" messages with a delay between each.
; The window also contains a button to test the interface responsiveness while
; the list box is being populated.
; The click events of the "Run Nonthreaded" and "Run Multithreaded" buttons
; result in the same procedure being executed. The only difference is that when
; the "Run Multithreaded" button is pressed the method is executed in a new thread.

#Program$ = "Multithreaded Response"
#Version$ = "1.0"
EnableExplicit

; Constants
Enumeration
  #winMain
  #lblResponse
  #txtResponse
  #lstHello
  #butNonthread
  #butThreaded
  #butTest
  #butExit
EndEnumeration

Declare FillList(*value)
Declare ToggleButtons(toggle)

; GUI Metrics
Define gap, lstw, lsth, butw, buth, winw, winh
Define lblw, lblh, txtw, txth,thread
gap = 20
lblw = 100: lblh = 24
txtw = 100: txth = lblh
lstw = 500: lsth = 200
butw = 150: buth = 30
winw = lstw+gap*2: winh = txth+lsth+buth*2+gap*7

; Create GUI
Define title$, flags, x, y
title$ = " "+#Program$+"  "+#Version$
flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_TitleBar|#PB_Window_ScreenCentered
OpenWindow(#winMain, 0, 0, winw, winh, title$, flags)
; Labelled text box
y = gap: x = gap
TextGadget(#lblResponse, x, y+4, lblw, lblh, "Click Response")
x + lblw
StringGadget(#txtResponse, x, y, txtw, txth, "")
; List gadget
y + txth+ gap*2: x = gap
ListViewGadget(#lstHello, x, y, lstw, lsth)
; Buttons
y + lsth+gap*2: x = gap
ButtonGadget(#butNonthread, x, y, butw, buth, "Run Nonthreaded")
x = (winw-butw)/2
ButtonGadget(#butThreaded, x, y, butw, buth, "Run Multithreaded")
y + buth+gap: x = butw/2+gap*3/2
ButtonGadget(#butTest, x, y, butw, buth, "Test Responsiveness")
x = winw-butw-gap
ButtonGadget(#butExit, x, y, butw, buth, "E x i t")

; Event loop
Define done=#False
Repeat
  Select WaitWindowEvent()
  Case #PB_Event_Gadget, #PB_Event_Menu
    Select EventGadget() ; Or EventMenu()
    Case #butNonthread
      ; Respond to the nonThreadedButton click event
      FillList(1)
    Case #butThreaded
      ; Respond to the multiThreadedButton click event
      ; Launch a thread to do the update
      If Not IsThread(thread) 
           thread = CreateThread(@FillList(), 2)
      EndIf   
    Case #butTest
      ; Respond to the testResponseButton click event
      SetGadgetText(#txtResponse, Str(Val(GetGadgetText(#txtResponse))+1))
    Case #butExit ; Exit button
      done=#True
    EndSelect
  Case #PB_Event_CloseWindow
    done=#True
  EndSelect
Until done
If IsThread(thread) 
  WaitThread(thread) 
EndIf   
End

Procedure FillList(*value)
Protected i
; Populate list box
  Debug "Value = "+Str(*value)
  ClearGadgetItems(#lstHello)
  SetGadgetText(#txtResponse, "0")
  ToggleButtons(#False)
  For i = 0 To 9
    AddGadgetItem(#lstHello, -1, "Hello World " + Str(i))
    Delay(1000)
  Next i
  ToggleButtons(#True)
EndProcedure

Procedure ToggleButtons(toggle)
; Toggle the form buttons on or off accordingly.
  DisableGadget(#butNonthread, #True-toggle)
  DisableGadget(#butThreaded, #True-toggle)
EndProcedure
Windows 11, Manjaro, Raspberry Pi OS
Image
ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Re: Multithreading Demo

Post by ABBKlaus »

idle, basically you are doing the same thing as akj.
Both of you are leaving the main loop without waiting for the thread has ended.
You will see if you add a closewindow() after the Repeat/Until loop.
A crash like this will occur : The specified #Gadget is not initialized.

This code should demonstrate what i mean

Code: Select all

; Multithreaded Response
; www.codeguru.com/columns/dotnet/article.php/c4593

; When either the "Run Nonthreaded" or "Run Multithreaded" buttons are pressed
; they will be temporarily disabled and a list box will be filled with
; "Hello World" messages with a delay between each.
; The window also contains a button to test the interface responsiveness while
; the list box is being populated.
; The click events of the "Run Nonthreaded" and "Run Multithreaded" buttons
; result in the same procedure being executed. The only difference is that when
; the "Run Multithreaded" button is pressed the method is executed in a new thread.

#Program$ = "Multithreaded Response"
#Version$ = "1.0"
EnableExplicit

; Constants
Enumeration
  #winMain
  #lblResponse
  #txtResponse
  #lstHello
  #butNonthread
  #butThreaded
  #butTest
  #butExit
EndEnumeration

Structure STR_Thread
  ThreadID.i
  ThreadData.i
  ThreadQuit.i
  ThreadEnd.i
EndStructure

Declare FillList(*Thread.STR_Thread)
Declare ToggleButtons(toggle)

Procedure FillList(*Thread.STR_Thread)
  Protected i
  ; Populate list box
  Debug "FillList()"
  Debug "ThreadData = "+Str(*Thread\ThreadData)
  ClearGadgetItems(#lstHello)
  SetGadgetText(#txtResponse, "0")
  ToggleButtons(#False)
  For i = 0 To 9
    AddGadgetItem(#lstHello, -1, "Hello World " + Str(i))
    ; check if thread should exit
    If *Thread\ThreadQuit
      Break
    EndIf
    Delay(1000)
  Next i
  ToggleButtons(#True)
  Debug "FillList() end"
EndProcedure

Procedure ToggleButtons(toggle)
  ; Toggle the form buttons on or off accordingly.
  DisableGadget(#butNonthread, #True-toggle)
  DisableGadget(#butThreaded, #True-toggle)
EndProcedure

; GUI Metrics
Define gap, lstw, lsth, butw, buth, winw, winh
Define lblw, lblh, txtw, txth
Define thread.STR_Thread

gap = 20
lblw = 100: lblh = 24
txtw = 100: txth = lblh
lstw = 500: lsth = 200
butw = 150: buth = 30
winw = lstw+gap*2: winh = txth+lsth+buth*2+gap*7

; Create GUI
Define title$, flags, x, y
title$ = " "+#Program$+"  "+#Version$
flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_TitleBar|#PB_Window_ScreenCentered
OpenWindow(#winMain, 0, 0, winw, winh, title$, flags)
; Labelled text box
y = gap: x = gap
TextGadget(#lblResponse, x, y+4, lblw, lblh, "Click Response")
x + lblw
StringGadget(#txtResponse, x, y, txtw, txth, "")
; List gadget
y + txth+ gap*2: x = gap
ListViewGadget(#lstHello, x, y, lstw, lsth)
; Buttons
y + lsth+gap*2: x = gap
ButtonGadget(#butNonthread, x, y, butw, buth, "Run Nonthreaded")
x = (winw-butw)/2
ButtonGadget(#butThreaded, x, y, butw, buth, "Run Multithreaded")
y + buth+gap: x = butw/2+gap*3/2
ButtonGadget(#butTest, x, y, butw, buth, "Test Responsiveness")
x = winw-butw-gap
ButtonGadget(#butExit, x, y, butw, buth, "E x i t")
AddWindowTimer(#winMain,1,100)

; Event loop
Define done=#False
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_Timer
      If thread\ThreadID
        If IsThread(thread\ThreadID)
          Debug "Thread is running"
        Else
          Debug "Thread has ended"
          thread\ThreadID=0
        EndIf
      EndIf
    Case #PB_Event_Gadget, #PB_Event_Menu
      Select EventGadget() ; Or EventMenu()
        Case #butNonthread
          ; Respond to the nonThreadedButton click event
          FillList(thread)
        Case #butThreaded
          ; Respond to the multiThreadedButton click event
          ; Launch a thread to do the update
          If Not IsThread(thread\ThreadID) 
            thread\ThreadID = CreateThread(@FillList(), thread)
          EndIf   
        Case #butTest
          ; Respond to the testResponseButton click event
          SetGadgetText(#txtResponse, Str(Val(GetGadgetText(#txtResponse))+1))
        Case #butExit ; Exit button
          done=#True
          thread\ThreadQuit=1
      EndSelect
    Case #PB_Event_CloseWindow
      done=#True
      thread\ThreadQuit=1
  EndSelect
Until done And thread\ThreadID=0
CloseWindow(#winMain) ; see if we have coded threading the correct way
BR Klaus
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Multithreading Demo

Post by idle »

Thats better thanks, it was what I meant to do but I shouldn't be posting code at 4:20 am in the morning! :oops:
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Multithreading Demo

Post by ts-soft »

For me, this isn't multithreaded, is only threaded.
In multithreaded, i can start more as one of the same thread at the same time.
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Re: Multithreading Demo

Post by ABBKlaus »

I completely disagree with your point thomas, the above example is mulithreaded just like the wikipedia descibes it.

BR Klaus
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Multithreading Demo

Post by ts-soft »

In every time there run only one thread, never more. This is single threaded for me. I can't read the english wiki.
In multithreaded application you can run more as one thread at the same time and you require mutex or semaphore to control the access of linklist.
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
cas
Enthusiast
Enthusiast
Posts: 597
Joined: Mon Nov 03, 2008 9:56 pm

Re: Multithreading Demo

Post by cas »

Main loop is one thread, with CreateThread() you open another thread. So in this example, when you press "Run Multithreaded" button, you have two threads running at same time --> multithreaded.

But this example can be done in single thread mode (with responsive gui) with help of AddWindowTimer().
Thorium
Addict
Addict
Posts: 1305
Joined: Sat Aug 15, 2009 6:59 pm

Re: Multithreading Demo

Post by Thorium »

Multithreading gets realy interessting with a job queue so it takes advantage of multiple cores.
Dont need a demo to start 2 threads. ^^
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Multithreading Demo

Post by skywalk »

cas wrote:But this example can be done in single thread mode (with responsive gui) with help of AddWindowTimer().
:?: I thought an AddWindowTimer() would be tied to the main thread?
So if a non-responsive Procedure() had control, are you saying a window timer would still fire an event in the main event loop?

I was going to ask Trond the same question in his post...
http://www.purebasic.fr/english/viewtop ... 13#p347013
He lists several options to address long procedure calls hanging the GUI, but no AddWindowTimer()?
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
cas
Enthusiast
Enthusiast
Posts: 597
Joined: Mon Nov 03, 2008 9:56 pm

Re: Multithreading Demo

Post by cas »

I said that you can use AddWindowTimer() for this example because there is no additional processing:

Code: Select all

#Program$ = "Not Multithreaded Response"
#Version$ = "1.0"
EnableExplicit

; Constants
Enumeration
  #winMain
  #lblResponse
  #txtResponse
  #lstHello
  #butNonthread
  #butThreaded
  #butTest
  #butExit
EndEnumeration

Declare ToggleButtons(toggle)

; GUI Metrics
Define gap, lstw, lsth, butw, buth, winw, winh
Define lblw, lblh, txtw, txth
gap = 20
lblw = 100: lblh = 24
txtw = 100: txth = lblh
lstw = 500: lsth = 200
butw = 150: buth = 30
winw = lstw+gap*2: winh = txth+lsth+buth*2+gap*7

; Create GUI
Define title$, flags, x, y
title$ = " "+#Program$+"  "+#Version$
flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_TitleBar|#PB_Window_ScreenCentered
OpenWindow(#winMain, 0, 0, winw, winh, title$, flags)
; Labelled text box
y = gap: x = gap
TextGadget(#lblResponse, x, y+4, lblw, lblh, "Click Response")
x + lblw
StringGadget(#txtResponse, x, y, txtw, txth, "")
; List gadget
y + txth+ gap*2: x = gap
ListViewGadget(#lstHello, x, y, lstw, lsth)
; Buttons
y + lsth+gap*2: x = gap
ButtonGadget(#butNonthread, x, y, butw, buth, "Run Nonthreaded")
x = (winw-butw)/2
;ButtonGadget(#butThreaded, x, y, butw, buth, "Run Multithreaded")
y + buth+gap: x = butw/2+gap*3/2
ButtonGadget(#butTest, x, y, butw, buth, "Test Responsiveness")
x = winw-butw-gap
ButtonGadget(#butExit, x, y, butw, buth, "E x i t")

; Event loop
Define done=#False
Global add_counter
Repeat
  Select WaitWindowEvent()
  Case #PB_Event_Gadget, #PB_Event_Menu
    Select EventGadget() ; Or EventMenu()
    Case #butNonthread
      ; Respond to the nonThreadedButton click event
      ;FillList(1)
      ClearGadgetItems(#lstHello)
      SetGadgetText(#txtResponse, "0")
      ToggleButtons(#False)
    Case #butTest
      ; Respond to the testResponseButton click event
      SetGadgetText(#txtResponse, Str(Val(GetGadgetText(#txtResponse))+1))
    Case #butExit ; Exit button
      done=#True
    EndSelect
  Case #PB_Event_Timer
    If EventTimer()=0
      AddGadgetItem(#lstHello, -1, "Hello World " + Str(add_counter))
      add_counter+1
      If add_counter=10
        ToggleButtons(#True)
      EndIf
    EndIf
  Case #PB_Event_CloseWindow
    done=#True
  EndSelect
Until done
End


Procedure ToggleButtons(toggle)
  DisableGadget(#butNonthread, #True-toggle)
  If toggle=0
    AddWindowTimer(#winMain,0,1000)
  Else
    add_counter=0
    RemoveWindowTimer(#winMain,0)
  EndIf
EndProcedure
And it is not recommended to modify gadgets from secondary thread. Gadgets should be updated only from main thread. So if you have background procedure processing something then use SendMessage_() and update gadgets in window callback.
ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Re: Multithreading Demo

Post by ABBKlaus »

cas wrote:And it is not recommended to modify gadgets from secondary thread. Gadgets should be updated only from main thread. So if you have background procedure processing something then use SendMessage_() and update gadgets in window callback.
where do you read that, please explain (i already searched the whole forum) ?
The only thing that i know of is this : http://www.purebasic.com/documentation/ ... indow.html
Note: When opening a Window from a thread, the thread must also call WindowEvent() or WaitWindowEvent() in a loop to process events for this window, as window events are not sent between different threads.
BR Klaus
cas
Enthusiast
Enthusiast
Posts: 597
Joined: Mon Nov 03, 2008 9:56 pm

Re: Multithreading Demo

Post by cas »

ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Re: Multithreading Demo

Post by ABBKlaus »

Yes i know that thread, f34k is explaing the wrong use of LockMutex().
I do not read somewhere that you should not use threads to modify gadgets !

Lets get the eventqeue running ;-)
Post Reply