Python多线程与多进程中join()方法的效果是相同的。

首先需要明确几个概念:

知识点一:
当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。

知识点二:
当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。

知识点三:
此时join的作用就凸显出来了,join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在终止。

知识点四:
join有一个timeout参数:
当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和。简单的来说,就是给每个子线程一个timeout的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。
没有设置守护线程时,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出

import threading
import time
def sub1():
    global count
    tmp = count
    time.sleep(0.001)
    count = tmp + 1
    time.sleep(2)
count = 0
def verify(sub):
    global count
    thread_list = []
    for i in range(100):
        t = threading.Thread(target=sub, args=())
        t.start()
        thread_list.append(t)
    for j in thread_list:
        j.join()
    print(count)
verify(sub1)

结果不稳定,
等我们把

tmp = count
    time.sleep(0.001)
    count = tmp + 1

换成

count+=1

结果就正常了,这是因为:
尽管count+=1是非原子操作,但是因为CPU执行的太快了,比较难以复现出多进程的非原子操作导致的进程不安全。经过代替之后,尽管只sleep了0.001秒,但是对于CPU的时间来说是非常长的,会导致这个代码块执行到一半,GIL锁就释放了。即tmp已经获取到count的值了,但是还没有将tmp + 1赋值给count。而此时其他线程如果执行完了count = tmp + 1, 当返回到原来的线程执行时,尽管count的值已经更新了,但是count = tmp + 1是个赋值操作,赋值的结果跟count的更新的值是一样的。最终导致了我们累加的值有很多丢失。造成线程不安全,
所以对此我们可以上锁来解决线程不安全.

为什么要加锁?

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

多线程枷锁
1.对于int,short,char,BOOL等小于等于4字节的简单数据类型,如果无逻辑上的先后关系,多线程读写可以完全不用加锁
2.尽管float为4字节,多线程访问时也需要加锁
3.对于大于4字节的简单类型,比如double,__int64等,多线程读写必须加锁。
4.对于所有复杂类型,比如类,结构体,容器等类型必须加锁

补充一点,当做多线程同步io操作时候。例如,多线程socket通讯。要用原子操作,如果用互斥锁会锁死。因为多核CPU轮训的不确定性,造成互斥锁同时产生竞争关系。如果使用原子操作(例如自旋锁),则会完美解决此问题

同步、异步、阻塞、非阻塞

python主线程获取子线程的变量值 python子线程不阻塞主线程_多线程


python主线程获取子线程的变量值 python子线程不阻塞主线程_python主线程获取子线程的变量值_02


1.同步机制 发送方发送请求之后,需要等接收方发回响应后才接着发

2.异步机制 发送方发送一个请求之后不等待接收方响应这个请求,就继续发送下个请求。

3.阻塞调用 调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回,该线程在此过程中不能进行其他处理

4.非阻塞调用 调用结果不能马上返回,当前线程也不会被挂起,而是立即返回执行下一个调用。(网络通信中主要指的是网络套接字Socket的阻塞和非阻塞方式,而soket 的实质也就是IO操作)

5.同步阻塞方式 发送方发送请求之后一直等待响应。接收方处理请求时进行的IO操作如果不能马上等到返回结果,就一直等到返回结果后,才响应发送方,期间不能进行其他工作

6.同步非阻塞方式 发送方发送请求之后,一直等待响应,接受方处理请求时进行的IO操作如果不能马上的得到结果,就立即返回,取做其他事情。但是由于没有得到请求处理结果,不响应发送方,发送方一直等待。一直等到IO操作完成后,接收方获得结果响应发送发后,接收方才进入下一次请求过程。(实际不应用)

7.异步阻塞方式 发送方向接收方请求后,不等待响应,可以继续其他工作,接收方处理请求时进行IO操作如果不能马上得到结果,就一直等到返回结果后,才响应发送方,期间不能进行其他操作。 (实际不应用)

8.异步非阻塞方式 发送方向接收方请求后,不等待响应,可以继续其他工作,接收方处理请求时进行IO操作如果不能马上得到结果,也不等待,而是马上返回取做其他事情。当IO操作完成以后,将完成状态和结果通知接收方,接收方在响应发送方。(效率最高)