首页 > 其他分享 >多线程共享资源之竞态条件

多线程共享资源之竞态条件

时间:2023-07-29 12:11:28浏览次数:29  
标签:__ thread counter 共享资源 threading num 线程 竞态 多线程

什么是竞态条件?

竞态条件是指多个线程在访问和操作共享资源时,由于执行顺序的不确定性而导致结果不确定或出现错误。

示例1:

'''
竞态条件是指多个线程在访问和操作共享资源时,由于执行顺序的不确定性而导致结果不确定或出现错误。
'''

import threading
# 共享变量
counter = 0


# 线程函数
def increment():
    global counter
    '''
    在Python中,使用下划线 _ 作为一个临时变量的命名约定是很常见的。这个约定表明该变量的值不会被使用,或者不关心该变量的具体值。
    for _ in range(100000) 表示一个循环,循环执行10万次。在每次循环迭代中,由于并不需要使用循环变量的值,因此使用下划线 _ 来表示这个临时变量。
    这种情况下,使用下划线 _ 可以告诉读者,这个循环变量的值并不重要,只是为了控制循环次数而已。
    通过使用下划线 _,可以避免创建一个不必要的变量,并提高代码的可读性。
    '''
    for _ in range(100000):
        counter += 1


def decrement():
    global counter
    for _ in range(100000):
        counter -= 1


def main():
    # 创建两个线程
    t1 = threading.Thread(target=increment)
    t2 = threading.Thread(target=decrement)

    # 启动线程
    t1.start()
    t2.start()

    # 等待线程结束
    t1.join()
    t2.join()

    # 打印最终结果
    print("Counter:", counter)


if __name__ == '__main__':
    main()

输出:Counter: 0

多次执行从结果上,是对的。多线程存在竞态条件,可能导致结果不正确。(有些结果是正确的,有写结果是错误的,存在概率问题)

在这段代码中,两个线程分别对共享变量 counter 进行加一和减一操作。由于这两个线程同时访问和修改 counter 变量,可能会导致竞态条件的问题。

在某些情况下,当一个线程正在执行增加操作时,另一个线程可能同时执行减少操作,这可能导致结果不可预测。

然而,即使竞态条件发生,也不能保证最终结果一定是0。因为在 Python 中, counter += 1counter -= 1 操作并不是原子操作,它们实际上是多个步骤的组合。

例如,在 counter += 1 操作中,首先需要读取 counter 的当前值,然后进行加一操作,最后将新值写回到 counter 中。类似地,counter -= 1 操作也包括读取、减一和写回这三个步骤。

由于这些操作不是原子的,两个线程之间的交替执行可能会导致一些操作被覆盖或丢失,从而导致最终结果出现错误。

示例2 

 1 import threading
 2 
 3 
 4 # 全局变量
 5 g_num = 0
 6 
 7 
 8 # 对g_num进行加操作
 9 def sum_num1():
10     for i in range(1000000):
11         global g_num
12         g_num += 1
13 
14     print("g_num1:", g_num)
15 
16 
17 # 对g_num进行加操作
18 def sum_num2():
19     for i in range(1000000):
20         global g_num
21         g_num += 1
22 
23     print("g_num2:", g_num)
24 
25 
26 if __name__ == '__main__':
27     # 创建子线程
28     sum1_thread = threading.Thread(target=sum_num1)
29     sum2_thread = threading.Thread(target=sum_num2)
30 
31     # 启动线程
32     sum1_thread.start()
33     sum2_thread.start()
34     sum1_thread.join()
35     sum2_thread.join()
36     print("Final g_num:", g_num)

输出:(存在出现问题的概率,模拟出来有概率)

g_num1: 1295051
g_num2: 1999991
Final g_num: 1999991

解决方法

为了解决这个问题,可以使用同步机制(如锁)来确保每次只有一个线程能够访问和修改共享变量

 1 import threading
 2 
 3 # 全局变量
 4 g_num = 0
 5 # 全局变量作为锁对象(保证多个线程使用的是同一把锁)。在多个线程中通过 with lock: 语句来获取该锁。
 6 lock = threading.Lock()
 7 
 8 
 9 # 对g_num进行加操作
10 def sum_num1():
11     global g_num
12     for i in range(1000000):
13         with lock:
14             g_num += 1
15     print("g_num1:", g_num)
16 
17 
18 # 对g_num进行加操作
19 def sum_num2():
20     global g_num
21     for i in range(1000000):
22         with lock:  # 通过with语句实现自动上锁、解锁
23             g_num += 1
24     print("g_num2:", g_num)
25 
26 
27 if __name__ == '__main__':
28     # 创建子线程
29     sum1_thread = threading.Thread(target=sum_num1)
30     sum2_thread = threading.Thread(target=sum_num2)
31 
32     # 启动线程
33     sum1_thread.start()
34     sum2_thread.start()
35 
36     # 等待子线程结束
37     sum1_thread.join()
38     sum2_thread.join()
39 
40     # 打印最终结果
41     print("Final g_num:", g_num)

输出:

g_num1: 1841952
g_num2: 2000000
Final g_num: 2000000

 

标签:__,thread,counter,共享资源,threading,num,线程,竞态,多线程
From: https://www.cnblogs.com/allenxx/p/17589607.html

相关文章

  • 多线程共享全局变量的问题
    线程之间共享全局变量多个线程都是在同一个进程中,多个线程使用的资源都是同一个进程中的资源,因此多线程间是共享全局变量问题示例1importthreading234#全局变量5g_num=0678#对g_num进行加操作9defsum_num1():10foriinrange(......
  • 多线程编程
    一、线程,进程介绍线程:是操作系统中独立运行的最小单位。每个线程都有自己的执行路径、程序计数器、堆栈和一组寄存器。线程共享进程的资源,如内存和文件描述符,可以并发执行,从而提高程序的并发性和响应性。进程:是操作系统中运行的一个程序实例。它拥有独立的内存空间和系统资源,如......
  • 多线程
    多线程实现的几种方式1.继承Thread类,重写run方法。2.实现Runnable,实现run方法。3.实现Callable接口。4.实现有返回结果的线程,使用ExecutorService、Callable、Future实现返回结果的线程。附带学习地址:https://www.php.cn/faq/543054.html......
  • 实现多线程多任务的步骤
    步骤 1.导入线程模块importthreading2.创建子线程并指定执行的任务sub_thread=threading.Thread(target=任务名3.启动线程执行任务sub_thread.start() 示例1'''2在Python中,实现多线程多任务可以通过使用threading模块来创建和管理线程34最佳实践:......
  • Android多线程及异步处理问题
    1、问题提出1)为何需要多线程?2)多线程如何实现?3)多线程机制的核心是啥?4)到底有多少种实现方式?2、问题分析1)究其为啥需要多线程的本质就是异步处理,直观一点说就是不要让用户感觉到“很卡”。eg:你点击按钮下载一首歌,......
  • java多线程学习-java.util.concurrent详解
    java多线程学习-java.util.concurrent详解(一)Latch/Barrier   Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent,提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。从这篇blog起,我将跟大家一起共同学习这些新的Java多线......
  • 关于异步多线程
    方法一:利用线程池或@Async注解使用@Async注解,可以实现多个方法并行执行,然后将它们的返回结果进行处理。@Async注解会使被标注的方法在调用时,将任务提交给一个线程池中的线程去执行,不会阻塞主线程。下面是一个简单的示例,演示如何使用@Async注解来处理多个方法的返回结果:......
  • SpringBoot中定时任务开启多线程避免多任务堵塞
    场景SpringBoot中定时任务与异步定时任务的实现:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/117083609使用SpringBoot原生方式实现定时任务,已经开启多线程支持,以上是方式之一。除此之外还可通过如下方式。为什么SpringBoot定时任务是单线程的?查看注解@Ena......
  • Android 多线程编程 - 线程的基本使用
    1.线程的状态New:新创建状态。线程被创建,还没有调用start方法,在线程运行之前还有一些基础工作要做。Runnable:可运行状态。一旦调用start方法,线程就处于Runnable状态。一个可运行线程可能正在运行,也可能没有运行,这取决于操作系统给线程提供运行的时间。Blocked:阻塞状态。表示线程被......
  • Python time模块和datetime模块,以及多线程
    1.time模块1.1time.time()函数time.time()函数返回自1970年1月1日0点到现在的秒数。纪元时间戳可以用于剖析代码,也就是测量一段代码的运行时间。如果在代码块开始时调用time.time(),并在结束时再次打开,就可以用第二个时间戳减去第一个,得到这两次调用之间经过的时间。......