并发编程(三)

2022-03-24 15:33:25 浏览数 (1)

目录

  • 线程
    • 简介
    • 开设线程的两种方式
      • 方法一:
      • 方法二:
    • 线程对象的join方法
    • 线程之active_count模块
    • 线程之current_thread模块
    • 守护线程
    • 线程数据共享
    • 线程互斥锁
    • 补:TCP服务端实现并发

线程

简介

线程是一个资源单位,真正被CPU执行的其实是进程里面的线程,例如打开word是打开一个进程,那么在里执行写,读,插入图片等操作就是进行一个个线程操作; 通俗理解:进程类似于工厂,线程类似于是工厂里面的一条条流水线,所有的进程肯定是最少有一个线程; 进程间数据默认是隔离的,但是同一个进程内的多个线程数据是共享的;

开设线程的两种方式

  • 开设进程需要做哪些操作

重新申请一块内存空间 将所需的资源全部导入

  • 开设线程需要做哪些操作

上述两个步骤都不需要 所以开设线程消耗的资源远比开设进程的少

方法一:

代码语言:javascript复制
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('主进程')  


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

方法二:

代码语言:javascript复制
# 和进程类似,继承的方式
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方法是,主进程等待子进程执行完再运行; 线程中顾名思义是一样的,主线程等待子线程执行完再执行

代码语言:javascript复制
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()方法获取进程号来验证两个线程同属于一个进程

代码语言:javascript复制
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模块

统计当前活跃的线程数

代码语言:javascript复制
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模块

获取当前线程的名字

代码语言:javascript复制
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拿

守护线程

类似守护进程一样,一个线程结束其他线程也得结束(陪葬) 主线程的结束意味着整个进程的结束,所以主线程需要等待里面所有非守护线程的结束才能结束

代码语言:javascript复制
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('主线程')

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

代码语言:javascript复制
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秒就打印了

线程数据共享

验证同一个进程内线程间数据共享 注意是同一进程下!

代码语言:javascript复制
from threading import Thread
money = 100

def test():
    global money
    money = 999

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

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

线程互斥锁

多个线程修改数据和多进程修改数据一样容易出现数据错乱,这样需要线程互斥锁来解决这个问题; 对比进程互斥锁中的查票买票代码理解;

代码语言:javascript复制
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服务端实现并发

服务端

代码语言:javascript复制
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()

客户端

代码语言:javascript复制
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'))

0 人点赞