开工前我就觉得有什么不太对劲,感觉要背锅。这可不,上班第三天就捅锅了。
我们有个了不起的后台程序,可以动态加载模块,并以线程方式运行,通过这种形式实现插件的功能。而模块更新时候,后台程序自身不会退出,只会将模块对应的线程关闭、更新代码再启动,6 得不行。
于是乎我就写了个模块准备大展身手,结果忘记写退出函数了,导致每次更新模块都新创建一个线程,除非重启那个程序,否则那些线程就一直苟活着。
这可不行啊,得想个办法清理呀,要不然怕是要炸了。
那么怎么清理呢?我能想到的就是两步走:
找出需要清理的线程号 tid;
销毁它们;
找出线程ID
和平时的故障排查相似,先通过 ps 命令看看目标进程的线程情况,因为已经是 setName 设置过线程名,所以正常来说应该是看到对应的线程的。 直接用下面代码来模拟这个线程:
Python 版本的多线程
#coding: utf8
import threading
import os
import time
def tt():
info = threading.currentThread()
while True:
print 'pid: ', os.getpid()
print info.name, info.ident
time.sleep(3)
t1 = threading.Thread(target=tt)
t1.setName('OOOOOPPPPP')
t1.setDaemon(True)
t1.start()
t2 = threading.Thread(target=tt)
t2.setName('EEEEEEEEE')
t2.setDaemon(True)
t2.start()
t1.join()
t2.join() 输出:
root@10-46-33-56:~# python t.py pid: 5613 OOOOOPPPPP 139693508122368 pid: 5613 EEEEEEEEE 139693497632512 ...
可以看到在 Python 里面 输出的线程名就是我们设置的那样,然而 Ps 的结果却是令我怀疑人生:
root@10-46-33-56:~# ps -Tp 5613 PID SPID TTY TIME CMD 5613 5613 pts/2 0 python 5613 5614 pts/2 0 python 5613 5615 pts/2 0 python
正常来说不该是这样呀,我有点迷了,难道我一直都是记错了?用别的语言版本的多线程来测试下:
C 版本的多线程
#include<stdio.h>
#include<sys/syscall.h>
#include<sys/prctl.h>
#include<pthread.h>
void *test(void *name)
{
pid_t pid, tid;
pid = getpid();
tid = syscall(__NR_gettid);
char *tname = (char *)name;
// 设置线程名字
prctl(PR_SET_NAME, tname);
while(1)
{
printf("pid: %d, thread_id: %u, t_name: %s\n", pid, tid, tname);
sleep(3);
}
}
int main()
{
pthread_t t1, t2;
void *ret;
pthread_create(&t1, NULL, test, (void *)"Love_test_1");
pthread_create(&t2, NULL, test, (void *)"Love_test_2");
pthread_join(t1, &ret);
pthread_join(t2, &ret);
} 输出:
root@10-46-33-56:~# gcc t.c -lpthread && ./a.out pid: 5575, thread_id: 5577, t_name: Love_test_2 pid: 5575, thread_id: 5576, t_name: Love_test_1 pid: 5575, thread_id: 5577, t_name: Love_test_2 pid: 5575, thread_id: 5576, t_name: Love_test_1 ...
用 PS 命令再次验证:
root@10-46-33-56:~# ps -Tp 5575 PID SPID TTY TIME CMD 5575 5575 pts/2 0 a.out 5575 5576 pts/2 0 Love_test_1 5575 5577 pts/2 0 Love_test_2
这个才是正确嘛,线程名确实是可以通过 Ps 看出来的嘛!
不过为啥 Python 那个看不到呢?既然是通过 setName 设置线程名的,那就看看定义咯:
[threading.py]
class Thread(_Verbose):
...
@property
def name(self):
"""A string used for identification purposes only.
It has no semantics. Multiple threads may be given the same name. The
initial name is set by the constructor.
"""
assert self.__initialized, "Thread.__init__() not called"
return self.__name
@name.setter
def name(self, name):
assert self.__initialized, "Thread.__init__() not called"
self.__name = str(name)
def setName(self, name):
self.name = name
... 看到这里其实只是在 Thread 对象的属性设置了而已,并没有动到根本,那肯定就是看不到咯~
这样看起来,我们已经没办法通过 ps 或者 /proc/ 这类手段在外部搜索 python 线程名了,所以我们只能在 Python 内部来解决。
于是问题就变成了,怎样在 Python 内部拿到所有正在运行的线程呢?
threading.enumerate 可以完美解决这个问题!Why?
Because 在下面这个函数的 doc 里面说得很清楚了,返回所有活跃的 线程对象 ,不包括终止和未启动的。
[threading.py]
def enumerate():
"""Return a list of all Thread objects currently alive.
The list includes daemonic threads, dummy thread objects created by
current_thread(), and the main thread. It excludes terminated threads and
threads that have not yet been started.
"""
with _active_limbo_lock:
return _active.values() + _limbo.values() 因为拿到的是 Thread 的对象,所以我们通过这个能到该线程相关的信息!
请看完整代码示例:
#coding: utf8
import threading
import os
import time
def get_thread():
pid = os.getpid()
while True:
ts = threading.enumerate()
print '------- Running threads On Pid: %d -------' % pid
for t in ts:
print t.name, t.ident
print
time.sleep(1)
def tt():
info = threading.currentThread()
pid = os.getpid()
while True:
print 'pid: {}, tid: {}, tname: {}'.format(pid, info.name, info.ident)
time.sleep(3)
return
t1 = threading.Thread(target=tt)
t1.setName('Thread-test1')
t1.setDaemon(True)
t1.start()
t2 = threading.Thread(target=tt)
t2.setName('Thread-test2')
t2.setDaemon(True)
t2.start()
t3 = threading.Thread(target=get_thread)
t3.setName('Checker')
t3.setDaemon(True)
t3.start()
t1.join()
t2.join()
t3.join() 输出:
root@10-46-33-56:~# python t_show.py pid: 6258, tid: Thread-test1, tname: 139907597162240 pid: 6258, tid: Thread-test2, tname: 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Checker 139907576182528 ...
代码看起来有点长,但是逻辑相当简单, Thread-test1 和 Thread-test2 都是打印出当前的 pid、线程 id 和 线程名字,然后 3s 后退出,这个是想模拟线程正常退出。
而 Checker 线程则是每秒通过 threading.enumerate 输出当前进程内所有活跃的线程。
可以明显看到一开始是可以看到 Thread-test1 和 Thread-test2 的信息,当它俩退出之后就只剩下 MainThread 和 Checker 自身而已了。
销毁指定线程
既然能拿到名字和线程 id,那我们也就能干掉指定的线程了!
假设现在 Thread-test2 已经黑化,发疯了,我们需要制止它,那我们就可以通过这种方式解决了:
在上面的代码基础上,增加和补上下列代码:
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def stop_thread(thread):
_async_raise(thread.ident, SystemExit)
def get_thread():
pid = os.getpid()
while True:
ts = threading.enumerate()
print '------- Running threads On Pid: %d -------' % pid
for t in ts:
print t.name, t.ident, t.is_alive()
if t.name == 'Thread-test2':
print 'I am go dying! Please take care of yourself and drink more hot water!'
stop_thread(t)
print
time.sleep(1) 输出
root@10-46-33-56:~# python t_show.py pid: 6362, tid: 139901682108160, tname: Thread-test1 pid: 6362, tid: 139901671618304, tname: Thread-test2 ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True Thread-test2 139901671618304 True Thread-test2: I am go dying. Please take care of yourself and drink more hot water! ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True Thread-test2 139901671618304 True Thread-test2: I am go dying. Please take care of yourself and drink more hot water! pid: 6362, tid: 139901682108160, tname: Thread-test1 ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True // Thread-test2 已经不在了
3 天前发布
Python:线程之定位与销毁
c
linux
python
266 次阅读 · 读完需要 30 分钟
8
背景
开工前我就觉得有什么不太对劲,感觉要背锅。这可不,上班第三天就捅锅了。
我们有个了不起的后台程序,可以动态加载模块,并以线程方式运行,通过这种形式实现插件的功能。而模块更新时候,后台程序自身不会退出,只会将模块对应的线程关闭、更新代码再启动,6 得不行。
于是乎我就写了个模块准备大展身手,结果忘记写退出函数了,导致每次更新模块都新创建一个线程,除非重启那个程序,否则那些线程就一直苟活着。
这可不行啊,得想个办法清理呀,要不然怕是要炸了。
那么怎么清理呢?我能想到的就是两步走:
找出需要清理的线程号 tid;
销毁它们;
找出线程ID
和平时的故障排查相似,先通过 ps 命令看看目标进程的线程情况,因为已经是 setName 设置过线程名,所以正常来说应该是看到对应的线程的。 直接用下面代码来模拟这个线程:
Python 版本的多线程
#coding: utf8
import threading
import os
import time
def tt():
info = threading.currentThread()
while True:
print 'pid: ', os.getpid()
print info.name, info.ident
time.sleep(3)
t1 = threading.Thread(target=tt)
t1.setName('OOOOOPPPPP')
t1.setDaemon(True)
t1.start()
t2 = threading.Thread(target=tt)
t2.setName('EEEEEEEEE')
t2.setDaemon(True)
t2.start()
t1.join()
t2.join() 输出:
root@10-46-33-56:~# python t.py pid: 5613 OOOOOPPPPP 139693508122368 pid: 5613 EEEEEEEEE 139693497632512 ...
可以看到在 Python 里面 输出的线程名就是我们设置的那样,然而 Ps 的结果却是令我怀疑人生:
root@10-46-33-56:~# ps -Tp 5613 PID SPID TTY TIME CMD 5613 5613 pts/2 0 python 5613 5614 pts/2 0 python 5613 5615 pts/2 0 python
正常来说不该是这样呀,我有点迷了,难道我一直都是记错了?用别的语言版本的多线程来测试下:
C 版本的多线程
#include<stdio.h>
#include<sys/syscall.h>
#include<sys/prctl.h>
#include<pthread.h>
void *test(void *name)
{
pid_t pid, tid;
pid = getpid();
tid = syscall(__NR_gettid);
char *tname = (char *)name;
// 设置线程名字
prctl(PR_SET_NAME, tname);
while(1)
{
printf("pid: %d, thread_id: %u, t_name: %s\n", pid, tid, tname);
sleep(3);
}
}
int main()
{
pthread_t t1, t2;
void *ret;
pthread_create(&t1, NULL, test, (void *)"Love_test_1");
pthread_create(&t2, NULL, test, (void *)"Love_test_2");
pthread_join(t1, &ret);
pthread_join(t2, &ret);
} 输出:
root@10-46-33-56:~# gcc t.c -lpthread && ./a.out pid: 5575, thread_id: 5577, t_name: Love_test_2 pid: 5575, thread_id: 5576, t_name: Love_test_1 pid: 5575, thread_id: 5577, t_name: Love_test_2 pid: 5575, thread_id: 5576, t_name: Love_test_1 ...
用 PS 命令再次验证:
root@10-46-33-56:~# ps -Tp 5575 PID SPID TTY TIME CMD 5575 5575 pts/2 0 a.out 5575 5576 pts/2 0 Love_test_1 5575 5577 pts/2 0 Love_test_2
这个才是正确嘛,线程名确实是可以通过 Ps 看出来的嘛!
不过为啥 Python 那个看不到呢?既然是通过 setName 设置线程名的,那就看看定义咯:
[threading.py]
class Thread(_Verbose):
...
@property
def name(self):
"""A string used for identification purposes only.
It has no semantics. Multiple threads may be given the same name. The
initial name is set by the constructor.
"""
assert self.__initialized, "Thread.__init__() not called"
return self.__name
@name.setter
def name(self, name):
assert self.__initialized, "Thread.__init__() not called"
self.__name = str(name)
def setName(self, name):
self.name = name
... 看到这里其实只是在 Thread 对象的属性设置了而已,并没有动到根本,那肯定就是看不到咯~
这样看起来,我们已经没办法通过 ps 或者 /proc/ 这类手段在外部搜索 python 线程名了,所以我们只能在 Python 内部来解决。
于是问题就变成了,怎样在 Python 内部拿到所有正在运行的线程呢?
threading.enumerate 可以完美解决这个问题!Why?
Because 在下面这个函数的 doc 里面说得很清楚了,返回所有活跃的 线程对象 ,不包括终止和未启动的。
[threading.py]
def enumerate():
"""Return a list of all Thread objects currently alive.
The list includes daemonic threads, dummy thread objects created by
current_thread(), and the main thread. It excludes terminated threads and
threads that have not yet been started.
"""
with _active_limbo_lock:
return _active.values() + _limbo.values() 因为拿到的是 Thread 的对象,所以我们通过这个能到该线程相关的信息!
请看完整代码示例:
#coding: utf8
import threading
import os
import time
def get_thread():
pid = os.getpid()
while True:
ts = threading.enumerate()
print '------- Running threads On Pid: %d -------' % pid
for t in ts:
print t.name, t.ident
print
time.sleep(1)
def tt():
info = threading.currentThread()
pid = os.getpid()
while True:
print 'pid: {}, tid: {}, tname: {}'.format(pid, info.name, info.ident)
time.sleep(3)
return
t1 = threading.Thread(target=tt)
t1.setName('Thread-test1')
t1.setDaemon(True)
t1.start()
t2 = threading.Thread(target=tt)
t2.setName('Thread-test2')
t2.setDaemon(True)
t2.start()
t3 = threading.Thread(target=get_thread)
t3.setName('Checker')
t3.setDaemon(True)
t3.start()
t1.join()
t2.join()
t3.join() 输出:
root@10-46-33-56:~# python t_show.py pid: 6258, tid: Thread-test1, tname: 139907597162240 pid: 6258, tid: Thread-test2, tname: 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Checker 139907576182528 ...
代码看起来有点长,但是逻辑相当简单, Thread-test1 和 Thread-test2 都是打印出当前的 pid、线程 id 和 线程名字,然后 3s 后退出,这个是想模拟线程正常退出。
而 Checker 线程则是每秒通过 threading.enumerate 输出当前进程内所有活跃的线程。
可以明显看到一开始是可以看到 Thread-test1 和 Thread-test2 的信息,当它俩退出之后就只剩下 MainThread 和 Checker 自身而已了。
销毁指定线程
既然能拿到名字和线程 id,那我们也就能干掉指定的线程了!
假设现在 Thread-test2 已经黑化,发疯了,我们需要制止它,那我们就可以通过这种方式解决了:
在上面的代码基础上,增加和补上下列代码:
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def stop_thread(thread):
_async_raise(thread.ident, SystemExit)
def get_thread():
pid = os.getpid()
while True:
ts = threading.enumerate()
print '------- Running threads On Pid: %d -------' % pid
for t in ts:
print t.name, t.ident, t.is_alive()
if t.name == 'Thread-test2':
print 'I am go dying! Please take care of yourself and drink more hot water!'
stop_thread(t)
print
time.sleep(1) 输出
root@10-46-33-56:~# python t_show.py pid: 6362, tid: 139901682108160, tname: Thread-test1 pid: 6362, tid: 139901671618304, tname: Thread-test2 ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True Thread-test2 139901671618304 True Thread-test2: I am go dying. Please take care of yourself and drink more hot water! ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True Thread-test2 139901671618304 True Thread-test2: I am go dying. Please take care of yourself and drink more hot water! pid: 6362, tid: 139901682108160, tname: Thread-test1 ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True // Thread-test2 已经不在了
2 条评论
舞林 · 1 天前
如果是我可能kill -9了,宁可错杀一千,不可放过一个,蛤蛤
赞 回复
不行呀~ -9 进程里全死了~
— Lin_R 作者 · 1 天前
以上就是Python线程中定位与销毁的详细介绍(附示例)的详细内容,更多请关注Gxl网其它相关文章!
查看更多关于Python线程中定位与销毁的详细介绍(附示例)的详细内容...