網(wǎng)站首頁 編程語言 正文
這次讓我們來看看一個(gè)真實(shí)場景吧:銀行轉(zhuǎn)賬
一、舉例銀行轉(zhuǎn)賬
假設(shè)現(xiàn)在有一個(gè)xuewei的賬號(hào)里面有 100W。
然后有多個(gè)任務(wù)在轉(zhuǎn)賬,轉(zhuǎn)入轉(zhuǎn)出都是跟這個(gè)xuewei賬號(hào)相關(guān)的。
而且這些任務(wù)發(fā)生是隨機(jī)的。
我們先把上面的場景寫成代碼:
xuewei_account = 100 # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): ? ? global xuewei_account ? ? xuewei_account += money
下面是多個(gè)線程,多線程模擬轉(zhuǎn)賬事件,我們假設(shè)有4個(gè)事件在同時(shí)發(fā)生。
import random import threading import datetime import time xuewei_account = 100 # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): ? ? global xuewei_account ? ? xuewei_account += money # 創(chuàng)建4個(gè)任務(wù)給學(xué)委賬戶轉(zhuǎn)賬 for i in range(10000): ? ? threading.Thread(target=lambda: transfer(-1)).start() ? ? threading.Thread(target=lambda: transfer(1)).start() ? ? threading.Thread(target=lambda: transfer(-1)).start() ? ? threading.Thread(target=lambda: transfer(1)).start() # 等待活躍線程只剩下主線程MainThread time.sleep(10) print("-" * 16) print("活躍線程數(shù):", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學(xué)委賬戶余額:", xuewei_account)
這里啟動(dòng)了4個(gè)線程循環(huán)了10000次,也就是4萬個(gè)線程,分別于學(xué)委的賬戶進(jìn)行轉(zhuǎn)賬。
下面是運(yùn)行結(jié)果:
運(yùn)行幾次學(xué)委的賬戶還是正確的,余額還是100W。
上面的代碼線程幾萬個(gè),但每次運(yùn)行的操作都很簡單,完成一次加法。
線程一個(gè)接一個(gè)start,非??焖倬颓袚Q下一個(gè)線程, 我們看到程序沒有出現(xiàn)問題。
下面進(jìn)行改造,這次不要就4萬線程了,我們讓轉(zhuǎn)賬這個(gè)任務(wù)耗時(shí)更多,每啟動(dòng)一個(gè)線程進(jìn)行模擬10萬次轉(zhuǎn)賬。
import random import threading import datetime import time xuewei_account = 100 # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): ? ? global xuewei_account ? ? for x in range(100000): ? ? ? ? xuewei_account += money
?創(chuàng)建4個(gè)任務(wù)給重復(fù)學(xué)委賬戶轉(zhuǎn)賬:
for i in range(10): ? ? threading.Thread(target=lambda: transfer(-1)).start() ? ? threading.Thread(target=lambda: transfer(1)).start() ? ? threading.Thread(target=lambda: transfer(-1)).start() ? ? threading.Thread(target=lambda: transfer(1)).start() time.sleep(10) print("-" * 16) print("活躍線程數(shù):", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學(xué)委賬戶余額:", xuewei_account)
這里運(yùn)行的結(jié)果就比較出乎意料了:
多線程編程復(fù)雜的地方就在這里了, 有時(shí)候明明平平無奇的代碼,改造成多線程,就很容易出bug!
當(dāng)然上面的代碼并不是平平無奇,相比第一段代碼,上面的轉(zhuǎn)賬函數(shù)做的事件更多,更耗時(shí)。
二、問題解決
我們加上鎖。
代碼如下:
import random import threading import datetime import time xuewei_account = 100 lock = threading.Lock() # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): ? ? lock.acquire() ? ? global xuewei_account ? ? for x in range(100000): ? ? ? ? xuewei_account += money ? ? lock.release() # 創(chuàng)建4個(gè)任務(wù)給重復(fù)學(xué)委賬戶轉(zhuǎn)賬 for i in range(10): ? ? threading.Thread(target=lambda: transfer(-1)).start() ? ? threading.Thread(target=lambda: transfer(1)).start() ? ? threading.Thread(target=lambda: transfer(-1)).start() ? ? threading.Thread(target=lambda: transfer(1)).start() time.sleep(10) print("-" * 16) print("活躍線程數(shù):", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學(xué)委賬戶余額:", xuewei_account)
運(yùn)行結(jié)果如下:
上面的代碼不管怎么運(yùn)行,運(yùn)行多少次最后學(xué)委的賬戶都是100.(PS:學(xué)委不會(huì)聯(lián)系讀者轉(zhuǎn)賬的,這個(gè)特別注意)。
不管多少個(gè)線程,每次轉(zhuǎn)賬函數(shù)內(nèi)部轉(zhuǎn)賬的代碼(從global到 += money這一段代碼)只會(huì)被一個(gè)線程調(diào)用。
三、總結(jié)
展示了同步機(jī)制解決一些編程問題的思路。讀者可以多多借鑒,思考鎖的應(yīng)用。
為什么在對(duì)amount重度操作(本文第二段代碼)的時(shí)候,計(jì)算就出錯(cuò)了!
這里amount
相當(dāng)于多線程都在操作的變量,也就是共享變量,多線程編程要特別注意這類變量,避免出現(xiàn)對(duì)共享變量的操作,有些程序在并發(fā)規(guī)模很小的時(shí)候一點(diǎn)問題也沒有。
并發(fā)編程是高度利用CPU計(jì)算能力的編程方式,并發(fā)程序也就是在并行執(zhí)行同類任務(wù)的程序。這個(gè)可以跟單線程應(yīng)用比較。
原文鏈接:https://levin.blog.csdn.net/article/details/121943332
相關(guān)推薦
- 2022-07-29 python?判斷字符串當(dāng)中是否包含字符(str.contain)_python
- 2022-05-18 Python學(xué)習(xí)之自定義異常詳解_python
- 2022-08-14 win?sever?2022如何占用操作主機(jī)角色_win服務(wù)器
- 2023-03-23 Nginx實(shí)現(xiàn)http自動(dòng)跳轉(zhuǎn)到https_nginx
- 2023-03-16 windows下Docker部署Flask的詳細(xì)教程_docker
- 2022-04-23 C語言字符串替換空格實(shí)例詳解_C 語言
- 2022-04-25 JQuery異步post上傳表單數(shù)據(jù)標(biāo)準(zhǔn)化模板_jquery
- 2022-07-20 上傳文件至Github倉庫
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支