python3 多线程并发

149 阅读3分钟

threading

  • 继承threading.thread
  • 直接使用threading.thread
  • join等待线程执行结束

下面代码每个任务都是休眠10秒,如果不使用多线程则一共需要30秒,而使用了多线程,只需要10秒。

import time
from threading import Thread


def task1():
    print("task1")
    time.sleep(10)
    print("task1 finish")


def task2():
    print("task2")
    time.sleep(10)
    print("task2 finish")


class MyThread(Thread):
    def run(self):
        print("mythread")
        time.sleep(10)
        print("mythread finish")


if __name__ == '__main__':
    t = time.time()
    # 创建线程的方式1
    thread1 = Thread(target=task1)
    thread2 = Thread(target=task2)
    # 创建线程的方式2
    thread3 = MyThread()
    thread1.start()
    thread2.start()
    thread3.start()
    # 等待线程结束
    thread1.join()
    thread2.join()
    thread3.join()
    
    print(time.time() - t)

主线程默认等待子线程执行结束,如果主线程想提前结束,则设置为守护子线程

如果主线程执行完了,但是非守护线程还没执行结束,此时,守护线程会继续执行一直到非守护线程结束。

import threading
import time


def task():
    for i in range(10):
        print(i)
        time.sleep(1)


thread = threading.Thread(target=task)
thread.start()
import threading
import time


def task():
    while True:
        print("child thread")
        time.sleep(1)


# 默认主线程会等待所有的子线程都执行才会退出
# 可以设置deamon,标记子线程为守护线程,这样主线程执行结束,子线程自动退出
thread = threading.Thread(target=task, daemon=True)
thread.start()

使用多线程实现socket并发

服务端

import socket
from threading import Thread

'''
a = "hello"
# 字符串转byte
a = bytes(a, encoding="utf-8")
print(a)
a = str(a, encoding="utf-8")
print(a)
'''


# server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建socket
server = socket.socket()

# 绑定端口号
server.bind(("0.0.0.0", 8080))
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server.listen(100)


def do_communicate(conn):
    count = 0
    while True:
        try:
            data = conn.recv(1024*1024)
            print(len(data))
            if len(data) == 0 : break
            # print(data.decode("utf-8"))
            count += 1
            msg = f"收到{count}条消息."
            conn.send(msg.encode("utf-8"))

        except ConnectionResetError as e:
            print(e)


while True:
    conn, (ip, addr) = server.accept()
    t = Thread(target=do_communicate, args=(conn,))
    t.start()

客户端

import socket
import time

if __name__ == "__main__":
    client = socket.socket()
    client.connect(("127.0.0.1", 8080))

    while True:
        str = "1234567890"*1024*1024
        client.send(str.encode("utf-8"))
        time.sleep(1)
        data = client.recv(1024)
        print(data.decode("utf-8"))
    client.close()

线程属性

import time
from threading import  Thread,active_count,current_thread


def task():
    print("child thread : ", current_thread(), current_thread().name)
    time.sleep(1)

if __name__ == '__main__':

    t = Thread(target=task)
    t.start()

    print("active", active_count())  # 当前激活的线程数
    print("main thread:", current_thread(), current_thread().name)

互斥锁

如果没有锁,多线程会出现数据错误。

import time
from threading import Thread, active_count, current_thread

ticket = 1000


def sell_ticket():
    global ticket
    temp = ticket
    if temp == 0:
        print("没票了")
        return
    time.sleep(1)
    temp -= 1
    ticket = temp
    print("ticket", ticket)


if __name__ == '__main__':

    list = []
    for i in range(1000):
        t = Thread(target=sell_ticket)
        t.start()
        list.append(t)
    for t in list:
        t.join()
    print("tick left", ticket)

通过添加互斥锁解决数据错乱的问题

import time
from threading import Thread, Lock
ticket = 1000
mutex = Lock()

def sell_ticket():
    global ticket
    mutex.acquire()
    temp = ticket
    if temp == 0:
        print("没票了")
        return
    time.sleep(0.1)
    temp -= 1
    ticket = temp
    print("ticket", ticket)
    mutex.release()


if __name__ == '__main__':

    list = []
    for i in range(1000):
        t = Thread(target=sell_ticket)
        t.start()
        list.append(t)
    for t in list:
        t.join()
    print("tick left", ticket)

GIL全局解释器锁

因为CPyhon解释器的内存管理不是线程安全的,因此会阻止多个线程同时执行代码。

下面代码看起来没问题,但是如果range(1000)改成range(2000)就会出现-1000。但是如果把time.sleep(0.1)删除。则程序可以正常运行。

import time
from threading import Thread, Lock
ticket = 1000
def sell_ticket():
    global ticket
    if ticket == 0:
        print("没票了")
        return
    time.sleep(0.1)
    ticket-=1
    print("ticket", ticket)


if __name__ == '__main__':

    list = []
    for i in range(1000):
        t = Thread(target=sell_ticket)
        t.start()
        list.append(t)
    for t in list:
        t.join()
    print("tick left", ticket)

递归锁

# 递归锁
# 获取锁的之后,可以继续获取锁
import time
from threading import Thread, RLock

mutext = RLock()


def task1():
    mutext.acquire()
    mutext.acquire()
    time.sleep(1)
    print("abc")
    mutext.release()
    mutext.release()


if __name__ == '__main__':
    t1 = Thread(target=task1)
    t2 = Thread(target=task1)
    t1.start()
    t2.start()

信号量

可以设置同时并发数。

from threading import Thread, Semaphore
import time
import random

# sem = Semaphore()  # 默认为1
sem = Semaphore(3)


def task(name):
    sem.acquire()
    print(f"{name} task...")
    time.sleep(random.randint(2, 6))
    sem.release()


if __name__ == '__main__':
    Thread(target=task, args="1").start()
    Thread(target=task, args="2").start()
    Thread(target=task, args="3").start()
    Thread(target=task, args="4").start()
    Thread(target=task, args="5").start()
    Thread(target=task, args="6").start()

Event

# event事件
import time
from threading import Thread, Event

event = Event()


def task1():
    print("task1")
    time.sleep(3)
    print("task1 finished")
    event.set()


def task2():
    print("task2")
    event.wait()  # 等待event通知
    print("task2 finished")


if __name__ == '__main__':
    Thread(target=task1).start()
    Thread(target=task2).start()