线程

作者: 两分与桥 | 来源:发表于2018-04-25 19:38 被阅读9次

操作系统承上启下,管理硬件,对上提供接口。
没有一个绝对的定义,
操作系统是一个用来协调、管理和控制计算机硬件和软件资源的系统程序,它位于硬件和软件之间。

操作系统的发展:
穿孔卡片——》晶体管和批处理系统,就是一批数据输入和一批数据输出
多道程序设计,就是当IO阻塞时切换任务——》分时操作系统:多个联机终端+多道技术
现在就是个人计算机了

进程:本质上就是一段程序的运行过程(抽象概念)
线程:最小的执行单元(实例)
进程:最小的资源单位

进程,就是CPU高速切换,对状态的保存和切换

进程内部开启进程,共享一套数据(这个是重点),就是线程,线程可以实现在进程内并发

1.一个程序至少有一个进程,一个进程至少有一个线程(进程可以理解成线程的容器)。

2.进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

3.线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行的的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

4.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位,

5.一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

那什么是并发、并行呢?

并发:是指系统具有处理多个任务(动作)的能力
并行:是指系统具有 同时 处理多个(动作)的能力

并行相对于并发多了“同时”这两个字,强调的是同一个时间点去执行,而不是相同与并发的 CPU 的高速切换进行

在知乎上看到一个很有意思的回答:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

十分通俗易懂吧

线程,线程内部是共享一套数据的,由于python的GIL锁限制,python 的每一个进程在同一时刻只能有一个线程在运行,而如果想同一个时刻运行多个线程的话,可以用多个进程来解决,但是进程消耗大,每个进程都有着不同的内存地址和数据,如果需要通信,那就要消耗相对与线程相比十几倍的资源去通信,不是必要,少用进程。

先看线程,下面上程序:

import threading

def aa(a):
    while a > 0:
        print('a ========== ', a)
        a = a - 1

def bb(b):
    while b > 0:
        print('b = ', b)
        b = b - 1

t1 = threading.Thread(target=aa, args=(100,))
t2 = threading.Thread(target=bb, args=(100,))

t1.start()
t2.start()

输出结果:(只贴出了一部分,可以看到 python 线程就是并发的,而不是并行的)
b =  12
a ==========  3
a ==========  2
a ==========  1
b =  11
b =  10
b =  9
b =  8
b =  7
b =  6
b =  5
b =  4
b =  3
b =  2
b =  1

自定义 Thread ,要继承 threading.Thread, start 方法会调用到 run 方法

import threading

class Mythread(threading.Thread):
    def __init__(self, a):
        threading.Thread.__init__(self)
        self.a = a
    def run(self):
        print('my run, a = ', self.a)

t = Mythread(100)
t.start()

输出结果:
 my run, a =  100

join 堵塞进程,就是在 t1.join 这句代码一直堵塞住,堵塞到 t1 执行完了,就算是 t2.join 也只能等待 t1 执行完毕(原来我以前理解的有问题啊)

import threading
import time

def aa():
    print(time.ctime()+'--a')
    time.sleep(3)
    print(time.ctime()+'--a')
def bb():
    print(time.ctime()+'-----b')
    time.sleep(6)
    print(time.ctime()+'-----b')

t1 = threading.Thread(target=aa)
t2 = threading.Thread(target=bb)

t1.start()
t2.start()

t1.join() # 在这里阻塞住,直到 线程 t1 执行完毕,
print('-------------------------------------------------------', time.ctime())
t2.join()

输出结果:
Wed Apr 25 20:15:18 2018--a
Wed Apr 25 20:15:18 2018-----b
Wed Apr 25 20:15:21 2018--a
------------------------------------------------------- Wed Apr 25 20:15:21 2018
Wed Apr 25 20:15:24 2018-----b

主线程等待子线程都执行完毕后才退出,
可以看看下面这个,十分清楚

import threading
import time

def aa():
    print(time.ctime()+'--a')
    time.sleep(3)
    print(time.ctime()+'--a')
def bb():
    print(time.ctime()+'-----b')
    time.sleep(6)
    print(time.ctime()+'-----b')

l = []
t1 = threading.Thread(target=aa)
l.append(t1)
t1.start()
t2 = threading.Thread(target=bb)
l.append(t2)
t2.start()
for i in l:
    i.join() #当线程 t1 执行结束,才执行下面的打印操作
    print(i)
print('-------------------------------------------------------', time.ctime())

输出结果:
Wed Apr 25 20:24:03 2018--a
Wed Apr 25 20:24:03 2018-----b
Wed Apr 25 20:24:06 2018--a
<Thread(Thread-1, stopped 1416)>     # t1 执行完了,走下一句
Wed Apr 25 20:24:09 2018-----b
<Thread(Thread-2, stopped 12460)>
------------------------------------------------------- Wed Apr 25 20:24:09 2018

守护线程,就是设置后的子线程跟随主线程一起退出,不管子线程是否执行完毕了,用 t.setDaemon(True),就是子线程守护主线程。。
很重要的一点是,守护线程要设置在 t.start() 之前

import threading
import time

def aa():
    print(time.ctime()+'--a')
    time.sleep(3)
    print(time.ctime()+'--a')
def bb():
    print(time.ctime()+'-----b')
    time.sleep(6)
    print(time.ctime()+'-----b')

l = []
t1 = threading.Thread(target=aa)
l.append(t1)

t2 = threading.Thread(target=bb)
l.append(t2)

for i in l:
    i.setDaemon(True)  # 必须设置在 t.start() 之前
    i.start()
    print(i)
print('-------------------------------------------------------', time.ctime())

输出结果:
Wed Apr 25 20:36:36 2018--a
<Thread(Thread-1, started daemon 2708)>
Wed Apr 25 20:36:36 2018-----b
<Thread(Thread-2, started daemon 5988)>
------------------------------------------------------- Wed Apr 25 20:36:36 2018
#主线程一旦执行完,整个程序就立即退出了,子线程跟随主线程一齐退出

同步 & 异步

同步:当进程执行到一个IO(等待外部数据)的时候,-------在等待接收
异步                                       --------不等待,一直到数据接收成功,再回来处理

python 的 GIL ,也就是全局解释锁
因为有 GIL,所以,同一时刻,一个进程内,只有一个线程被 CPU 执行

任务可以分为,IO密集型和计算密集型,

对于 IO 密集型的任务,python的多线程是有意义的,time.sleep()是IO,爬虫也是。
对于计算密集型的任务,python的多线程就不推荐,可以采用多进程+协程(进程也是十分消耗资源,特别是进程之间的切换)

Python 同步锁
同步锁,就是加锁的模块不切换,变成串行的模式进行

先看一个不加同步锁的,

import threading
import time

num = 100
def test():
    global num
    time.sleep(0.1)
    temp = num
    num = temp - 1

l = []

for i in range(100):
    t = threading.Thread(target=test)
    t.start()
    l.append(t)

for t in l:
    t.join()

print(num)


输出结果:
0  # 现在的 python 3 执行结果每次都是0
(------num 的输出值会随着每次执行都不相同,当第一个线程取到num的值时为100, 遇到time.sleep 
就切换到下一个线程,第二个线程取到的num的值还是100(因为第一个线程没有执行完赋值给num),
遇到time.sleep 还是切换到下一个线程,到第三个,第四个,········
直到第一个不再堵塞了,也就是第一个线程的time.sleep 执行完了,再赋值······-------

加了同步锁,也就是加了锁的代码在同一时刻只允许一个线程运行,就是程序在加锁的代码上是以串行执行的,所以下面这个程序的运行时间就为十秒多(0.1 * 100)

import threading
import time

num = 100
def test():
    global num
    A.acquire()
    time.sleep(0.1)
    temp = num
    num = temp - 1
    A.release()


A = threading.Lock()
l = []

for i in range(100):
    t = threading.Thread(target=test)
    t.start()
    l.append(t)

for t in l:
    t.join()

print(num)

输出还是0

同步锁会有死锁现象,就是线程A有A锁,想获得B锁,线程B有B锁,想获得A锁,彼此不后退,就成了死锁

import threading
import time

class Mythread(threading.Thread):
    def run(self):
        self.actionA()
        self.actionB()

    def actionA(self):
        A.acquire()
        print(self.name,'gotA',time.ctime())
        time.sleep(2)

        B.acquire()
        print(self.name,'gotB',time.ctime())
        time.sleep(1)

        B.release()
        A.release()

    def actionB(self):
        B.acquire()
        print(self.name,'gotB',time.ctime())
        time.sleep(2)

        A.acquire()
        print(self.name,'gotA',time.ctime())
        time.sleep(1)

        A.release()
        B.release()

if __name__ == '__main__':

    A = threading.Lock()
    B = threading.Lock()
    l = []
    for i in range(5):
        t = Mythread()
        t.start()
        l.append(t)

    for t in l:
        t.join()

    print('ending-------------------------')

输出结果:
Thread-1 gotA Thu Apr 26 08:30:31 2018
Thread-1 gotB Thu Apr 26 08:30:33 2018
Thread-1 gotB Thu Apr 26 08:30:34 2018
Thread-2 gotA Thu Apr 26 08:30:34 2018
(一直卡在这里,程序无法向后运行)
(也就是线程1把actionA函数执行完后,释放了A、B锁,A锁被释放后就被线程2抢占,执行actionA,
线程1要去执行actionB,获得了B锁,线程1、线程2等到各自从休眠中醒来时,都找不到各自需要的锁,
彼此又都不释放,就成了死锁。)

而解决的方法呢,就是递归锁,
递归锁,内部存在计数器,如果计数器为0,线程就可以拿
计数器每 acquire 一次加1,每 release 一次减1,不为0线程就不能拿

上面的程序改成下面的样子:

import threading
import time

class Mythread(threading.Thread):
    def run(self):
        self.actionA()
        self.actionB()

    def actionA(self):
        r_lock.acquire() #每 acquire 一次 RLock 内部计数器 count 加1
        print(self.name,'gotA',time.ctime())
        time.sleep(2)

        r_lock.acquire() # count = 2
        print(self.name,'gotB',time.ctime())
        time.sleep(1)

        r_lock.release() # count =1 每 release 一次 count 减1
        r_lock.release() # count = 0 RLock 的 count=0 就可以被其他线程调用了

    def actionB(self):
        r_lock.acquire()
        print(self.name,'gotB',time.ctime())
        time.sleep(2)

        r_lock.acquire()
        print(self.name,'gotA',time.ctime())
        time.sleep(1)

        r_lock.release()
        r_lock.release()

if __name__ == '__main__':

    r_lock = threading.RLock()
    l = []
    for i in range(5):
        t = Mythread()
        t.start()
        l.append(t)

    for t in l:
        t.join()

    print('ending-------------------------')

输出结果:
Thread-1 gotA Thu Apr 26 08:41:29 2018
Thread-1 gotB Thu Apr 26 08:41:31 2018
Thread-2 gotA Thu Apr 26 08:41:32 2018
Thread-2 gotB Thu Apr 26 08:41:34 2018
Thread-2 gotB Thu Apr 26 08:41:35 2018
Thread-2 gotA Thu Apr 26 08:41:38 2018
Thread-4 gotA Thu Apr 26 08:41:39 2018
Thread-4 gotB Thu Apr 26 08:41:41 2018
Thread-5 gotA Thu Apr 26 08:41:42 2018
Thread-5 gotB Thu Apr 26 08:41:44 2018
Thread-5 gotB Thu Apr 26 08:41:45 2018
Thread-5 gotA Thu Apr 26 08:41:47 2018
Thread-3 gotA Thu Apr 26 08:41:48 2018
Thread-3 gotB Thu Apr 26 08:41:50 2018
Thread-3 gotB Thu Apr 26 08:41:51 2018
Thread-3 gotA Thu Apr 26 08:41:53 2018
Thread-1 gotB Thu Apr 26 08:41:54 2018
Thread-1 gotA Thu Apr 26 08:41:56 2018
Thread-4 gotB Thu Apr 26 08:41:57 2018
Thread-4 gotA Thu Apr 26 08:41:59 2018
ending-------------------------
(整个程序就以串行的形式执行)

同步条件,event,三种属性,event.set():给出条件; event.clear():清除上次给的条件; event.wait() :不满足条件时一直堵塞

import threading
import time

def AA():
    time.sleep(1)
    print('aa',event.is_set()) #event.is_set() 方法检测是否设置了 set()
    event.set()
    time.sleep(5)
    print('aa, end')
    event.set()
def BB():
    event.wait() #没有 set 时一直堵塞
    print('bb')
    time.sleep(1)
    event.clear()

    event.wait()
    print('bb end')

if __name__ == '__main__':
    event = threading.Event()
    l = []
    for i in range(3):
        t = threading.Thread(target=BB) # 加入三个 BB 线程
        l.append(t)
    t = threading.Thread(target=AA)
    l.append(t)

    for i in l: # 线程A是加在list最后的,也是最后被运行的
        i.start()
    for i in l:
        i.join()

输出结果:
aa False #只有当AA给出同步条件后 event.wait() 才不再堵塞
bb
bb
bb
aa, end
bb end
bb end
bb end

信号量,也就是固定同时运行确定的线程数目,多的线程等到运行的线程结束才能运行

import threading
import time

def AA(x):
    if semaphore.acquire():
        print(x, time.ctime())
        time.sleep(3)
        print(x, 'end', time.ctime())
        semaphore.release()

if __name__ == '__main__':
    semaphore = threading.Semaphore(2)
    l = []
    for i in range(6):
        t = threading.Thread(target=AA, args=(i,))
        t.start()
        l.append(t)
    print('end--------')

输出结果:
0 Thu Apr 26 10:38:20 2018
1 Thu Apr 26 10:38:20 2018
end--------
0 end Thu Apr 26 10:38:23 2018
2 Thu Apr 26 10:38:23 2018
1 end Thu Apr 26 10:38:23 2018
3 Thu Apr 26 10:38:23 2018
2 end Thu Apr 26 10:38:26 2018
4 Thu Apr 26 10:38:26 2018
3 end Thu Apr 26 10:38:26 2018
5 Thu Apr 26 10:38:26 2018
4 end Thu Apr 26 10:38:29 2018
5 end Thu Apr 26 10:38:29 2018

队列,分为进程队列和线程队列,queue,涉及到数据安全,也就是线程间通信,最主要的是同步,先看一个不用队列的情况

import threading
import time

l = ['1','2','3','4','5']

def AA():
    a = l[-1]
    print(a)
    time.sleep(1)
    l.remove(a)

if __name__ == '__main__':
    li = []
    for i in range(2):
        t = threading.Thread(target=AA)
        t.start()
        li.append(t)
    for t in li:
        t.join()
    print('end--------')

输出结果直接报错了,可以看到有两个线程取到了一样的数字,当list中的5被删除后,
另一个线程的5就找不到了,自然而然就报错了
5
5
end--------
Exception in thread Thread-2:
Traceback (most recent call last):
  File "D:\anaconda\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "D:\anaconda\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "C:/Users/libai/PycharmProjects/begin/a/process.py", line 10, in AA
    l.remove(a)
ValueError: list.remove(x): x not in list

队列的三种模式:
Queue() :先进先出,FIFO
LifoQueue() :先进后出,LIFO
PriorityQueue() :优先级模式,可以设定队列元素的优先级

import queue  # 属于线程队列
a = queue.LifoQueue
b = queue.PriorityQueue

q = queue.Queue() # 默认是先进先出模式,FIFO,可以加参数,
                  # 如queue.Queue(2) 默认只能放两个数,想要再放数据
#                 # 进去,就要等待了,会一直等待到有数据被取出,
                  # 自己的数据放进去了为止
q.put(12)
q.put('bbs')
q.put({'name':'li'})

while True:
    data = q.get() #get 不到数据也会一直等待,等到取到数据了为止
    print(data)
    print('------------------------------')

输出结果:
12
------------------------------
bbs
------------------------------
{'name': 'li'}
------------------------------
# ······没有执行完 一直堵塞

生产者、消费者模型,中间用队列作为中介处理,彼此解耦。
队列负责通信。


下面就是进程了,看下一篇
参考大神的文章:https://www.cnblogs.com/yuanchenqi/articles/6248025.html

相关文章

  • Android

    线程间通信 主线程和工作线程主线程和工作线程 工作线程与工作线程工作线程与工作线程 为什么主线程Looper.lo...

  • 三、操作系统之线程

    前言 什么是线程 引入线程的原因 线程的概念 线程和进程的关系 线程结构 线程有点 多线程模型 用户线程和内核线程...

  • Thread

    队列 线程锁 多线程,线程池 队列 多线程爬虫示例 多线程 自定义线程 线程池

  • 总结多线程与设计模式+synchronized+性能+高吞吐+死

    Java线程 Java语言的线程 何谓线程 线程启动 线程的暂时停止 线程的共享互斥 线程的协调 线程的状态转移 ...

  • 多线程编程

    摘要 线程概念,线程与进程的区别与联系学会线程控制,线程创建,线程终止,线程等待了解线程分离与线程安全学会线程同步...

  • java线程池

    线程VS线程池 普通线程使用 创建线程池 执行任务 执行完毕,释放线程对象 线程池 创建线程池 拿线程池线程去执行...

  • java并发之守护线程

    java中有两种线程,用户线程和守护线程用户线程:主线程停止时,用户线程不会停止守护线程:主线程停止时,守护线程也...

  • Java线程池的使用

    线程类型: 固定线程 cached线程 定时线程 固定线程池使用 cache线程池使用 定时调度线程池使用

  • 线程基础知识

    线程学习 线程的基础知识 线程是什么? 线程和进程的关系 线程的6个状态 线程优先级 主线程、多线程、后台线程的概...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

网友评论

      本文标题:线程

      本文链接:https://www.haomeiwen.com/subject/oxaylftx.html