The Synergy program differs from the usual multithreading: the cooperative program is non-preemptive.
When a co-program is running, it cannot be stopped from the outside. It stops only when the co-program explicitly calls yield.
When there is no preemptive, programming becomes much easier without having to be crazy about syncing bugs.
All synchronization in the program is explicit, just make sure that a co-program calls yield outside of its critical region.
For such non-preemptive multithreading, as long as one thread invokes a blocking operation, the entire program stops before the operation is complete.
Here's an interesting way to solve this problem: download several remote files over HTTP.
The following example tests the download of the LUA source code, which uses the luasocket module:
LocalSocket =require "Socket"LocalHost ="www.lua.org"LocalFile1 ="/ftp/lua-5.3.3.tar.gz"LocalHTTP ="http/1.0\r\nuser-agent:wget/1.12 (LINUX-GNU) \r\naccept: */*\r\nhost:www.lua.org\r\nconnection:keep-alive\r\n \ r \ n"LocalSock =assert(Socket.connect (Host1, the)) Sock:send ("GET".. file1. HTTP)Repeat LocalChunk,status,partial = Sock:receive (4096) Print("chuck:size:",String.len(Chunkorpartial), statusor "OK")untilStatus = ="closed"sock:close ()
Under normal circumstances, the receive function returns a string. If an error occurs, nil is returned with an additional error code and the content read before the error (partial).
Next download a few files, the most stupid way is to download, but too slow. The program spends most of its time waiting for data to be received.
More specifically, the time is spent on the receive blocking call.
The workaround is that when a link has no data available, the program can read the data from other links.
It is clear that the collaboration program provides an easy way to build this concurrent download.
A new thread is created for each download task, and as long as a thread has no data available, it transfers control to a simple scheduler.
This scheduler calls other download threads.
Before rewriting the program in a synergistic program, rewrite the previous download code:
functionreceive (Connection)LocalS,status,partial = Connection:receive (2^Ten) returnSorPartial,statusEnd functionDownload (host,file)LocalSock =assert(Socket.connect (Host, the)) LocalCount =0 --record the number of bytes receivedSock:send ("GET".. FILE: HTTP)Repeat LocalChunk,status =receive (sock) Count= Count + #ChunkuntilStatus = ="closed"sock:close ()Print(File,count)EndDownload (host,file1)
This is a function wrapper to download a file, just call download. It takes about 18 seconds to download a file separately.
In the case of concurrency, however, the receive code cannot be blocked, so it should hang when it does not have the data available:
functionreceive (Connection) Connection:settimeout (0)--set to non-blocking LocalS,status,partial = Connection:receive (2^Ten) ifStatus = ="Timeout" ThenCoroutine.yield(Connection)End returnSorPartial,statusEnd
The settimeout call makes the operation of this link not blocked.
Even in the case of timeouts, the connection returns what has been read, which is recorded in the partial variable.
The following code uses table threads to save all running threads for the scheduler.
The Get function guarantees that each download task is executed in a separate thread.
The scheduler itself is essentially a loop that iterates through all the threads and wakes up their execution one after the other.
When the thread is finished, it is removed from the list.
Threads = {}--table that holds active threadsfunctionget (Host,file)LocalCO =coroutine.create(function()--Create a collaboration programDownload (host,file)End) Table.insert(Threads,co)--Insert ListEnd functionDispatch ()Locali =1 while true Do ifThreads[i] = =Nil Then --no threads. ifthreads[1] ==Nil Then Break End --is the table an empty table?i =1 --start the loop again End LocalStatus,res =Coroutine.resume(Threads[i])--Wake the thread to continue downloading the file if notRes Then --whether the thread has completed the task Table.remove(threads,i)--Remove the I thread from list ElseI= i +1 --Check the next thread End EndEnd
Finally, the main program needs to create all the threads and invoke the scheduler.
LocalFile1 ="/ftp/lua-5.3.3.tar.gz"LocalFile2 ="/ftp/lua-5.3.2.tar.gz"LocalFile3 ="/ftp/lua-5.3.1.tar.gz"LocalFile4 ="/ftp/lua-5.3.0.tar.gz"LocalFile5 ="/ftp/lua-5.2.4.tar.gz"LocalFile6 ="/ftp/lua-5.2.3.tar.gz"LocalFile7 ="/ftp/lua-5.2.2.tar.gz"LocalFile8 ="/ftp/lua-5.2.1.tar.gz"LocalFile9 ="/ftp/lua-5.2.0.tar.gz"get (Host,file1) get (Host,file2) get (Host,file3) get (host,file4) get (host,file5) get (Host,file6) get (Host,file7) Get (Host,file8) get (Host,file9) dispatch ()--Main Loop
Downloading 9 files simultaneously takes 36 seconds and is much faster than downloading 9 files in a serial.
But it found that CPU usage ran to 98%.
To avoid this situation, you can use the Select function in Luasocket (Socket.select (RECVT, Sendt [, timeout]).
To apply this function in the current implementation, you need to fix the schedule if you are stuck in a blocking state while waiting:
functiondispatch_new ()Locali =1 LocalTimedOut = {}--RECVT Collection while true Do ifThreads[i] = =Nil Then --no threads. ifthreads[1] ==Nil Then Break End --table is empty tablei =1 --start the loop againTimedOut = {}--walk through all threads and start a new round of traversal End LocalStatus,res =Coroutine.resume(Threads[i])--Wake the thread to continue downloading the file if notRes Then --If the res is complete nil, only the status one return value is true. Otherwise res is the yield-passed parameter connection. Table.remove(threads,i)--Remove the I thread from list ElseI= i +1 --Check the next threadtimedout[#timedout +1] =Resif#timedout = = #threads Then --are all the threads blocked? Socket.Select(timedout)--if the thread has data, it returns End End EndEnd
... ...
Dispatch_new ()--Main Loop
Receive passes the time-out connection through yield to the resume res. If all connections are timed out, the scheduler uses Select to wait for the status of these links to change.
After the final run of the modified version of the program, 9 file downloads took 24 seconds, the CPU occupancy rate of less than 5%.
Chapter9_4 non-preemptive multi-threaded