线程

什么是进程:

指的是一条流水线的工作过程,关键的一句话:一个进程内最少自带一个线程,其实进程根本不能执行,进程不是执行单位,是资源的单位,分配资源的单位。线程才是执行单位

 

进程vs线程:

1, 同一个进程内的多个线程是共享该进程的资源的,不同进程内的线程资源肯定是隔离的

2, 创建线程的开销比创建进程的开销要小的多

 

并发三个任务:1,启动三个进程:因为每个进程中有一个线程,但是我一个进程中开启三个线程就够了

进程有很多优点,它提供了多道编程,让我们感觉我们每一个人都拥有自己的cpu和其他资源,可以提高计算机的利用率。

进程缺点:

1, 进程只能在同一时间干一件事,如果想同时干两件事或多件事,进程就无能为力了

2, 进程在执行的过程中如果阻塞,也将无法执行

注意:进程是资源分配的最小单位,线程是cpu调度的最小单位,每一个进程中至少有一个线程

 

二、进程和线程之间的关系

1、地址空间和其他资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他程度不可见

2、通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来通信---需要进程同步和互斥手段的辅助,以保证数据的一致性。就类似进程中的锁的作用

3、调度和切换:线程上下文切换比进程上下文切换要快的多

4、在多线程操作系统中,进程不是一个可执行的实体,真正去执行程序的不是进程,是线程。

 

三、线程的特点

1、轻型实体

线程中的实体基本上不拥有系统资源,只是有一些不可少的、能保证独立运行的资源。

线程的实体包括程序、数据和TCB

TCB包括以下信息:

(1)      线程状态

(2)      当线程不运行时,被保存的现场资源

(3)      一组执行堆栈

(4)      存放每个线程的局部变量主存区

(5)      访问同一个进程中的主存和其他资源

2、独立调度和分派的基本单位

在多线程OS中,线程时能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小

3、共享进程资源

线程在同一进程中的各个线程都可以共享该进程所拥有的资源

4、可并发执行

在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程能并发执行;同样,不同进程中的线程也能并发执行,允许利用和发挥了处理机与外围设备并行工作的能力。

 

四、线程的实际应用场景

开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程,只能在一个进程里开启三个线程,如果时单线程,那就只能时,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字

 

五、内存中的线程

线程的问题是:

1, 父进程有多个线程,那么开启的子进程是否需要同样多的线程

2, 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?因此,在多线程的代码中,需要更多的心思来设计程序的逻辑,保护程序的数据

 

六、用户级线程和内核级线程

线程的实现可以分为两类:用户级线程和内核级线程,后者又称为内核支持的线程或轻量级进程,在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户及线程,有的系统实现了内核级线程

 

1, 用户级线程

内核的切换由用户状态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核cpu,在用户空间模拟操作系统对进程的调度,来调用一个进程中的线程,每个进程中都会由一个运行时系统,用来调度线程。此时当该进程获取cpu时,进程内再调度出一个线程去执行,同一时刻只有一个线程执行

2, 内核级线程

切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回给用户态;可以很好的利用smp,即利用多核cpu。Windows线程就是这样

3, 用户级和内核级线程的对比

1, 内核支持线程是os内核可感知的,而用户级线程是os内核不可感知的

2, 用户级线程的创建、撤销和调度不需要os内核的支持,是在语言(如Java)这一级处理的,而内核支持线程的创建、撤销和调度都需要os内核提供支持,而且与进程的创建、撤销和调度大体是相同的

3, 在只有用户级线程的体统内,cpu调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在由内核支持线程的系统内,cpu调度则以线程为单位,由os的线程调度程序负责线程的调度

4, 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断

5, 用户级线程的程序实体时运行在用户状态下的程序,而内核线程的程序实体则时可以运行在任何状态下的程序

 

 

内核级线程的优缺点:

优点:当有多个处理机时,一个进程的多个线程可以同时执行

缺点:由内核进行调度

 

用户级线程的优缺点:

优点:

线程的调度不需要内核直接参与,控制简单

可以在不支持线程的操作系统中实现

创建和销毁线程,线程切换代价等进程管理的代价比内核线程少得多

允许每个进程定制自己的调度算法,线程管理比较灵活。

线程能够利用的表空间和堆栈空间比内核级线程多

同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题

缺点:

资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用

 

3、混合实现

用户级与内核级的多路复用,内核同一调度内核线程,每个内核线程对应n个用户线程,用户创建一个线程,那么操作系统内核也跟着创建一个线程来专门执行你用户的这个线程

 

import time
from threading import Thread
#多线程并发,是不是看着和多进程很类似
def func(n):
    time.sleep(1)
    print(n)

#并发效果,1秒打印出了所有的数字
for i in range(10):
    t = Thread(target=func,args=(i,))
    t.start()
多线程简单实现

 

1.线程创建

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('太白',))
    t.start()
    print('主线程')
方式1

 

import time
from threading import Thread
class Sayhi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        time.sleep(2)
        print('%s say hello' % self.name)


if __name__ == '__main__':
    t = Sayhi('太白')
    t.start()
    print('主线程')
方式2

 

2.多线程与多进程

 

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello',os.getpid())

if __name__ == '__main__':
    #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1=Thread(target=work)
    t2=Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid',os.getpid())

    #part2:开多个进程,每个进程都有不同的pid
    p1=Process(target=work)
    p2=Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid',os.getpid())

首先来看看pid(进程id)
pid
from  threading import Thread
from multiprocessing import Process
import os
def work():
    global n  #修改全局变量的值
    n=0

if __name__ == '__main__':
    # n=100
    # p=Process(target=work)
    # p.start()
    # p.join()
    # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100


    n=1
    t=Thread(target=work)
    t.start()
    t.join()   #必须加join,因为主线程和子线程不一定谁快,一般都是主线程快一些,所有我们要等子线程执行完毕才能看出效果
    print('',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
# 通过一个global就实现了全局变量的使用,不需要进程的IPC通信方法
进程与线程开启效率比较

 

  在这里我们简单总结一下:

    进程是最小的内存分配单位

    线程是操作系统调度的最小党委

    线程被CPU执行了

    进程内至少含有一个线程

    进程中可以开启多个线程 

      开启一个线程所需要的时间要远小于开启一个进程

      多个线程内部有自己的数据栈,数据不共享

      全局变量在多个线程之间是共享的

 

3.多线程实现socket

import multiprocessing
import threading

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
s.listen(5)


def action(conn):
    while 1:
        data = conn.recv(1024)
        print(data)
        msg = input('服务端输入:')
        conn.send(bytes(msg, encoding='utf-8'))


if __name__ == '__main__':

    while 1:
        conn, addr = s.accept()
        p = threading.Thread(target=action,args=(conn,))
        p.start()
服务端
import socket

s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',8001))

while 1:
    msg = input('>>>: ').strip()
    if not msg:continue

    s.send(msg.encode('utf-8'))
    data = s.recv(1024)
    print(data)
客户端

4.Thread类的其他方法

 

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
其他方法

 

 

from threading import Thread
import threading
from  multiprocessing import Process
import os
import time
def work():
    time.sleep(3)
    print(threading.current_thread().getName())

if __name__ == '__main__':
    t = Thread(target=work)
    t.start()

    print(threading.current_thread())
    print(threading.current_thread().getName())  # 主线程名称
    print(threading.current_thread().ident)  # 主线程ID
    print(threading.get_ident())  # 主线程ID
    print(threading.enumerate())  # 连同主线程在内有两个运行的线程
    print(threading.active_count())
    print('主线程/主进程')
    '''
    打印结果:
    <_MainThread(MainThread, started 14104)>
    MainThread
    14104
    14104
    [<_MainThread(MainThread, started 14104)>, <Thread(Thread-1, started 17976)>]
    2
    主线程/主进程
    Thread-1
    '''
代码示例

5.守护线程

1, 守护线程

无论是进程还是线程,都遵循守护xxx会等待主xxx运行完毕后销毁。需要强调的是:运行完毕并非终止运行

对主进程来说,运行完毕指的是主进程代码运行完毕

对于主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello'%name)
if __name__ == '__main__':
    t = Thread(target=sayhi,args=('taibai',))
    t.setDaemon(True)
    t.start()

    print('主进程')
    print(t.is_alive())
守护进程示例1

 

from threading import Thread
from multiprocessing import Process
import time

def func1():
    while 1:
        print(666)
        time.sleep(0.5)
def func2():
    print('hello')
    time.sleep(3)
if __name__ == '__main__':
    t = Thread(target=func1,)
    t.daemon = True
    t.start()
    t2 = Thread(target=func2,)
    t2.start()
    print('主线程代码执行完了')
    # p = Process(target=func1,)
    # p.daemon = True
    # p.start()
    #
    # p2 = Process(target=func2,)
    # p2.start()
    # time.sleep(1) #让主进程等1秒,为了能看到func1的打印效果
    # print('主进程代码执行完啦!') #通过结果你会发现,如果主进程的代码运行完毕了,那么主进程就结束了,因为主进程的守护进程p随着主进程的代码结束而结束了,守护进程被回收了,这和线程是不一样的,主线程的代码完了并不代表主线程运行完毕了,需要等着所有其他的非守护的子线程执行完毕才算完毕
守护进程示例2

 

 锁

import time
from threading import Thread,Lock

num = 100
def func(tl):
    # time.sleep(3)
    print('xxxxx')
    time.sleep(1)
    global num
    tl.acquire()
    tep = num
    time.sleep(0.001)
    tep = tep - 1
    num = tep
    tl.release()
    # num -= 1

if __name__ == '__main__':
    tl = Lock()
    t_list = []
    for i in range(10):
        t = Thread(target=func,args=(tl,))
        t_list.append(t)
        t.start()
    [tt.join() for tt in t_list]
    # t.join()
    print('主线程的num',num)
加锁解决数据不安全的问题

 

import time
from threading import Thread,Lock,RLock

def func1(lock_A,lock_B):
    lock_A.acquire()
    time.sleep(0.5)
    print('alex拿到了A锁')
    lock_B.acquire()
    print('alex拿到了B锁')
    lock_B.release()
    lock_A.release()

def func(lock_A,lock_B):
    lock_B.acquire()
    print('taibai拿到了B锁')
    lock_A.acquire()
    print('taibai拿到了A锁')
    lock_A.release()
    lock_B.release()
if __name__ == '__main__':
    lock_A = Lock()
    lock_B = Lock()
    t1 = Thread(target=func1,args=(lock_A,lock_B))
    t2 = Thread(target=func1, args=(lock_A, lock_B))
    t1.start()
    t2.start()
死锁

 

import time
from threading import Thread,Lock,RLock

def func1(lock_A,lock_B):
    lock_A.acquire()
    time.sleep(0.5)
    print('alex拿到了A锁')
    lock_B.acquire()
    print('alex拿到了B锁')
    lock_B.release()
    lock_A.release()

def func2(lock_A,lock_B):
    lock_B.acquire()
    print('taibai拿到了B锁')
    lock_A.acquire()
    print('taibai 拿到了A锁')
    lock_A.release()
    lock_B.release()

if __name__ == '__main__':
    # lock_A = RLock()
    # lock_A = Lock()
    # lock_B = RLock()
    # lock_B = Lock()
    lock_A = lock_B = RLock()
    # lock_A = lock_B = Lock()
    t1 = Thread(target=func1,args=(lock_A,lock_B))
    t2 = Thread(target=func2,args=(lock_A,lock_B))
    t1.start()
    t2.start()
递归锁解决死锁问题

 

1, 信号量

Semaphore 管理一个内置的计数器

每当调用acquire()时内置计数器-1

调用release()时内置计数器+1

计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()

 

import time
import random
from threading import Thread,Semaphore

def func1(i,s):
    s.acquire()
    print('客官%s里面请~~'%i)
    time.sleep(random.randint(1,3))
    s.release()

if __name__ == '__main__':
    s = Semaphore(4)
    for i in range(10):
        t = Thread(target=func1,args=(i,s))
        t.start()
信号量

 

from threading import Thread,Event

e = Event()  #默认是False,

# print(e.isSet())
print('开始等啦')
e.set() #将事件对象的状态改为True
print(e.isSet())
# e.clear() #将e的状态改为False

e.wait()  #如果e的状态为False,就在这里阻塞
print('大哥,还没完事儿,你行')
事件

 

 

 

 

 

 

 

 

 

 

posted @ 2018-12-01 14:52  DanielYang11  阅读(231)  评论(0编辑  收藏  举报