python多线程下数据安全问题

几个问题

1、python多线程下,局部变量、全局变量都有安全风险吗?

一个线程的局部变量只有线程自己能看见,不会影响其他线程。而全局变量是所有线程共享的,修改时要注意线程安全问题。

2、如果全局变量不直接写在文件,而是写在文件的类里,还有风险么?

在多线程安全问题上,类内的方法和类外的函数并没有本质的区别。线程安全问题主要出现在多个线程同时访问和修改同一份数据时,可能会导致数据的不一致或者其他预期之外的结果。

  • 【线程安全】:如果一个 "类内"的方法 或 "类外"的函数 访问和修改的都是局部变量,那么它们在多线程环境下都是线程安全的。因为每个线程都有自己的执行环境,局部变量存储在这个执行环境中,因此各个线程之间的局部变量是互不干扰的。
  • 【线程风险】:如果一个 "类内"的方法 访问和修改的是实例变量,那么这个方法在多线程环境下可能是不安全的,因为实例变量是在堆内存中存储的,是可以被多个线程共享的。

案例:

def processing_data(task_id, collection_name):
    '''
        写在类外面,多线程下,每个线程独立拥有该方法,所以是线程安全。
    :param task_id:
    :param collection_name:
    :return:
    '''
    print(task_id + collection_name)
    if collection_name == "a":
        time.sleep(10)
    print(task_id + collection_name)


class LabelService:

    def create_thread(self, task_id, collection_name_list):
        """
        :param task_id: 任务id
        :param collection_name_list: 集合名称列表
        """
        # 多线程处理数据
        for collection_name in collection_name_list:
            # 创建线程
            thread_processing_data = threading.Thread(
                # target=self.processing_data1,  # todo 方法内的变量是实例变量,调用同一实例的同一方法,方法内的变量会被多线程篡改,不安全!
                target=LabelService().processing_data1,  # todo 方法内的变量是实例变量,调用不同实例的同一方法,不同实例间相互隔离,线程安全~
                # target=self.processing_data2,  # todo 方法内的变量是局部变量,多个线程同时调用同一实例的同一方法,该方法内的局部变量也是相互隔离的,线程安全~
                # target=self.processing_data3,  # todo 函数内的变量是局部变量,多个线程同时调用同一类外函数,该函数内的局部变量也是相互隔离的,线程安全~
                args=(task_id, collection_name))
            thread_processing_data.name = task_id + "data_push_" + collection_name
            # 启动线程
            thread_processing_data.start()
            print("创建线程:[" + thread_processing_data.name + "]" + str(thread_processing_data.is_alive()))

    def processing_data2(self, task_id, collection_name):
        print(task_id + collection_name)
        if collection_name == "a":
            time.sleep(10)
        print(task_id + collection_name)

    def processing_data1(self, task_id, collection_name):
        self.task_id = task_id
        self.collection_name = collection_name
        print(self.task_id + self.collection_name)
        if collection_name == "a":
            time.sleep(10)
        print(self.task_id + self.collection_name)

LabelService().create_thread("qqqqqqqqqqqqqq", ["a", "b"])

3、锁和threadlocal哪个好?

锁和threadLocal的适用场景不同。

作用对象:全局变量

适用场景:

  • 锁:某个变量需要被多个线程共享他的最新值,就要用锁来限制多线程间的动作。
  • threadLocal:某个变量需要在每个线程里各自维护一份不同的数据。

(1)举例:

比如一个班级(某web项目)里有30个学生(并发执行30个线程),那"班级名"在全班都是公开的,"家庭住址"是需要每个学生时刻记住的(比如:回家、填报个人信息等动作中都要用到)。所以"班级名"、"家庭住址"都是全局变量,只不过前者value整个web共享后者value每个线程各自维护一份、并且在各自存活过程中一直存在

如果班级名改了,那班里30人的班级名都跟着变;

如果某个学生的"家庭住址"改了,不会影响其他学生,也不用通知其他学生。

所以:

  • "班级名"的修改需要加锁,这样就不会部分同学知道班级改名了,部分同学不知道,当别人问你们是哪个班时,就不会出现多个答案冲突的情况。
  • "家庭住址"可以放到threadLocal中,只用修改threadLocal中的该变量即可。

(2)案例

① ThreadLocal
反例】全局变量:多线程下不安全
import time, threading
balance = 0

def change_it(n):
    global balance
    balance = balance + n
    print(threading.current_thread().name, " +++++:", str(balance))
    balance = balance - n
    print(threading.current_thread().name, "-----:", str(balance))

def run_thread(n):
    for i in range(100):
        change_it(n)

t1 = threading.Thread(target = run_thread , args = (5,))
t2 = threading.Thread(target = run_thread ,args = (8,))
t1.start()
t2.start()

'''
thread.join()方法的作用是阻塞"执行xx.join()"这行代码的线程:在这里就是主线程(如果实在t2线程执行这行代码,就是t2要等待t1执行完),使主线程在该方法被调用的子线程结束之前等待。
t1.join()使主线程等待t1线程结束,t2.join()使主线程等待t2线程结束。
如果你不写thread.join(),那么主线程和子线程将会并发执行,也就是说,主线程不会等待子线程结束就会继续执行。这种情况下,主线程可能在子线程之前就结束,此时子线程可能还没有执行完。这种情况下,由于主线程(执行`print(balance))已经结束,程序也就结束了,因此你可能看不到子线程的执行结果。
'''
t1.join()
t2.join()
print(balance)
正例】全局变量:使用threadLocal
import threading
import time

def set_telephone(telephone):
    local.telephone = telephone
    print(threading.current_thread().name + " 放入的手机是", local.telephone + "\n")
    time.sleep(1)
    get_telephone()

def get_telephone():
    print(threading.current_thread().name + " 取出的手机是", local.telephone + "\n")

if __name__ == '__main__':
    local = threading.local()
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
        thread.start()
② 锁

python多线程中的锁你知道几种?
对死锁的理解
死锁核心思想

反例】死锁

死锁的条件:

(1)同时存在两把锁

(2)锁的获取是异步的

(3)一个线程 抢到一把锁之后还要去抢第二把锁

(4)一个线程抢到一把锁,另一个线程抢到另一把锁

###### 死锁现象

import time
from threading import Thread, Lock

noodle_lock = Lock()  # 面锁
fork_lock = Lock()  # 叉子锁


def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条了' % name)
    fork_lock.acquire()
    print('%s拿到叉子了' % name)

    print('%s吃面' % name)
    time.sleep(3)

    fork_lock.release()
    print('%s放下叉子' % name)
    noodle_lock.release()
    print('%s放下面' % name)


def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    noodle_lock.acquire()
    print('%s拿到面条了' % name)

    print('%s吃面' % name)
    time.sleep(3)

    noodle_lock.release()
    print('%s放下面' % name)
    fork_lock.release()
    print('%s放下叉子' % name)


if __name__ == '__main__':
    name_list1 = ['zhangsan', 'lisi']
    name_list2 = ['wangwu', 'zhaoliu']

    for name in name_list1:
        Thread(target=eat1, args=(name,)).start()
    for name in name_list2:
        Thread(target=eat2, args=(name,)).start()

本例中,可能会出现:线程 lisi 先调用eat1()拿到了noodle_lock, 同时 线程wangwu 调用eat2()拿到了fork_lock。

随后,在 线程 lisi 希望拿到 fork_lock才会释放noodle_lock,线程wangwu 希望拿到 noodle_lock才会释放fork_lock。大家互不相让,形成死锁。

【正例】可重入锁(递归锁)

改造 :使用 可重入锁(递归锁)解决死锁问题。

可重入锁,是一种特殊的线程锁,允许同一个线程在没有释放自己已经获得的锁的情况下,再次获得自己已经获得的那个锁。

全局只有一把锁 ,而且是可重入锁(递归锁)!

这时如果一个线程获取了锁,另一个线程就被阻塞,直到对方释放锁。

import time
from threading import Thread, Lock, RLock

fork_lock = noodle_lock = RLock()

def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条了' % name)
    fork_lock.acquire()
    print('%s拿到叉子了' % name)

    print('%s吃面' % name)
    time.sleep(3)

    fork_lock.release()
    print('%s放下叉子' % name)
    noodle_lock.release()
    print('%s放下面' % name)


def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    noodle_lock.acquire()
    print('%s拿到面条了' % name)

    print('%s吃面' % name)
    time.sleep(3)

    noodle_lock.release()
    print('%s放下面' % name)
    fork_lock.release()
    print('%s放下叉子' % name)


if __name__ == '__main__':
    name_list1 = ['AA', 'BB', 'CC']
    name_list2 = ['大锤', '茉莉', "云朵"]

    for name in name_list1:
        Thread(target=eat1, args=(name, )).start()
    for name in name_list2:
        Thread(target=eat2, args=(name, )).start()
相关推荐
懒大王爱吃狼20 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
秃头佛爷1 小时前
Python学习大纲总结及注意事项
开发语言·python·学习
深度学习lover2 小时前
<项目代码>YOLOv8 苹果腐烂识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·苹果腐烂识别
API快乐传递者3 小时前
淘宝反爬虫机制的主要手段有哪些?
爬虫·python
阡之尘埃5 小时前
Python数据分析案例61——信贷风控评分卡模型(A卡)(scorecardpy 全面解析)
人工智能·python·机器学习·数据分析·智能风控·信贷风控
hikktn6 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
23zhgjx-NanKon8 小时前
华为eNSP:QinQ
网络·安全·华为
23zhgjx-NanKon8 小时前
华为eNSP:mux-vlan
网络·安全·华为
丕羽8 小时前
【Pytorch】基本语法
人工智能·pytorch·python
昔我往昔9 小时前
阿里云文本内容安全处理
安全·阿里云·云计算