在使用vrep的thread scirpt的时候我竟然会对这么简单的一个问题产生疑惑,但是为了要多学习,多进步,还是把这个问题记录一下:
一个文件里有许多的函数。一个函数执行完了,会执行到另外一个函数。在执行某个函数的时候,中间也会跑出去执行别的函数,这怎么用线程的概念来解释它? 这个问题可以用时间片轮转来解释:
在单核cpu中程序是片段执行,程序员就必须理解,在自己的程序运行时不是独一无二的,我们看似很顺畅的工作,其实是由一个个的执行片段构成的,我们眼中相邻的两条语句甚至同一个语句中两个不同的运算符之间,都有可能插入其他线程或进程的动作。具体查看时间片轮转调度
在vrep thread scirpt中,每隔2ms就会自动切换到另外一个线程,2ms之后在切换回来。sim.setThreadAutomaticSwitch()、sim.switchthread()等函数就是用来控制这个过程的。sim.setThreadAutomaticSwitch()能够禁止自动切换。sim.switchthread()指明在哪个时刻从一个线程切换到另外一个线程。我想这可能和vrep的thread scirpt管理机制有关,毕竟一个scene里可以有好几个的thread scirpt,每一个thread scirpt都是需要执行的。
接下来稍微具体的介绍一下这个过程。
1.线程和协程的概念
(1)线程
首先复习一下多线程。我们都知道线程——Thread。每一个线程都代表一个执行序列。当我们在程序中创建多线程的时候,看起来,同一时刻多个线程是同时执行的,不过实质上多个线程是并发的,因为只有一个CPU,所以实质上同一个时刻只有一个线程在执行。在一个时间片内执行哪个线程是不确定的,我们可以控制线程的优先级,不过真正的线程调度由CPU的调度决定。
(2)协程
那什么是协程呢?协程跟线程都代表一个执行序列。不同的是,协程把线程中不确定的地方尽可能的去掉,执行序列间的切换不再由CPU隐藏的进行,而是由程序显式的进行。
所以,使用协程实现并发,需要多个协程彼此协作。
Lua的多线程并不是真的多线程,而是协程——Lua的线程和状态、Lua中的协同程序
协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停。[1]
Lua不支持真正的多线程,而是一种协作式的多线程,彼此之间协作完成,并不是抢占完成任务,由于这种协作式的线程,因此可以避免由不可预知的线程切换所带来的问题;另一方面,Lua的多个状态之间不共享内存,这样便为Lua中的并发操作提供了良好的基础。
coroutine运行一系列的协作多线程。每个coroutine相当于一个thread。通过yield-resume实现在不同thread之间切换控制权。但是,跟常规的多线程不同,coroutine是非抢占式的。一个coroutine在运行的时候,不可能被其他的coroutine从外部将其挂起,只有由其本身显式地调用yield才会挂起,并交出控制权。对一些程序来说,这没有任何问题,相反,因为非抢占式的缘故,程序变得更加简单。我们不需要担心同步问题的bug,因为在threads之间的同步都是显式的。我们只需要保证在对的时刻调用yield就可以了
2.1Non-threaded child scripts 单线程脚本/非线程脚本
单线程的程序执行时,是按照顺序执行的。The script is non-threaded. In that case, it behaves as a function that is called in each simulation step. This also means, it is inherently synchronized with the main simulation loop.
2.2 Threaded child scripts 多线程子脚本
每一个脚本都在一个thread里启动。
The script is threaded. In that case, you can precisely time your code to be synchronized with the main simulation loop with following few instructions:- sim.SetThreadAutomaticSwitch()
- sim.SwitchThread()
simSetThreadResumeLocation / sim.setThreadResumeLocation 可以决定位置和脚本的执行顺序
simSetThreadSwitchTiming / sim.setThreadSwitchTiming 指定当前线程在切换到另外一个线程的之前,此线程执行多长时间
a threaded child script is running more like a coroutine than a thraditional thread:
By default, when a threaded child script is launched, it will execute about 2ms. Then V-REP will interrupt or pause it, and only resume it in next simulation step (i.e. when the simulation time has become t=t+dt). It will resume for about 2ms, and be interrupted again. And resume again in next simulation step, and so on, and so forth.
代码片段1:没有线程切换函数,while loop会浪费宝贵的计算资源。需要再while loop里执行完一个2ms
function sysCall_threadmain() -- Put some initialization code here: sensorHandleFront=sim.getObjectHandle("DoorSensorFront") sensorHandleBack=sim.getObjectHandle("DoorSensorBack") motorHandle=sim.getObjectHandle("DoorMotor") -- Here we execute the regular thread code: while sim.getSimulationState()~=sim.simulation_advancing_abouttostop do resF=sim.readProximitySensor(sensorHandleFront) resB=sim.readProximitySensor(sensorHandleBack) if ((resF>0)or(resB>0)) then sim.setJointTargetVelocity(motorHandle,-0.2) else sim.setJointTargetVelocity(motorHandle,0.2) end -- this loop wastes precious computation time since we should only read new -- values when the simulation time has changed (i.e. in next simulation step). end end function sysCall_cleanup() -- Put some clean-up code here: end
默认情况下,vrep每隔两毫秒就会自动从一个线程切换到另外一个线程。然后就会在下一个simulation pass恢复执行。
sim.switchthread() 能够缩短时间,提高效率(把2ms缩短,可以和代码1进行对比 )
V-REP uses threads to mimic(模拟) the behavior of coroutines, instead of using them traditionally, which allows for a great deal of flexibility and control: by default a threaded child script will execute for about 1-2 milliseconds before automatically switching to another thread. This default behavior can be changed with the sim.setThreadSwitchTiming or sim.setThreadAutomaticSwitch. Once the current thread was switched, it will resume execution at next simulation pass (i.e. at a time currentTime+simulationTimeStep). The thread switching is automatic (occurs after the specified time), but the sim.switchThread command allows to shorten that time when needed. Using above three commands, an excellent synchronization with the main simulation loop can be achieved. Following code (handling the automatic sliding doors from above's example) shows child script synchronization with the main simulation loop
代码片段2:使用线程切换函数sim.setThreadAutomaticSwitch(false)、sim.switchthread()
对代码片段1进行改变,来得到更好的效果:不会浪费宝贵的计算资源而且能够和仿真同步运行
function sysCall_threadmain()
-- Put some initialization code here:
sim.setThreadAutomaticSwitch(false) -- disable automatic thread switches
sensorHandleFront=sim.getObjectHandle("DoorSensorFront")
sensorHandleBack=sim.getObjectHandle("DoorSensorBack")
motorHandle=sim.getObjectHandle("DoorMotor")
-- Here we execute the regular thread code:
while sim.getSimulationState()~=sim.simulation_advancing_abouttostop do
resF=sim.readProximitySensor(sensorHandleFront)
resB=sim.readProximitySensor(sensorHandleBack)
if ((resF>0)or(resB>0)) then
sim.setJointTargetVelocity(motorHandle,-0.2)
else
sim.setJointTargetVelocity(motorHandle,0.2)
end
sim.switchThread() -- Explicitely switch to another thread now!
-- from now on, above loop is executed once every time the main script is about to execute.
-- this way you do not waste precious computation time and run synchronously.
-- 以上的循环在main scirpt里只会执行一次。这样做不会浪费宝贵的计算资源而且能够和仿真同步运行
end
end
3.与thread scirpt相关的常用函数
simSetThreadAutomaticSwitch / sim.setThreadAutomaticSwitch
Description | Allows to temporarily forbid thread switches. If the current script doesn't run in a thread (i.e. if it runs in the application main thread), this function has no effect. By default, V-REP doesn't use "regular" threads, but something similar to hybrid threads (which behave like coroutines, but can also behave like regular threads). This allows much more flexibility and execution control of the threads. For complete control over the switching moment, see also sim.getThreadAutomaticSwitch, sim.setThreadSwitchTiming, sim.switchThread, sim.setThreadIsFree and sim.setThreadResumeLocation. |
C synopsis | - |
C parameters | - |
C return value | - |
Lua synopsis | number result=sim.setThreadAutomaticSwitch(Boolean switchIsAutomatic) |
Lua parameters |
switchIsAutomatic: if true, the thread will be able to automatically switch to another thread, otherwise the switching is temporarily disabled.
|
Lua return values |
result: 1 if the command was successful, 0 if it didn't have an effect (e.g. because the function was called from the main or application thread), or -1 in case of an error.
|
simSwitchThread / sim.switchThread 在某个时刻将当前线程切换到另外一个线程
Description | Allows specifying the exact moment at which the current thread should switch to another thread. If the current script doesn't run in a thread (i.e. if it runs in the application main thread), this function has no effect. By default, V-REP doesn't use "regular" threads, but something similar to hybrid threads (which behave like coroutines, but can also behave like regular threads). This allows much more flexibility and execution control of the threads: each thread (except for the main or application thread) has a switch timing associated, which specifies how long the thread will run before switching to other threads. By default this value is 2 millisecond, but can be modified with sim.setThreadSwitchTiming. That timing can be shortened with sim.switchThread. Use with care when calling this function from a plugin. See also the sim.setThreadAutomaticSwitch, sim.setThreadResumeLocation and sim.setThreadIsFree functions. |
C synopsis | simInt simSwitchThread() |
C parameters | None |
C return value | 1 if the thread was switched (the current thread gave control to other threads until the next calculation pass), 0 if it was not switched (e.g. because the function was called from the main or application thread, or from a thread started by the user), or -1 in case of an error. |
Lua synopsis | number result=sim.switchThread() |
Lua parameters | None |
Lua return values | result: 1 if the thread was switched (the current thread gave control to other threads until the next calculation pass), 0 if it was not switched (e.g. because the function was called from the main or application thread), or -1 in case of an erro |
simSetThreadSwitchTiming / sim.setThreadSwitchTiming指定当前线程在切换到另外一个线程的之前,此线程执行多长时间
Description | Allows specifying a switching time for the thread in which the current script runs. If the current script doesn't run in a thread (i.e. if it runs in the application main thread), this function has no effect. By default, V-REP doesn't use "regular" threads, but something similar to hybrid threads (which behave like coroutines, but can also behave like regular threads). This allows much more flexibility and execution control of the threads: each thread (except for the main or application thread) has a switch timing associated, which specifies how long the thread will run before switching to other threads (the execution duration per calculation pass). By default this value is 2 millisecond, but this function allows changing that value (on a thread-basis). Acceptable values are between 0 and 200. For complete control over the switching moment, see also sim.setThreadAutomaticSwitch, sim.switchThread, sim.setThreadIsFree and sim.setThreadResumeLocation. |
C synopsis | - |
C parameters | - |
C return value | - |
Lua synopsis | number result=sim.setThreadSwitchTiming(number deltaTimeInMilliseconds) |
Lua parameters |
deltaTimeInMilliseconds: desired non-stop execution time before a switching occurs. A value of x will let the thread execute for x-1 to x milliseconds before switching to another thread.
|
Lua return values |
result: 1 if the timing was set, 0 if it was not set (e.g. because the function was called from the main or application thread), or -1 in case of an error.
|
simResumeThreads / sim.resumeThreads 恢复某个线程
Description | In conjunction with sim.setThreadResumeLocation, sim.resumeThreads allows specifying when and in which order threads are resumed. By default, V-REP doesn't use "regular" threads, but something similar to hybrid threads (which behave like coroutines, but can also behave like regular threads). This allows much more flexibility and execution control of the threads. Once a thread switched to another thread, it will resume execution at the beginning of next simulation pass by default. In order to also have full synchronization control between threads, you can assign a resume location and order to each thread. When sim.resumeThreads(x) is called, all threads that were assigned a resume location of x will be resumed. See also sim.setThreadResumeLocation, sim.setThreadSwitchTiming, sim.switchThread and sim.setThreadIsFree. This function can only be called in the main script. |
simSetThreadResumeLocation / sim.setThreadResumeLocation
Description | Allows specifying when and in which order child script threads are resumed. If the current script doesn't run in a thread (i.e. if it runs in the application main thread), this function has no effect. By default, V-REP doesn't use "regular" threads, but something similar to hybrid threads (which behave like coroutines, but can also behave like regular threads). This allows much more flexibility and execution control of the threads. Once a thread switched to another thread, it will resume execution when the main script calls sim.resumeThreads with the corresponding argument, which represents a child script thread resume location. In order to also have full synchronization control between threads, you can assign a resume location and order to each thread with this function. See also sim.setThreadSwitchTiming, sim.setThreadAutomaticSwitch, sim.switchThread and sim.setThreadIsFree. |
C synopsis | - |
C parameters | - |
C return value | - |
Lua synopsis | number result=sim.setThreadResumeLocation(number location,number order) |
Lua parameters |
location: a
child script thread resume location.
order: a
script resume or execution order.
|
Lua return values |
result: 1 if the command was applied, 0 if it was not (e.g. because the function was called from the main or application thread), or -1 in case of an error.
|
4.举例说明:
创建分成以下四种情况:
4.1
function sysCall_threadmain() local lastSimulationTime=sim.getSimulationTime() local simulationStep=sim.getSimulationTimeStep() while true do local cnt=0 -- Inner loop: while sim.getSimulationTime()==lastSimulationTime do cnt=cnt+1 end printf('Inner loop executed %i times for one simulation step.',cnt) local dt=sim.getSimulationTime()-lastSimulationTime printf('Thread was interrupted %i times since last simulation step.',math.floor((dt/simulationStep)+0.5)) lastSimulationTime=sim.getSimulationTime() end end
4.2
function sysCall_threadmain() local lastSimulationTime=sim.getSimulationTime() local simulationStep=sim.getSimulationTimeStep() while true do local cnt=0 -- Inner loop: while sim.getSimulationTime()==lastSimulationTime do cnt=cnt+1 sim.switchThread() end printf('Inner loop executed %i times for one simulation step.',cnt) local dt=sim.getSimulationTime()-lastSimulationTime printf('Thread was interrupted %i times since last simulation step.',math.floor((dt/simulationStep)+0.5)) lastSimulationTime=sim.getSimulationTime() end end
function sysCall_threadmain() local lastSimulationTime=sim.getSimulationTime() local simulationStep=sim.getSimulationTimeStep() while true do local cnt=0 -- Inner loop: while sim.getSimulationTime()==lastSimulationTime do for j=1,100000,1 do end -- delay loop cnt=cnt+1 sim.switchThread() end printf('Inner loop executed %i times for one simulation step.',cnt) local dt=sim.getSimulationTime()-lastSimulationTime printf('Thread was interrupted %i times since last simulation step.',math.floor((dt/simulationStep)+0.5)) lastSimulationTime=sim.getSimulationTime() end end
没有关闭线程的自动切换,又增加了for 循环,内循环只进行了一次,但是由于线程会自动切换,中断发生了许多次。
4.4
function sysCall_threadmain() sim.setThreadAutomaticSwitch(false) local lastSimulationTime=sim.getSimulationTime() local simulationStep=sim.getSimulationTimeStep() while true do local cnt=0 -- Inner loop: while sim.getSimulationTime()==lastSimulationTime do for j=1,100000,1 do end -- delay loop cnt=cnt+1 sim.switchThread() end printf('Inner loop executed %i times for one simulation step.',cnt) local dt=sim.getSimulationTime()-lastSimulationTime printf('Thread was interrupted %i times since last simulation step.',math.floor((dt/simulationStep)+0.5)) lastSimulationTime=sim.getSimulationTime() end end
参考:
Clarification about "Thread related functionality"
how to simulate precise interrupt timing in V-Rep?