Python 列表是否线程安全?

2023-05-29 16:32:46 浏览数 (5)

亿牛云代理亿牛云代理

Python中的列表不是线程安全的,在多线程环境下,对列表的操作可能会导致数据冲突或错误。但是,并非所有列表操作都是线程不安全的。如果操作是原子的,也就是说不能被线程调度机制打断,那么就没有问题。比如L.append(x)和L.pop()就是原子操作,所以是thread安全。如果操作不是原子的,或者涉及修改多个列表元素,那么就需要使用锁或者其他同步机制来保证线程安全。例如,Li = Lj 和 L.append(L- 1) 不是原子操作,因此它们可能会导致冲突。可以使用 dis 模块来检查操作是否是原子操作。

例如下面就是多线程非安全操作:

代码语言:python代码运行次数:0复制
# 导入线程模块和dis模块
import threading
import dis

# 定义一个列表
L = [1, 2, 3, 4]

# 定义一个函数,用于对列表进行非原子操作
def swap(i, j):
    # 交换L[i]和L[j]的值
    L[i], L[j] = L[j], L[i]

# 定义一个函数,用于检查操作是否是原子操作
def check_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)

输出结果可能是:

代码语言:python代码运行次数:0复制
[2, 1, 4, 3]
 10           0 LOAD_FAST                0 (i)
              2 LOAD_FAST                1 (j)
              4 ROT_TWO
              6 LOAD_GLOBAL              0 (L)
              8 STORE_SUBSCR

 11          10 LOAD_GLOBAL              0 (L)
             12 LOAD_FAST                1 (j)
             14 BINARY_SUBSCR
             16 LOAD_GLOBAL              0 (L)
             18 LOAD_FAST                0 (i)
             20 BINARY_SUBSCR
             22 ROT_TWO
             24 LOAD_GLOBAL              0 (L)
             26 STORE_SUBSCR
             28 LOAD_CONST               0 (None)
             30 RETURN_VALUE
None

可以看到,swap函数不是一个原子操作,因为它包含了多个字节码指令,而且涉及到对列表元素的修改。这样的操作在多线程环境下可能会导致数据冲突或错误。

下面是一个原子操作,因此是线程安全:

代码语言:python代码运行次数:0复制
# 导入线程模块、dis模块和requests模块
import threading
import dis
import requests

# 定义一个列表
L = []

# 定义一个函数,用于对列表进行原子操作
def append(x):
    # 向列表末尾添加元素x
    L.append(x)

# 定义一个函数,用于检查操作是否是原子操作
def check_atomic(func):
    # 使用dis模块的dis函数打印操作的字节码
    print(dis.dis(func))

# 定义一个函数,用于通过代理IP的用户名和密码方式进行网络传递
def send(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/post
    url = "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)

输出结果可能是:

代码语言:python代码运行次数:0复制
[1, 2, 3, 4]
 10           0 LOAD_GLOBAL              0 (L)
              2 LOAD_METHOD              1 (append)
              4 LOAD_FAST                0 (x)
              6 CALL_METHOD              1
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
None

{
  "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的用户名和密码方式成功地将列表的长度传递给了目标网址,并得到了响应结果。

0 人点赞