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()

'''
xx.join()方法的作用是阻塞执行“xx.join()”这行代码的线程:在这里就是主线程(如果是在t2线程的run里执行这行代码,就是t2要等待t1执行完),使主线程等待子线程结束后才最后结束。
t1.join()使主线程等待t1线程结束,t2.join()使主线程等待t2线程结束。
如果你不写xx.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()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python多线程(multithreading)和多进程(multiprocessing)模块可以用于处理数据,提高程序效率。下面是一个简单的例子: 使用多线程处理数据 ``` import threading def process_data(data): # 处理数据的函数 pass def main(): data = get_data() # 获取数据 threads = [] for i in range(4): # 创建4个线程 t = threading.Thread(target=process_data, args=(data,)) threads.append(t) t.start() # 等待所有线程完成 for t in threads: t.join() if __name__ == '__main__': main() ``` 上面的代码中,我们首先定义了一个处理数据的函数 process_data(),然后使用多线程模块创建了 4 个线程来处理数据。最后等待所有线程完成。 使用多进程处理数据 ``` import multiprocessing def process_data(data): # 处理数据的函数 pass def main(): data = get_data() # 获取数据 processes = [] for i in range(4): # 创建4个进程 p = multiprocessing.Process(target=process_data, args=(data,)) processes.append(p) p.start() # 等待所有进程完成 for p in processes: p.join() if __name__ == '__main__': main() ``` 上面的代码中,我们首先定义了一个处理数据的函数 process_data(),然后使用多进程模块创建了 4 个进程来处理数据。最后等待所有进程完成。 需要注意的是,多进程处理数据的开销比多线程大,因为每个进程都需要独立的内存空间和上下文。因此,如果处理的数据量较小,可以使用多线程模块,而如果处理的数据量较大,可以考虑使用多进程模块。 ### 回答2: Python中的多线程是一种并发编程的方式,可以有效地处理数据。多线程可以同时执行多个任务,提高程序的运行效率和响应速度。 在Python中,可以使用内置的`threading`模块来实现多线程编程。通过创建多个线程对象,每个线程都可以独立执行不同的任务。这样可以在处理数据时同时执行多个任务,提高数据处理的速度。 多线程处理数据时,需要考虑线程的同步和数据共享问题。由于多个线程会同时访问和修改共享的数据,可能会导致数据冲突和不一致。为了解决这个问题,可以使用锁(Lock)或其他同步机制来确保线程之间的数据同步和一致性。 在多线程处理数据时,还需要注意线程的安全性。Python中的全局解释器锁(Global Interpreter Lock,GIL)限制了线程的并行执行,使得多线程不能真正实现并行处理。因此,对于某些密集计算型的任务,多线程可能并不能提升性能。 在结合Python多线程处理数据时,可以根据实际情况选择合适的线程数和线程策略,以达到最佳的数据处理效果。此外,还可以通过多进程、协程等方式来处理数据,根据具体需求选择合适的并发编程方式。 ### 回答3: Python是一种非常流行的编程语言,它有很多强大的库和工具,其中包括对多线程处理数据的支持。多线程是一种并发编程的方式,它可以让程序同时执行多个任务,提高程序的效率。 在Python中,可以使用内置的`threading`模块来实现多线程处理数据。这个模块提供了创建和管理线程的功能,可以让我们更方便地使用多线程。 要使用多线程处理数据,首先我们需要定义一个线程函数,这个函数会被多个线程同时执行。在这个函数中,我们可以编写具体的数据处理逻辑。然后,我们可以使用`threading.Thread`类创建多个线程,并将线程函数作为参数传递给这些线程对象。最后,我们只需要调用这些线程对象的`start`方法,就可以启动这些线程并开始并行处理数据了。 多线程处理数据的一个常见应用场景是并行计算。例如,我们可以将一个大型的计算任务分成多个子任务,然后分配给不同的线程同时进行计算。这样,整个计算过程可以在较短的时间内完成。 另一个常见的应用场景是并行IO操作,比如数据的读取和写入。通过将这些IO操作分配给不同的线程,可以提高数据的处理速度。 需要注意的是,在多线程处理数据时,我们需要注意处理线程之间的同步和互斥问题。多个线程同时访问共享的数据时,可能会引发竞争条件和数据不一致的问题。为了避免这些问题,我们可以使用锁或者其他同步机制来保证线程之间的正确和有序的执行。 总的来说,Python中的多线程可以帮助我们实现并发处理数据的需求,提高程序的效率。不过,要合理使用多线程,并注意处理线程之间的同步和互斥问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值