线程基本内容

1.线程:

  能被操作系统调度(给cpu执行)的最小单位
  同一个进程中的多个线程可以同时被CPU执行
  数据共享、可以利用多核;数据不安全
  开启关闭切换时间开销小
  在CPython中的多线程
  gc:垃圾回收机制
  引用计数 + 分代回收
  全局解释器锁的出现主要是为了完成gc的回收机制,对不同线程的引用计数的变化记录更加准确
  GIL:global interpreter lock
  导致了同一个进程中的多个线程只能有一个线程真正被cpu执行

  节省的是io操作的时间,而不是cpu计算的时间;因为cpu计算速度非常快,我们没有办法把一条进程中的所有io操作都规避掉
  JPython中: 垃圾回收机制不同;能利用多核

2.开启线程

import os
from threading import Thread, current_thread, enumerate, active_count
import time


def func(i):

    print(f'start{i}', current_thread().ident)
    time.sleep(1)
    print(f'end{i}')


if __name__ == '__main__':

    t_ls = []
    for i in range(10):
        t = Thread(target=func, args=(i, ))
        t.start()

        print(t.ident, os.getpid())

        t_ls.append(t)

    print(enumerate(), active_count())

    for t in t_ls: t.join()

    print('所有的线程都执行完了')


"""
线程是不能从外部terminate的
所有的子线程只能是自己执行完代码之后就关闭

current_thread 获取当前代码所在的线程的对象,通过.ident获取线程的id

enumerate 列表,存储了所有活着的线程对象,包括主线程
    active_count就是这个列表的长度

"""

3.开启线程的另一种方法

ading import Thread


class MyThread(Thread):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        super().__init__()

    def run(self):
        print(self.ident)


t = MyThread(1, 2)
t.start()  # 开启线程 才在线程中执行run方法

print(t.ident)

4.线程中的数据共享

n = 100


def func():
    global n
    n -= 1


t_ls = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_ls.append(t)

for t in t_ls:
    t.join()

print(n)

5.守护线程

import time
from threading import Thread


def son():
    while True:
        print('in son')
        time.sleep(1)


def son2():
    for i in range(3):
        print('in son2 ****')
        time.sleep(1)


# t1 = time.time()

t = Thread(target=son)
t.daemon = True
t.start()

# t2 = time.time()
# print(t2-t1)

Thread(target=son2).start()


"""
主线程会等待子线程结束之后才结束;因为主线程线束进程就会结束

守护线程会随着主线程的结束而结束,即等待其他子线程都结束后才结束

其他子线程 --> 主线程结束 --> 主进程结束 --> 整个进程中的所有资源被回收 --> 守护线程也被回收

"""

6.线程中数据不安全的现象

from threading import Thread
import dis
import time


# n = 0
# def add():
#     for i in range(200000):
#         global n
#         n += 1
#
#
# def sub():
#     for i in range(200000):
#         global n
#         n -= 1
#
#
# t1 = Thread(target=add)
# t1.start()
#
# t2 = Thread(target=sub)
# t2.start()
#
# t1.join()
# t2.join()
#
# print(n)


# a = 0
# def func():
#     global a
#     a += 1
#
#
# dis.dis(func)
"""
+= -= /= while if 等都是数据不安全  + 和=赋值是分开的两个操作
append pop 数据安全 列表中的方法或字典中的方法去操作全局变量时,数据是安全的
一般单线程不会出现数据安全的问题

33            0 LOAD_GLOBAL              0 (a)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
            如果在存储新的a值之前恰好GIL锁轮转了,就会造成数据不安全的问题
              6 STORE_GLOBAL             0 (a)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

"""
n = []
def add():
    for i in range(500000):
        n.append(1)


def sub():
    for i in range(500000):
        if not n:
            time.sleep(0.0000001)
        n.pop()


t_ls = []
for i in range(20):
    t1 = Thread(target=add)
    t1.start()

    t2 = Thread(target=sub)
    t2.start()

    t_ls.append(t1)
    t_ls.append(t2)

for t in t_ls:
    t.join()

print(n)

7.线程锁

from threading import Thread, Lock
import time


# n = 0
# def add(lock):
#     for i in range(200000):
#         global n
#         with lock:
#             n += 1
#
#
# def sub(lock):
#     for i in range(200000):
#         global n
#         with lock:
#             n -= 1
#
#
# lock = Lock()
#
# t1 = Thread(target=add, args=(lock, ))
# t1.start()
#
# t2 = Thread(target=sub, args=(lock, ))
# t2.start()
#
# t1.join()
# t2.join()
#
# print(n)


n = []
def add():
    for i in range(500000):
        n.append(1)


def sub(lock):
    for i in range(500000):
        with lock:
            if not n:  # 判断列表是否为空
                time.sleep(0.0000001)  # 堵塞,强制cpu轮转
            n.pop()


lock = Lock()
t_ls = []
for i in range(20):
    t1 = Thread(target=add)
    t1.start()

    t2 = Thread(target=sub, args=(lock, ))
    t2.start()

    t_ls.append(t1)
    t_ls.append(t2)

for t in t_ls:
    t.join()

print(n)

8.单例模式

from threading import Thread, Lock
import time


class A:
    __instance = None
    lock = Lock()

    def __new__(cls, *args, **kwargs):
        with cls.lock:
            if not cls.__instance:
                time.sleep(0.0000001)  # 这里cpu轮转可能会造成单例模式失效,所以需要加锁
                cls.__instance = super().__new__(cls)

            return cls.__instance


def func():
    a = A()
    print(a)


lock = Lock()

for i in range(10):
    Thread(target=func).start()

9.递归锁

from threading import Lock, RLock, Thread


"""
Lock:互斥锁
    效率较高


RLock:递归锁
    在同一个线程中可以连续acquire多次,但是必须以相同次数的release

"""
# lock = Lock()
# with lock:
#     pass
#
#
# rlock = RLock()
# with rlock:
#     pass


def func(i, rlock):
    rlock.acquire()
    rlock.acquire()
    print(i, ': start')
    rlock.release()
    print(i, ': end')
    rlock.release()


rlock = RLock()
for i in range(5):
    Thread(target=func, args=(i, rlock)).start()

10.死锁现象

"""
死锁现象是怎么产生的?
    多把锁,不管是互斥锁还是递归锁 并且在多个线程中 交叉使用

如果是互斥锁,出现了死锁现象,最快速的解决方案把所有的互斥锁都改成同一把递归锁
    但是程序的效率会降低

一般情况下,一把互斥锁就足够了

"""
from threading import Lock, RLock, Thread
import time


noodle_lock = Lock()
fork_lock = Lock()

# noodle_lock = fork_lock = RLock()


def eat(name):
    noodle_lock.acquire()
    print(f'{name}抢到面了')

    fork_lock.acquire()
    print(f'{name}抢到叉子了')
    print(f'{name}可以开始吃面了')

    time.sleep(0.0001)

    fork_lock.release()
    print(f'{name}放下了叉子')

    noodle_lock.release()
    print(f'{name}放下了面')


def eat2(name):
    fork_lock.acquire()
    print(f'{name}抢到叉子了')

    noodle_lock.acquire()
    print(f'{name}抢到面了')
    print(f'{name}可以开始吃面了')

    noodle_lock.release()
    print(f'{name}放下了面')

    fork_lock.release()
    print(f'{name}放下了叉子')


Thread(target=eat, args=('alex', )).start()
Thread(target=eat2, args=('wusir', )).start()
Thread(target=eat, args=('日杂', )).start()
Thread(target=eat2, args=('大黑', )).start()

11.队列

import queue  # 线程之间数据安全的容器队列   原理:加锁 + 链表


# q = queue.Queue(4)  # fifo 先进先出的队列
#
# for i in range(4):
#     q.put(i)

# q.put(5)  # 这里会堵塞,因为上面队列中只允许存储4个值
# q.put_nowait(5)  # 几乎不用

# for i in range(4):
#     print(q.get())
#
# print(q.get())  # 这里会堵塞,因为队列中的值已被取完


# try:
#     q.get_nowait()
# except queue.Empty:  # 不是内置的错误类型,而是queue模块中的错误
#     print("队列为空")

#
# from queue import LifoQueue  # last in first out 后进先出 栈
#
#
# lq = LifoQueue()
# lq.put(1)
# lq.put(2)
# lq.put(3)
#
# for i in range(3):
#     print(lq.get())


from queue import PriorityQueue  # 优先级队列,根据放入数据的ascii码来从小到大输出


priq = PriorityQueue()
priq.put((2, 'alex'))
priq.put((1, 'wusir'))
priq.put((3, 'lgq'))

for i in range(3):
    print(priq.get())

 

posted @ 2020-10-08 16:20  LGQ天  阅读(129)  评论(0编辑  收藏  举报