A way to almost correctly trigger a function periodically

时间:2015-09-09 09:08:19

标签: python time scheduled-tasks milliseconds

I would like to trigger a function periodically, lets say, each 5s. The function will not consume many CPU time, so I expect that even if it is a blocking function it will not be a problem. I know that python will never be a real-time language, even if it run on a real-time os, I just want to avoid delays to significantly add up and desynchronize my data acquisition.

I have compared two different ways to resolve it and I am a little surprised. Using threading module (solution found on SO), there is a a delay adding up, and this is significant even on a short horizon of 100s.

import time
import threading

# Function to trigger
def q(t0, t1):
    print("{}: {}".format(t1, t1-t0))

# Scheduler:
def f(t):
    t0 = time.clock()
    q(t, t0)
    threading.Timer(5, f, (t0, )).start()

f(time.clock())

Output is:

1.2439980979269082e-06: 6.219990489634541e-07
5.003068943307586: 5.003067699309487
10.009677372203297: 5.006608428895712
15.017115547830327: 5.00743817562703
20.02463987032564: 5.007524322495312
25.03211007890369: 5.007470208578049
30.039602057448455: 5.007491978544767
35.04705640505075: 5.007454347602298
40.0545011116678: 5.0074447066170436
45.06215045597195: 5.007649344304156
50.069445571817724: 5.007295115845771
55.076933507368665: 5.007487935550941
60.0844149119296: 5.007481404560934
65.09188791950338: 5.007473007573779
70.09936870206525: 5.007480782561871
75.10685632661668: 5.00748762455143
80.11432187020186: 5.007465543585184
85.12207042335432: 5.007748553152453
90.12924456038506: 5.007174137030745
95.13673964892507: 5.007495088540011
100.1442070585074: 5.007467409582333
105.15168068808023: 5.007473629572829

When I solve my problem using old-fashion C style code for micro-controller:

import time

# Function to trigger:
def q(t0, t1):
    print("{}: {}".format(t1, t1-t0))

# Scheduler:
t0 = time.clock()
while True:
    t1 = time.clock()
    if (t1-t0)>=5:
        q(t0, t1)
        t0 = t1

I get:

5.0000009329985735: 5.0
10.000001243998097: 5.000000310999524
15.000001243998097: 5.0
20.0000012439981: 5.000000000000002
25.0000012439981: 5.0
30.0000012439981: 5.0
35.0000012439981: 5.0
40.0000012439981: 5.0
45.0000012439981: 5.0
50.0000012439981: 5.0
55.0000012439981: 5.0
60.0000012439981: 5.0
65.0000012439981: 5.0
70.0000012439981: 5.0
75.0000012439981: 5.0
80.0000012439981: 5.0
85.0000012439981: 5.0
90.0000012439981: 5.0
95.0000012439981: 5.0
100.0000012439981: 5.0
105.0000012439981: 5.0

Which seems to be really more reliable. I know that there might be a float point issue in those displays, but it cannot explain the difference between the two solutions.

  • Is it because threading module relies on time.sleep function?

In my point of view, I would say, second option is better because it avoids thread and recursion, even if it uses a endless loop. - Is there a better way to achieve that goal?

Going deeper in my problem: - How can I synchronize the trigger on a defined timestamps? Will the sched module be helpful?

1 个答案:

答案 0 :(得分:1)

问题中描述的第二种方法会产生一个繁忙的循环(占用所有CPU)。

比使用线程更简单的方法是使用旧的select 系统调用:

import time
import select
import socket # win

s = socket.socket() # win

# Function to trigger:
def q(t0, t1):
    print("{}: {}".format(t1, t1-t0))

# Scheduler:
t0 = time.time()
while True:
    select.select([s],[],[],5) #wait for 5 seconds (dummy socket for win)
    t1 = time.time()
    q(t0, t1)
    t0 = t1

结果:

1441792007.3: 5.00524997711
1441792012.3: 5.00554990768
1441792017.31: 5.00520896912
1441792022.31: 5.00508904457
1441792027.32: 5.00518012047
1441792032.32: 5.00523996353
1441792037.33: 5.00523781776

另外,time.clock在Linux上对我不起作用。文档说:

  

方法clock()将当前处理器时间作为Unix上以秒为单位表示的浮点数返回。精度取决于同名C函数的精度,但无论如何,这是用于对Python或时序算法进行基准测试的函数。

     

在Windows上,此函数返回自第一次调用此函数以来经过的挂钟秒,作为浮点数,基于Win32函数QueryPerformanceCounter。

也许你在Windows上?或者你是Linux,但第二个例子是 使CPU忙,time.clock确实给出了一个数字,而我的代码总是为0,因为没有真正涉及CPU周期。