您的位置:首页 > 编程语言

并发编程(三)

2022-01-19 00:46 976 查看

[toc]

线程

简介

线程是一个资源单位,真正被CPU执行的其实是进程里面的线程,例如打开word是打开一个进程,那么在里执行写,读,插入图片等操作就是进行一个个线程操作;

通俗理解:进程类似于工厂,线程类似于是工厂里面的一条条流水线,所有的进程肯定是最少有一个线程;

进程间数据默认是隔离的,但是同一个进程内的多个线程数据是共享的;

开设线程的两种方式

  • 开设进程需要做哪些操作
重新申请一块内存空间
将所需的资源全部导入
  • 开设线程需要做哪些操作
上述两个步骤都不需要 所以开设线程消耗的资源远比开设进程的少

方法一:

from threading import Thread
import time

def test(name):
print(f'{name}is running!')
time.sleep(2)
print(f'{name}is over!')

# 开设线程可以不用在main判断语句下开设
t = Thread(target=test,args=('Hammer',))
t.start()

print('主进程')

# 线程不需要考虑内存空间和资源的问题,也不需要等待主进程执行完再执行子进程等问题
# 所以开设线程所需要的时间更短,速度更快

方法二:

# 和进程类似,继承的方式
class Myclass(Thread):
def __init__(self,name):
super().__init__()
self.name = name

def run(self):
print(f'{self.name} is running !')
time.sleep(2)
print(f'{self.name} is over !')

t = Myclass('Hammer')
t.start()

线程对象的join方法

在进程中的join方法是,主进程等待子进程执行完再运行;

线程中顾名思义是一样的,主线程等待子线程执行完再执行

from threading import Thread
import time

def test(name):
print(f'{name} is running!')
time.sleep(2)
print(f'{name} is over! ')

# 开设线程
t = Thread(target=test,args=('子线程',))
t.start()
t.join()
print('主线程')

通过os.getpid()方法获取进程号来验证两个线程同属于一个进程

from threading import Thread
import time
import os
def test(name):
print(os.getpid()) # 9436
print(f'{name} is running!')
time.sleep(2)
print(f'{name} is over! ')

# 开设线程
t = Thread(target=test,args=('子线程',))
t.start() # 9436
print(os.getpid())
print('主线程')

线程之active_count模块

统计当前活跃的线程数

from threading import Thread,active_count
import time

def test(name):
print(f'{name} is running !')
time.sleep(2)
print(f'{name} is over !')

t = Thread(target=test,args=('线程1',))
t1 = Thread(target=test,args=('线程2',))
t.start()
t1.start()
print(active_count())  # 3
# 现有活跃线程数为3的原因是主线程加2个子线程

线程之current_thread模块

获取当前线程的名字

from threading import Thread,current_thread
import  time

def test(name):
print(f'子线程名:>>>{current_thread().name}')
print(f'{name} is running !')
time.sleep(2)
print(f'{name} is over!')

t = Thread(target=test,args=('线程1',))
t1 = Thread(target=test,args=('线程2',))
t.start()
t1.start()
print(f'主线程名:>>> {current_thread().name}')

# 结果
子线程名:>>>Thread-1
线程1 is running !
子线程名:>>>Thread-2
线程2 is running !
主线程名:>>> MainThread
线程2 is over!
线程1 is over!

# 子线程可以通过t.name直接拿,主线程必须通过方法.name拿

守护线程

类似守护进程一样,一个线程结束其他线程也得结束(陪葬)

主线程的结束意味着整个进程的结束,所以主线程需要等待里面所有非守护线程的结束才能结束

from threading import Thread
import time

def test(name):
print(f'{name} is running ')
time.sleep(2)
print(f'{name} is over !')

t = Thread(target=test,args=('子线程',))
t.daemon = True  # 守护线程必须放在start上
t.start()

print('主线程')

迷惑的例子,判断打印顺序

from threading import Thread
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(3)
print("end123")
def bar():
print(456)
time.sleep(1)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main-------")

# 结果
123
456
main-------
end123
end456

# 结果分析
开设线程直接打印123,然后遇到sleep方法
打印456,遇到sleep方法
打印main
这时候主线程没有直接结束,等待非守护线程(bar)结束才能结束,然而bar()方法中,sleep睡了3秒,那么print('end123')睡1秒就打印了

线程数据共享

验证同一个进程内线程间数据共享

注意是同一进程下!

from threading import Thread
money = 100

def test():
global money
money = 999

t = Thread(target=test)
t.start()
t.join()
print(money)

# 结果
999
# 这样是修改了的,和进程不一样

线程互斥锁

多个线程修改数据和多进程修改数据一样容易出现数据错乱,这样需要线程互斥锁来解决这个问题;

对比进程互斥锁中的查票买票代码理解;

from threading import Thread, Lock
from multiprocessing import Lock
import time

num = 100

def test(mutex):
global num
mutex.acquire()   # 抢锁
# 先获取num的数值
tmp = num
# 模拟延迟效果
time.sleep(0.1)
# 修改数值
tmp -= 1
num = tmp
mutex.release()   # 释放锁

t_list = []

# 声明锁
mutex = Lock()

for i in range(100):
t = Thread(target=test, args=(mutex,))
t.start()
t_list.append(t)
# 确保所有的子线程全部结束
for t in t_list:
t.join()
print(num)

# 如果不加锁,那么输出的num是99,比如减100次应该是0,在这里并发操作不加锁会产生数据的错乱,需要变成串行

补:TCP服务端实现并发

服务端

import socket
from threading import Thread
from multiprocessing import Process

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

# 和用户交互的代码封装
def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0: break
print(data.decode('utf8'))
sock.send(data + b'Hi!')
except ConnectionResetError as e:
print(e)
break
sock.close()

while True:
sock, addr = server.accept()
print(addr)
# 开设多进程或者多线程
t = Thread(target=talk, args=(sock,))
t.start()

客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))

whlie True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf8'))
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: