python 等待 API 调用时的多线程
python multithreading while waiting for API call
详细说明:我正在使用 python 制作一个小应用程序,它可以抓取前 100 首歌曲列表并从中创建一个 spotify 播放列表。我的瓶颈是 spotify API 一次只能搜索一首歌曲(以获取其内部 spotify ID)。
简而言之:我尝试了混合结果的多线程。
供参考,这是搜索歌曲的作用,不完全相关:
def __search_song(self, song: str):
result = self.sp.search(song + " NOT Karaoke", limit=1, type="track")
try:
sid = result["tracks"]["items"][0]["uri"]
except IndexError:
pass
else:
self.song_list.append(sid)
初步实施:
def __populate_playlist(self, song_list: list, pid: str):
for song in song_list:
self.__search_song(song)
self.sp.playlist_add_items(pid, self.song_list)
这是正常执行,“一个接一个”,它运行良好,但速度很慢,并且由于 UI(Tkinter 需要不断刷新)而使 window 挂起。
使用线程和队列的多线程:
q = queue.Queue()
def __worker():
while True:
item = q.get()
q.task_done()
threading.Thread(target=__worker, daemon=True).start()
def __populate_playlist(self, song_list: list, pid: str):
for song in song_list:
q.put(self.__search_song(song))
q.join()
self.sp.playlist_add_items(pid, self.song_list)
这有效,但是比原来的速度稍快。它确实解决了程序似乎没有响应的问题,但速度不够快。
然后我尝试删除队列并实现无序线程。
def __populate_playlist(self, song_list: list, pid: str):
# multiprocessing support
threads = []
for song in song_list:
t = threading.Thread(target=self.__search_song, args=(song, ))
threads.append(t)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.sp.playlist_add_items(pid, self.song_list)
这非常快,我说的是从 23 秒减少到 8 秒。显然,这会产生意想不到的后果,即播放列表被打乱,不再是真正的前 100 名。
我的问题很简单,是我的队列实现有问题,还是使用队列系统本身就提供了这么多开销?这是我第一次在应用程序中实现多线程,所以我可能遗漏了一些东西。
要再次遍历用例,我真的不在乎哪个先完成,只要保持顺序即可。我想过存储列表的初始顺序并使用字典来保存它的顺序和 spotify ID,但我仍在考虑它的实际实现。
如前所述,如果您想要异步调用,则很难保证顺序。但是将 ID 映射到歌曲名称的简单实现是:
def __search_song(self, song: str):
result = self.sp.search(song + " NOT Karaoke", limit=1, type="track")
try:
sid = result["tracks"]["items"][0]["uri"]
except IndexError:
pass
else:
self.song_list.append(sid)
self.song_to_sid[song] = sid
鉴于字典 song_to_sid
已在您 class 中实例化。
如果您随后只是遍历您的第一张地图(如果按顺序排列),您可以附加映射的 sid 以获得有序的播放列表。
拥有 运行 __populate_playlist
功能后,您可以执行以下操作:
top_hundred_playlist = []
for song_id in self.song_list:
top_hundred_playlist(self.song_to_sid[song_id])
在 Albin Sidås 的帮助下,最终代码看起来像这样:
def __search_song(self, song: str):
"""searches a song by a string song name returns spotify URI id"""
result = self.sp.search(song + " NOT Karaoke", limit=1, type="track")
try:
sid = result["tracks"]["items"][0]["uri"]
except IndexError:
self.song_to_sid[song] = ""
else:
self.song_to_sid[song] = sid
def __populate_playlist(self, song_list: list, pid: str):
# multiprocessing support
top_hundred_ids = []
threads = []
for song in song_list:
self.song_list_names.append(song)
t = threading.Thread(target=self.__search_song, args=(song, ))
threads.append(t)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
for song_id in self.song_list_names:
song_value = self.song_to_sid[song_id]
if song_value != "":
top_hundred_ids.append(song_value)
self.sp.playlist_add_items(pid, top_hundred_ids)
它最终只比完全异步解决方案慢了一秒钟,所以我认为这是成功的方法。我仍然愿意接受任何关于队列系统开销的澄清,但总而言之,这很棒。
详细说明:我正在使用 python 制作一个小应用程序,它可以抓取前 100 首歌曲列表并从中创建一个 spotify 播放列表。我的瓶颈是 spotify API 一次只能搜索一首歌曲(以获取其内部 spotify ID)。
简而言之:我尝试了混合结果的多线程。
供参考,这是搜索歌曲的作用,不完全相关:
def __search_song(self, song: str):
result = self.sp.search(song + " NOT Karaoke", limit=1, type="track")
try:
sid = result["tracks"]["items"][0]["uri"]
except IndexError:
pass
else:
self.song_list.append(sid)
初步实施:
def __populate_playlist(self, song_list: list, pid: str):
for song in song_list:
self.__search_song(song)
self.sp.playlist_add_items(pid, self.song_list)
这是正常执行,“一个接一个”,它运行良好,但速度很慢,并且由于 UI(Tkinter 需要不断刷新)而使 window 挂起。
使用线程和队列的多线程:
q = queue.Queue()
def __worker():
while True:
item = q.get()
q.task_done()
threading.Thread(target=__worker, daemon=True).start()
def __populate_playlist(self, song_list: list, pid: str):
for song in song_list:
q.put(self.__search_song(song))
q.join()
self.sp.playlist_add_items(pid, self.song_list)
这有效,但是比原来的速度稍快。它确实解决了程序似乎没有响应的问题,但速度不够快。
然后我尝试删除队列并实现无序线程。
def __populate_playlist(self, song_list: list, pid: str):
# multiprocessing support
threads = []
for song in song_list:
t = threading.Thread(target=self.__search_song, args=(song, ))
threads.append(t)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.sp.playlist_add_items(pid, self.song_list)
这非常快,我说的是从 23 秒减少到 8 秒。显然,这会产生意想不到的后果,即播放列表被打乱,不再是真正的前 100 名。
我的问题很简单,是我的队列实现有问题,还是使用队列系统本身就提供了这么多开销?这是我第一次在应用程序中实现多线程,所以我可能遗漏了一些东西。
要再次遍历用例,我真的不在乎哪个先完成,只要保持顺序即可。我想过存储列表的初始顺序并使用字典来保存它的顺序和 spotify ID,但我仍在考虑它的实际实现。
如前所述,如果您想要异步调用,则很难保证顺序。但是将 ID 映射到歌曲名称的简单实现是:
def __search_song(self, song: str):
result = self.sp.search(song + " NOT Karaoke", limit=1, type="track")
try:
sid = result["tracks"]["items"][0]["uri"]
except IndexError:
pass
else:
self.song_list.append(sid)
self.song_to_sid[song] = sid
鉴于字典 song_to_sid
已在您 class 中实例化。
如果您随后只是遍历您的第一张地图(如果按顺序排列),您可以附加映射的 sid 以获得有序的播放列表。
拥有 运行 __populate_playlist
功能后,您可以执行以下操作:
top_hundred_playlist = []
for song_id in self.song_list:
top_hundred_playlist(self.song_to_sid[song_id])
在 Albin Sidås 的帮助下,最终代码看起来像这样:
def __search_song(self, song: str):
"""searches a song by a string song name returns spotify URI id"""
result = self.sp.search(song + " NOT Karaoke", limit=1, type="track")
try:
sid = result["tracks"]["items"][0]["uri"]
except IndexError:
self.song_to_sid[song] = ""
else:
self.song_to_sid[song] = sid
def __populate_playlist(self, song_list: list, pid: str):
# multiprocessing support
top_hundred_ids = []
threads = []
for song in song_list:
self.song_list_names.append(song)
t = threading.Thread(target=self.__search_song, args=(song, ))
threads.append(t)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
for song_id in self.song_list_names:
song_value = self.song_to_sid[song_id]
if song_value != "":
top_hundred_ids.append(song_value)
self.sp.playlist_add_items(pid, top_hundred_ids)
它最终只比完全异步解决方案慢了一秒钟,所以我认为这是成功的方法。我仍然愿意接受任何关于队列系统开销的澄清,但总而言之,这很棒。