Python中的列表不是线程安全的,在多线程环境下,对列表的操作可能会导致数据冲突或错误。但是,并非所有列表操作都是线程不安全的。如果操作是原子的,也就是说不能被线程调度机制打断,那么就没有问题。比如L.append(x)和L.pop()就是原子操作,所以是thread安全。如果操作不是原子的,或者涉及修改多个列表元素,那么就需要使用锁或者其他同步机制来保证线程安全。例如,L[i] = L[j] 和 L.append(L[- 1]) 不是原子操作,因此它们可能会导致冲突。可以使用 dis 模块来检查操作是否是原子操作。
例如下面就是多线程非安全操作:
# 导入线程模块和dis模块importthreadingimportdis# 定义一个列表L= [1, 2, 3, 4] # 定义一个函数,用于对列表进行非原子操作defswap(i, j): # 交换L[i]和L[j]的值L[i], L[j] =L[j], L[i] # 定义一个函数,用于检查操作是否是原子操作defcheck_atomic(func): # 使用dis模块的dis函数打印操作的字节码print(dis.dis(func)) # 创建两个线程,分别执行swap(0, 1)和swap(2, 3)t1=threading.Thread(target=swap, args=(0, 1)) t2=threading.Thread(target=swap, args=(2, 3)) # 启动线程t1.start() t2.start() # 等待线程结束t1.join() t2.join() # 打印列表的结果print(L) # 检查swap函数是否是原子操作check_atomic(swap)
输出结果可能是:
[2, 1, 4, 3] 100LOAD_FAST0 (i) 2LOAD_FAST1 (j) 4ROT_TWO6LOAD_GLOBAL0 (L) 8STORE_SUBSCR1110LOAD_GLOBAL0 (L) 12LOAD_FAST1 (j) 14BINARY_SUBSCR16LOAD_GLOBAL0 (L) 18LOAD_FAST0 (i) 20BINARY_SUBSCR22ROT_TWO24LOAD_GLOBAL0 (L) 26STORE_SUBSCR28LOAD_CONST0 (None) 30RETURN_VALUENone
可以看到,swap函数不是一个原子操作,因为它包含了多个字节码指令,而且涉及到对列表元素的修改。这样的操作在多线程环境下可能会导致数据冲突或错误。
下面是一个原子操作,因此是线程安全:
# 导入线程模块、dis模块和requests模块importthreadingimportdisimportrequests# 定义一个列表L= [] # 定义一个函数,用于对列表进行原子操作defappend(x): # 向列表末尾添加元素xL.append(x) # 定义一个函数,用于检查操作是否是原子操作defcheck_atomic(func): # 使用dis模块的dis函数打印操作的字节码print(dis.dis(func)) # 定义一个函数,用于通过代理IP的用户名和密码方式进行网络传递defsend(proxy, username, password): # 设置代理IP的地址和端口proxy_url=f"http://{username}:{password}@{proxy}"# 设置代理IP的参数proxies= { "http": proxy_url, "https": proxy_url, } # 设置要传递的数据,这里假设是列表的长度data= {"length": len(L)} # 设置要传递的目标网址,这里假设是httpbin.org/posturl="http://httpbin.org/post"# 使用requests模块的post方法发送数据,并打印响应结果response=requests.post(url, data=data, proxies=proxies) print(response.text) # 创建四个线程,分别执行append(1)、append(2)、append(3)和append(4)t1=threading.Thread(target=append, args=(1,)) t2=threading.Thread(target=append, args=(2,)) t3=threading.Thread(target=append, args=(3,)) t4=threading.Thread(target=append, args=(4,)) # 启动线程t1.start() t2.start() t3.start() t4.start() # 等待线程结束t1.join() t2.join() t3.join() t4.join() # 打印列表的结果print(L) # 检查append函数是否是原子操作check_atomic(append) # 亿牛云(动态转发隧道代理) 爬虫代理加强版 设置代理信息proxy="www.16yun.cn:8080"username="16YUN"password="16IP"# 通过代理IP的用户名和密码方式进行网络传递send(proxy, username, password)
输出结果可能是:
[1, 2, 3, 4] 100LOAD_GLOBAL0 (L) 2LOAD_METHOD1 (append) 4LOAD_FAST0 (x) 6CALL_METHOD18POP_TOP10LOAD_CONST0 (None) 12RETURN_VALUENone{ "args": {}, "data": "", "files": {}, "form": { "length": "4" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "9", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "python-requests/2.26.0", "X-Amzn-Trace-Id": "Root=1-61c6f5a9-7d8c6c7b9a5f8f7c5e9a6b8b" }, "json": null, "origin": "123.456.789.10", "url": "http://httpbin.org/post"}
可以看到,append函数是一个原子操作,因为它只包含了一个字节码指令,而且不涉及到对列表元素的修改。这样的操作在多线程环境下不会导致数据冲突或错误。另外通过代理IP的用户名和密码方式成功地将列表的长度传递给了目标网址,并得到了响应结果。