Below code is an extended example of basic multithreading with channels. Starting from some examples in Mastering Nim i got to this code. It emulates some webserver-elements using workers with channels, without the use of any locks, which i find not so easy to use.
The inducement for the code was that i wondered what was the easiest way to store state of variables without using (locked) globals. In this case it is done by simply reserving one thread for storing state (the worker called statekeeper) and add data to it with help of the channels. While doing that i thought i emulate a dummy-webserver.
It is meant for programmers beginning to delve into multithreading. Comments and alternative approaches are welcome of course.
import std/os
type
Webpage = object
pageID: string
configdata: string
lastfetched: string
timedout: bool
Request = object
url: string
sourceip: string
webmethod: string
Sendable = object
url: string
targetip: string
body: string
var wepa_chob: Channel[Webpage]
wepa_chob.open()
var req_chob: Channel[Request]
req_chob.open()
var send_chob: Channel[Sendable]
send_chob.open()
proc statekeeper() {.thread.} =
var
allpagesq: seq[Webpage]
pageob: Webpage
while true:
pageob = wepa_chob.recv()
if pageob.pageID.len == 0:
echo "halting statekeeper.."
break
else:
allpagesq.add(pageob)
echo "Thread statekeeper added webpage: ", pageob.pageID
proc createPageState(idstring, configst, last: string; timedoutbo: bool) =
var pageob: Webpage
pageob.pageID = idstring
pageob.configdata = configst
pageob.lastfetched = last
pageob.timedout = timedoutbo
wepa_chob.send(pageob)
proc pagemaker(threadit: int) {.thread.} =
# create and exposes a webpage (sends on a channel)
var
reqob: Request
sendob: Sendable
while true:
reqob = req_chob.recv()
if reqob.url.len == 0: # empty url will halt the threads
echo "halting pagemaker-instance: " & $threadit
break
else:
sendob.url = reqob.url
sendob.targetip = reqob.sourceip
sendob.body = "processed html from " & reqob.url
send_chob.send(sendob)
echo "Pagemaker-thread_", threadit, " processed ", reqob.url, " and exposed it for sending"
proc haltPagemakerInstance() =
# to gratiously halt the worker
var reqob: Request
reqob.url = ""
req_chob.send(reqob)
proc bodysender(threadit: int) {.thread.} =
# sends the bodies it picks from the channel to target IP-addresses.
var sendob: Sendable
while true:
sendob = send_chob.recv()
if sendob.url.len == 0:
echo "halting bodysender-instance: " & $threadit
break
else:
echo "Bodysender-thread_", threadit, " sends the body for url: " & sendob.url & " to: " & sendob.targetip
proc haltBodysenderInstance() =
# to gratiously halt the worker
var sendob: Sendable
sendob.url = ""
send_chob.send(sendob)
proc simulateRequest(urlst, webmethodst, sourceipst, configst, last: string; timedoutbo: bool) =
# simulates the request of inet-user who want to retrieve a website
var reqob: Request
echo "Initializing request for: " & urlst
reqob.url = urlst
reqob.sourceip = sourceipst
reqob.webmethod = webmethodst
req_chob.send(reqob)
if urlst.len == 0: # the single worker of the statekeeper can be halted here
createPageState("", "","", false)
else:
createPageState("ID from: " & reqob.url, configst, last, timedoutbo)
# declare the threads
var
stateth: Thread[void]
pmakerthar: array[4, Thread[int]]
bsenderthar: array[2, Thread[int]]
echo "\pStarting the threads\p"
createThread(stateth, statekeeper)
# create threadpools where each worker has index firstit / secit
for firstit in 0 ..< pmakerthar.len:
createThread(pmakerthar[firstit], pagemaker, firstit)
for secit in 0 ..< bsenderthar.len:
createThread(bsenderthar[secit], bodysender, secit)
# simulate the sending of some webclient-requests
simulateRequest("www.xampl.com", "GET", "6.2.8.6", "some-config", "20260312", false)
simulateRequest("www.stuff.org", "POST", "8.1.4.6", "other-config", "20260225", false)
simulateRequest("www.gaming_addicts.com", "GET", "4.5.7.2", "big-config", "20260225", false)
# let processes complete before closing the show
sleep 3000
# stop the workers gratiously by sending empty channel-objects
echo "\pstopping the workers"
simulateRequest("","", "", "", "", false)
for it in 0..<pmakerthar.len:
haltPagemakerInstance()
for it in 0 ..< bsenderthar.len:
haltBodysenderInstance()
sleep 1000
echo "\pstop all threads"
joinThread stateth
# multiples get plural
joinThreads pmakerthar
joinThreads bsenderthar
echo "\pclose all channels"
wepa_chob.close()
req_chob.close()
send_chob.close()