我想测试一下c/c++,python,java和kotlin多线程的用法。这里先测试一下C/C++和python的,当然是简单的测试啦!
C/C++多线程
①win32平台,简单的c++多线程程序如下:
#include<windows.h>
#include<iostream.h>
DWORD WINAPI Thread1(LPVOID lpParameter);
void main(){
HANDLE hThread=CreateThread(NULL,0,Thread1,NULL,0,NULL);
CloseHandle(hThread);
cout<<"main thread is running"<<endl;
Sleep(1);
}
DWORD WINAPI Thread1(LPVOID lpParameter){
cout<<"thread1 is running"<<endl;
return 0;
}
备注:在这里,主函数就是主线程。在主线程中使用Sleep函数,把当前线程挂起,新创建的线程才拥有执行的机会;否则主线程运行完,程序也结束了。
②多线程售票的例子:
#include<windows.h>
#include<iostream.h>
DWORD WINAPI Thread1(LPVOID lpParameter);
DWORD WINAPI Thread2(LPVOID lpParameter);
int tickets=100;
void main(){
HANDLE hThread1=CreateThread(NULL,0,Thread1,NULL,0,NULL);
HANDLE hThread2=CreateThread(NULL,0,Thread2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
cout<<"main thread is running"<<endl;
Sleep(100);
}
DWORD WINAPI Thread1(LPVOID lpParameter){
while(TRUE){
if(tickets>0){
cout<<"thread1 sell tickets"<<tickets--<<endl;
}else{
break;
}
}
return 0;
}
DWORD WINAPI Thread2(LPVOID lpParameter){
while(TRUE){
if(tickets>0){
cout<<"thread2 sell tickets"<<tickets--<<endl;
}else{
break;
}
}
return 0;
}
>>
...
thread2 sell tickets100
thread2 sell tickets99
thread2 sell tickets97
thread2 sell tickets96
thread2 sell tickets95
thread2 sell tickets99
thread1 sell tickets98
thread2 sell tickets94
thread2 sell tickets92
thread2 sell tickets91
thread2 sell tickets90
thread2 sell tickets89
thread1 sell tickets93
thread2 sell tickets88
thread1 sell tickets87
thread2 sell tickets86
thread1 sell tickets85
thread1 sell tickets83
thread1 sell tickets82
thread1 sell tickets81
thread1 sell tickets80
thread1 sell tickets79
thread1 sell tickets78
....
备注:主线程挂起之后,线程1获得时间片,线程1开始运行,结束之后,线程2获得时间片,线程2运行一会,依次类推直到tickets递减为0(单核cpu中)
但是在多cpu的计算机中,线程是同时运行的。从输出结果可以看出,tickets并不是依次递减的。因为如果线程1和线程2是同时运行的,线程1和线程2中,同时获取到变量ticktes=99,就会输出thread1 sell tickets99,和thread1 sell tickets99。但是为什么线程2会输出两次thread2 sell tickets99,我目前还不理解。
③为了解决②中的问题,引入一个概念“线程的同步”
线程同步就是,一个线程对内存进行操作是,其他线程不能对该内存进行操作,直到这个线程完成了操作。在这里当线程1访问tickets变量的时候,应该不允许再让线程2访问tickets变量。直到线程1对于tickets变量完成减一操作之后,才允许线程2去访问它。
为了实现线程同步,可以为线程创建一个互斥对象(mutex),在单个线程读取tickets变量之前请求互斥对象,之后再释放
#include<windows.h>
#include<iostream.h>
DWORD WINAPI Thread1(LPVOID lpParameter);
DWORD WINAPI Thread2(LPVOID lpParameter);
int tickets=100;
HANDLE hMutex;
void main(){
HANDLE hThread1=CreateThread(NULL,0,Thread1,NULL,0,NULL);
HANDLE hThread2=CreateThread(NULL,0,Thread2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
cout<<"main thread is running"<<endl;
hMutex=CreateMutex(NULL,FALSE,NULL);
Sleep(1000);
}
DWORD WINAPI Thread1(LPVOID lpParameter){
while(TRUE){
WaitForSingleObject(hMutex,INFINITE);//请求互斥对象
if(tickets>0){
cout<<"thread1 sell tickets"<<tickets--<<endl;
}else{
break;
}
ReleaseMutex(hMutex);//释放互斥对象
}
return 0;
}
DWORD WINAPI Thread2(LPVOID lpParameter){
while(TRUE){
WaitForSingleObject(hMutex,INFINITE);
if(tickets>0){
cout<<"thread2 sell tickets"<<tickets--<<endl;
}else{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
>>
thread1 sell tickets100
thread2 sell tickets99
thread1 sell tickets98
thread2 sell tickets97
thread1 sell tickets96
thread2 sell tickets95
thread1 sell tickets94
thread2 sell tickets93
thread1 sell tickets92
thread2 sell tickets91
thread1 sell tickets90
这样以来,tickets就是依次递减的了
Python3多线程
Python3中可以使用_thread或threading实现多线程
①先看_thread模块,这个是为了兼容python2的thread模块。可以通过_thread.start_new_thread ( function, args[, kwargs] )创建新线程
import _thread
def thread1( a, b):
print("thread1 is running")
try:
_thread.start_new_thread(thread1,("is a", "is b", ))
except:
print("线程启动失败")
备注:这里发现一个问题,主线程运行完之后,程序并没有退出,而是继续等其他的线程运行完。
另外,创建线程的时候一定要给线程函数传参,哪怕参数是无用的,否则线程就会启动失败;或者传入空参数,如_thread.start_new_thread(thread1,())
②还是售票的那个例子:
import time
import _thread
tickets=50
def thread1( a):
global tickets
while(1):
if tickets>0:
print("thread1 sell tickets :%d in %s\n"%(tickets,time.ctime(time.time()) ))
tickets-=1
else:
break
def thread2(b):
global tickets
while(1):
if tickets>0:
print("thread2 sell tickets :%d in %s\n"%(tickets,time.ctime(time.time()) ))
tickets-=1
else:
break
try:
_thread.start_new_thread(thread1,( "is a", ))
_thread.start_new_thread(thread2,( "is b", ))
except:
print("线程启动失败\n")
>>
thread1 sell tickets :50 in Fri May 8 11:32:04 2020
thread2 sell tickets :50 in Fri May 8 11:32:04 2020
thread1 sell tickets :49 in Fri May 8 11:32:04 2020
thread2 sell tickets :48 in Fri May 8 11:32:04 2020
thread1 sell tickets :47 in Fri May 8 11:32:04 2020
thread2 sell tickets :46 in Fri May 8 11:32:04 2020
备注:线程1和线程2同时售卖了50号票,说明对于这个程序来说,也要实现一下线程同步;
后面增加了打印时间的,这几乎没什么意义,因为cpu运行时间太快,看不出区别;
与c不同的是,不需要把主线程挂起,以使得子线程运行,因为主线程结束之后,要等子线程运行完,程序才会退出(对于这个程序来说,子线程运行完,程序都没有退出。这也许是一个很严重的问题)
③线程同步(添加线程锁)
import time
import _thread
tickets=1000
lock=_thread.allocate()
def thread1( a):
global tickets
while(1):
lock.acquire()
if tickets>0:
print("thread1 sell tickets :%d in %s\n"%(tickets,time.ctime(time.time()) ))
tickets-=1
else:
_thread.exit()
break
lock.release()
#time.sleep(1)
def thread2(b):
global tickets
while(1):
lock.acquire()
if tickets>0:
print("thread2 sell tickets :%d in %s\n"%(tickets,time.ctime(time.time()) ))
tickets-=1
else:
_thread.exit()
break
lock.release()
#time.sleep(1)
try:
_thread.start_new_thread(thread1,( "is a", ))
_thread.start_new_thread(thread2,( "is b", ))
except:
print("线程启动失败\n")
备注:python线程中的锁,专业名词应该是GIL,即全局解释锁。加上锁之后,不会出现同一变量被同时访问的现象。
这里会发现一个问题,当tickets比较小的时候,几乎都是thread1在运行。递减的变量tickets要足够大的时候才能看到thread2也运行了。查了一下,原因是在多核cpu中,线程释放GIL到请求GIL之间几乎没有时间间隙,当在一个核心中的其他线程被唤醒的时候,主线程再一次获得了GIL,被唤醒的线程继续进入等待。据说这个也是python多线程的缺陷。
④使用threading模块,还是售票的列子
import threading
tickets=1000
lock=threading.Lock()
def thread1():
global tickets
while(1):
lock.acquire()
if tickets>0:
print("thread1 sell tickets: %d"%tickets)
tickets-=1
else:
break
lock.release()
def thread2():
global tickets
while(1):
lock.acquire()
if tickets>0:
print("thread2 sell tickets: %d"%tickets)
tickets-=1
else:
break
lock.release()
t1=threading.Thread(target=thread1,args=())
t2=threading.Thread(target=thread2,args=())
t1.start()
t2.start()
备注: 这个几乎跟使用_thread模块是一样的
⑤关于线程阻塞
比如我们要在tickets=0也就是等到两个子线程执行完之后,在主线程打印一条信息“tickets are sold out”。需要使用线程的join方法。之前的代码有一处错误,在while循环的break之前应该进行一次释放锁。
import threading
tickets=1000
lock=threading.Lock()
def thread1():
global tickets
while(1):
lock.acquire()
if tickets>0:
print("thread1 sell tickets: %d"%tickets)
tickets-=1
else:
print("thread1 finish")
lock.release()
break
lock.release()
def thread2():
global tickets
while(1):
lock.acquire()
if tickets>0:
print("thread2 sell tickets: %d"%tickets)
tickets-=1
else:
print("thread2 finish")
lock.release()
break
lock.release()
t1=threading.Thread(target=thread1,args=())
t2=threading.Thread(target=thread2,args=())
t1.start()
t2.start()
t1.join()
t2.join()
print("Tickets are sold out")
备注:join可以接收一个超时参数,表示线程执行多久之后,便结束对子线程的执行,回归到主线程。
⑥线程守护
t1.setDaemon(True)。在主线程结束的时候杀死子线程。
⑦面向对象,创建threading.Thread的子类。为了便于理解,可以查看threading模块里的Thread类。
备注:Thread类的构造函数为__init__(self, group=None, target=None, name=None,args=(), kwargs=None, *, daemon=None)。参数的含义分别是:
group:为未来ThreadGroup类发行的保留字段,应该设为None
target:可以被run()方法调用的对象。
name:线程名,默认用“Thread-N”,N为一个小的十进制数,来表示
args:调用目标函数的参数,是元组类型
kwargs:调用目标函数的关键字参数,是一个字典类型
daemon:应该是线程的守护模式
子类继承Thread,在做任何别的事情之前,需要调用基类的构造函数Thread.__init__()
run()方法表示线程活动。子类可以重载父类的此方法。标准的run()方法会对target参数传递过来的对象进行调用,并且附带上args和kwargs的参数。
start()方法用于开启线程活动。每个线程最多调用一次该函数。
先来个最简单的:
import threading
class SubThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
print_words(self.getName())
#要使用多线程做些啥操作
def print_words(name):
print("%s is running..."%name)
thread1=SubThread()
thread2=SubThread()
thread1.start()
thread2.start()
继续卖票吧:
import threading
tickets=100
class SubThread(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self)
self.name=name
def run(self):
sell_tickets(self.name)
def sell_tickets(name):
global tickets
while 1:
if tickets>0:
print("%s is selling tickets...%d\n"%(name,tickets))
tickets-=1
else:
break;
thread1=SubThread("thread wang")
thread2=SubThread("thread to")
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("tickets are sold out!!!")
备注:锁就不加了
⑧为了验证使用多线程的优势,比较一下单线程和多线程运行的时间