1. time.sleep()假死(挂起)的原因与解决办法
最近,使用chatGPT帮着写程序,完成通过API获取天气数据的程序,运行起来后出现了状况:莫名其妙的的假死(程序被挂起来,不执行了),而且在Linux环境中更容易挂起。
程序详见作者的博文” 使用chatGPT开发获取格点天气数据“。
起初,我的目的是开发个近期使用,临时获取气象数据的程序,而业务应用有专业人员在其应用中实现,未考虑线程开发内容,在chatGPT提醒下,有针对性写出线程服务程序。
注:都是chatGPT的方案,这次,增加了线程内容。
主要原因:
尽量避免在主线程中使用 sleep() 函数,因为这样会导致程序阻塞,无法被唤醒。如果必须在主线程中使用 sleep() 函数,可以考虑将任务提交到一个独立的线程中。
解决办法:
采用线程编程方法。
通过与chatGPT交流,最后,在我强力要求下,完成了”start_thread():“函数,主要代码如下:
import requests
import csv
import time
import datetime
import threading
# API接口
url = "https://devapi.qweather.com/v7/grid-weather/now"
# 请求参数,location为经纬度坐标,key为API密钥
params = {
"location": "103.59984,36.18057",
"key": "****************"
}
fieldnames = [ 'getTime', 'obsTime', 'temp', 'icon', 'text', 'wind360', 'windDir', 'windScale',
'windSpeed', 'humidity', 'precip', 'pressure', 'cloud', 'dew' ,'status']
# csv文件名
filename = 'qweather_data.csv'
logname = 'log2.txt'
time_status = ["00", "30"]
# 获取天气数据
def get_weather_data():
while True:
try:
# 获取当前时间
current_time = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
# 判断是否为整点或半点
if current_time.split(":")[1] in time_status:
# 发送API请求并获取返回结果
response = requests.get(url, params=params)
result = response.json()
# 提取关键数据并写入csv文件
now_data = result['now']
......
# 等待到整点或半点
now = time.localtime()
if now.tm_min < 30:
wait_time = (30 - now.tm_min) * 60 - now.tm_sec
else:
wait_time = (60 - now.tm_min) * 60 - now.tm_sec
print(f"{current_time}: Server is Runing, Wait_time: {wait_time}s\n")
time.sleep(wait_time)
except requests.exceptions.ConnectionError as e:
print(f"{current_time}: {e}. Retrying in 5 seconds...")
....
now = time.localtime()
if now.tm_min < 5:
wait_time = (5 - now.tm_min) * 60 - now.tm_sec
else:
wait_time = (35 - now.tm_min) * 60 - now.tm_sec
time.sleep(wait_time)
# time.sleep(30)
except Exception as e:
print(f"{current_time}: {e}.")
break
def start_thread():
t = threading.Thread(target=get_weather_data)
print('start server ... ')
t.start()
t.join()
start_thread()
在start_thread()函数中,我们创建了一个新的线程t,并将其启动。t的目标函数为get_weather_data(),也就是获取天气数据的函数。然后,我们调用了t.join(),等待线程t结束。因为我们的线程t永不结束,所以这里实际上不会有任何作用。
这个程序会在每个整点和半点获取一次天气数据,并打印获取成功的消息。在获取数据时发生异常,会打印异常信息。注意,为了避免在不必要的时间内占用CPU资源,我们使用time.sleep()函数在整点和半点之间等待一定时间。
2. 小结
- 使用time.sleep(),最好使用线程方法;
- 合理的引导chatGPT交流,能快速帮助我们解决问题。
3. chatGPT交流节选
chatGPT分析源代码
明白了,那么这个问题可能和Linux环境的一些特殊性有关,比如Linux中对于进程的限制等。你可以尝试以下方法来解决这个问题:
- 检查你的代码是否有死循环或死锁等问题,如果有可能会导致程序无法正常退出。
- 使用
nohup
命令在后台运行程序,并且输出日志到文件中,这样即使你关闭终端也可以让程序一直运行下去。例如:nohup python3 your_program.py > log.txt &
- 检查Linux环境中对于进程的限制,例如是否有进程数、文件打开数等的限制,如果有可以适当修改这些限制。你可以通过
ulimit -a
命令来查看当前系统的限制情况。 - 使用一些系统级别的进程管理工具来监控你的程序,例如
systemd
或supervisor
等,这些工具可以帮助你监控程序的状态并在程序异常退出时自动重启程序。
解释sleep原理:
在Python中,sleep函数是用来阻塞程序一段时间的方法。当程序调用sleep函数时,它会挂起当前线程,让CPU去执行其他线程或进程,等待指定的时间后,再唤醒当前线程继续执行。
sleep函数的阻塞是一种比较暴力的方式,它会占用当前线程的CPU时间片,使得其他线程无法获得CPU时间片执行任务。因此,如果在主线程中使用sleep函数,可能会导致整个程序的性能下降。
为了避免sleep函数阻塞主线程,我们可以使用线程来解决。在使用线程的情况下,可以在子线程中执行耗时的操作,而不会影响主线程的运行。在子线程中,可以使用sleep函数来等待一定的时间,而不会影响其他线程的运行。
下面是一个使用线程来解决sleep阻塞的例子:
import threading
import time
def worker():
while True:
print("Worker is running...")
time.sleep(1)
if __name__ == '__main__':
t = threading.Thread(target=worker)
t.start()
while True:
print("Main thread is running...")
time.sleep(1)
在这个例子中,我们创建了一个worker线程,它会一直运行并且每隔一秒钟输出一次。同时,主线程也会一直运行并且每隔一秒钟输出一次。在主线程中,我们使用sleep函数来阻塞一秒钟,而不是在worker线程中使用sleep函数。
这样做的好处是,worker线程可以在阻塞的同时,让CPU去执行主线程,避免了sleep函数阻塞主线程的问题。而且,使用线程还可以让程序具有更好的可扩展性,可以方便地增加或删除子线程。
需要注意的是,如果线程中存在共享资源,需要使用锁或者其他同步机制来保证线程安全。另外,如果线程中存在长时间的阻塞操作,例如网络I/O或者磁盘I/O,建议使用异步编程来避免阻塞。
在Linux环境中,主线程中使用sleep函数,是很容易造成程序挂起后,无法唤醒,有办法唤醒吗,请详细讲解。
在 Linux 环境中,使用 sleep 函数会将当前进程阻塞,让 CPU 休眠指定的时间,等待时间结束后再继续执行。如果在阻塞期间出现了某种信号(比如 SIGINT 或 SIGTERM),就可能无法在预期时间内唤醒程序。这是因为阻塞期间,进程无法处理信号。
为了解决这个问题,我们可以使用信号处理函数和 sigwait 函数来实现。 sigwait 函数允许我们在程序中等待一个或多个特定信号,并阻塞程序的执行,直到收到信号。
下面是一个示例代码,展示如何使用 sigwait 函数代替 sleep 函数:
import signal
import threading
def signal_handler(signum, frame):
"""信号处理函数"""
print('Received signal %d' % signum)
def wait_for_signal(signum):
"""等待信号的线程函数"""
signal.sigwait([signum])
# 注册信号处理函数
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 创建等待信号的线程
signal_thread = threading.Thread(target=wait_for_signal, args=(signal.SIGINT,))
signal_thread.start()
# 主线程继续执行其他操作
while True:
print('Doing something...')
在这个示例中,我们创建了一个等待信号的线程,使用 sigwait 函数阻塞线程的执行,等待 SIGINT 信号。当收到 SIGINT 信号时,信号处理函数会被调用,打印出一条消息。在主线程中,我们使用一个简单的循环打印一条消息,来模拟程序的其他操作。这样,我们就可以在主线程中继续执行其他操作,而不必使用阻塞的 sleep 函数。
需要注意的是,如果使用 sigwait 函数等待多个信号,那么当其中任意一个信号被触发时,阻塞才会被解除。因此,如果你需要等待多个信号,需要将信号处理函数分别注册到每个信号上,并在处理函数中设置标志来记录收到的信号。
参考:
louis. 进程池 阻塞式进程 和非阻塞式进程. 知乎 . 2022.05
肖永威. 使用chatGPT开发获取格点天气数据. CSDN博客. 2023.04