线程,程序执行的最小单元,单线程处理多个任务只能一个处理完后继续处理下一个直到全部处理完,多线程处理任务会比单线程处理起来快吗?在python程序里得看情况,首先有GIL锁的存在导致同一时刻只能有一个线程执行(执行遇到中断释放GIL锁),这乍一看和单线程处理多任务没有区别,但是如果执行的任务是I/O密集型任务就能够提高任务执行效率,但如果任务是CPU密集型任务显然得不到任何效率提升,反而还会因为上下文切换等导致执行不如单线程执行。

Python中实现多线程模块推荐使用threading,threading得到了java线程的启示。Queue模块对于线程同步是十分有用的,该模块提供了一个同步FIFO队列类型,这个类型非常便于处理线程之间的通信和协调。

threading模块

__all__ = ['get_ident', 'active_count', 'Condition', 'current_thread',
'enumerate', 'main_thread', 'TIMEOUT_MAX',
'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError',
'setprofile', 'settrace', 'local', 'stack_size']
threading模块提供对象 作用
current_thread/currentThread 为调用线程返回一个Thread对象。如果调用线程不是由threading模块创建的将创建并返回一个具有有限功能的Thread对象
Thread 表示控制线程的类,创建一个线程可以直接实例化一个Thread对象,直接实例化时需要指定参数target,t对象调用run方法即执行target(*args, **kwargs); 还可以同个继承Thread类创建一个线程,这样创建需要覆盖run方法而不需要传参target。
getName,setName t.getName(), t.setName(name)
getName返回t的名称,setName设置t的名称。线程名可以是任意字符串并且不要求唯一。
isAlive t.isAlive()
如果t是活动的,将返回True(t.start()已经执行,t.run()还没有结束)。否则返False
isDaemon, setDaemon t.isDaemon() t.setDaemon(daemonic)
如果t是一个驻留程序(即使t仍然是活动的,Python可以终止整个处理过程,这样可以结束线程t),isDaemon将返回True,否则返回False。最开始,当且仅当创建t的线程是一个驻留程序时,t才是一个驻留程序。只能在t.start()之前调用t.setDaemon(True)把t设置为一个驻留程序。通俗地讲正常情况下如果主线程执行内容中有两个子线程任务要执行,如果给子线程设置setDaemon(True),那么当主线程任务结束时两个子线程还是活动的时候主线程不会等待子线程执行完成。默认情况下都是False
join t.join(timeout=None)
创建这个线程的线程将会挂起直到t结束。只能在t.start()之后调用t.join()
run t.run()
run是执行代码(任务)的方法,不过统一使用t.start(), 让t..start()去调用执行run
start t.start()
start可以让t活动,并让run在一个单独的线程中执行,一个线程对象只能调用一次run方法

代码demo

import threading
import time def cal(n):
print(">>>>>>>>%s" % n)
time.sleep(n)
temp = n * n * n
print("result: {}".format(temp))
with open('{}.txt'.format(n), 'w') as f:
f.write('{}abcde'.format(n))
print("total_thread_count is: {}".format(threading.active_count())) if __name__ == '__main__':
t1 = threading.Thread(target=cal, args=(2,))
t2 = threading.Thread(target=cal, args=(3,))
t = threading.current_thread()
print(t.isDaemon()) # False
t2.start()
t1.start()
print("main thread ending") # 执行结果
"""
False
>>>>>>>>3
>>>>>>>>2
main thread ending
result: 8
total_thread_count is: 3
result: 27
total_thread_count is: 2
"""
# 同目录下生成2.txt和3.txt文件

给两个子线程setDaemon(True)执行实例

import threading
import time def cal(n):
print(">>>>>>>>%s" % n)
time.sleep(n)
temp = n * n * n
print("result: {}".format(temp))
with open('{}.txt'.format(n), 'w') as f:
f.write('{}abcde'.format(n))
print("total_thread_count is: {}".format(threading.active_count())) if __name__ == '__main__':
t1 = threading.Thread(target=cal, args=(2,))
t2 = threading.Thread(target=cal, args=(3,))
t = threading.current_thread()
t1.setDaemon(True)
t2.setDaemon(True)
print(t.isDaemon()) # False
t2.start()
t1.start()
print("main thread ending") # 执行结果
'''
False
>>>>>>>>3
>>>>>>>>2
main thread ending
'''
# 并不会生成2.txt和3.txt文件

使用类继承创建Thread对象

import threading
import time class CalTask(threading.Thread):
def __init__(self, num):
super(CalTask, self).__init__()
self.num = num def run(self):
n = self.num
print(">>>>>>>>%s" % n)
time.sleep(n)
temp = n * n * n
print("result: {}".format(temp))
with open('{}.txt'.format(n), 'w') as f:
f.write('{}abcde'.format(n))
print("total_thread_count is: {}".format(threading.active_count())) if __name__ == '__main__':
t1 = CalTask(num=2)
t2 = CalTask(num=3)
# t = threading.current_thread()
t = threading.currentThread()
# t1.setDaemon(True)
t2.setDaemon(True)
print(t.isDaemon()) # False
t2.start()
t1.start()
print("main thread ending")

Lock对象和RLock对象

Lock(互斥锁)是在多线程编程中线程同步控制的一种方法。在编程中遇到多个线程都修改同一个共享数据的时候就需要考虑线程同步控制。

线程同步能够保证多个线程安全访问竞争资源。互斥锁为资源引入两个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

代码实例1

import threading
import time total = 100 class CalTask(threading.Thread):
def __init__(self):
super(CalTask, self).__init__() def run(self):
global total
print(">>>>>>: ", total)
time.sleep(0.0001)
total -= 1 if __name__ == '__main__':
for i in range(100):
t = CalTask()
t.start()
print(threading.active_count())
while True:
if threading.active_count() == 1:
break
print(total) '''
执行结果:
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 99
>>>>>>: 99
>>>>>>: 99
>>>>>>: 99
>>>>>>: 95
>>>>>>: 95
... 9
0
'''
# 最终结果是total为0,每次直接修改全局变量total本身和Python存在的GIL锁的原因导致结果最终为0

代码实例2

import threading
import time total = 100 class CalTask(threading.Thread):
def __init__(self):
super(CalTask, self).__init__() def run(self):
global total
print(">>>>>>: ", total)
temp = total
time.sleep(0.0001)
total = temp - 1 if __name__ == '__main__':
for i in range(100):
t = CalTask()
t.start()
print(threading.active_count())
while True:
if threading.active_count() == 1:
break
print(total) '''
执行结果:
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 100
>>>>>>: 99
>>>>>>: 99
>>>>>>: 99
>>>>>>: 99
>>>>>>: 98
>>>>>>: 98
... 7
89
''' # 最终结果是total为89,这和代码实例一对比结果不同的原因是因为虽然有GIL锁的存在,但现在是通过一个中间变量来操作然后重新赋值给total,这样会造成数据不安全

代码实例3(代码2加互斥锁后)

import threading
import time total = 100
lock = threading.Lock() class CalTask(threading.Thread):
def __init__(self):
super(CalTask, self).__init__() def run(self):
global total
lock.acquire()
print(">>>>>>: ", total)
temp = total
time.sleep(0.0001)
total = temp - 1
lock.release() if __name__ == '__main__':
for i in range(100):
t = CalTask()
t.start()
print(threading.active_count())
while True:
if threading.active_count() == 1:
break
print(total) '''
执行结果:
>>>>>>: 100
>>>>>>: 99
>>>>>>: 98
>>>>>>: 97
>>>>>>: 96
>>>>>>: 95
>>>>>>: 94
>>>>>>: 93
>>>>>>: 92
>>>>>>: 91
>>>>>>: 90
>>>>>>: 89
90
>>>>>>: 88
>>>>>>: 87
... 0
'''

代码实例4(代码2线程+join)

import threading
import time total = 100
lock = threading.Lock() class CalTask(threading.Thread):
def __init__(self):
super(CalTask, self).__init__() def run(self):
global total
print(">>>>>>: ", total)
temp = total
time.sleep(0.0001)
total = temp - 1 if __name__ == '__main__':
for i in range(100):
t = CalTask()
t.start()
t.join()
print(threading.active_count())
while True:
if threading.active_count() == 1:
break
print(total) '''
执行结果:
>>>>>>: 100
>>>>>>: 99
>>>>>>: 98
>>>>>>: 97
>>>>>>: 96
>>>>>>: 95
>>>>>>: 94
>>>>>>: 93
>>>>>>: 92
>>>>>>: 91
>>>>>>: 90
>>>>>>: 89
>>>>>>: 88
>>>>>>: 87
...
1
0
''' # 串行执行,执行效率不然代码3(加锁)

join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分‘串行’,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

RLock递归锁

死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

死锁示例

from threading import Thread, Lock
import time
mutexA = Lock()
mutexB = Lock() class MyThread(Thread):
def run(self):
self.func1()
self.func2() def func1(self):
mutexA.acquire()
print('33[41m%s 拿到A锁33[0m' % self.name) mutexB.acquire()
print('33[42m%s 拿到B锁33[0m' % self.name)
mutexB.release() mutexA.release() def func2(self):
mutexB.acquire()
print('33[43m%s 拿到B锁33[0m' % self.name)
time.sleep(2) mutexA.acquire()
print('33[44m%s 拿到A锁33[0m' % self.name)
mutexA.release() mutexB.release() if __name__ == '__main__':
for i in range(10):
t = MyThread()
t.start() '''
33[41mThread-1 拿到A锁33[0m
33[42mThread-1 拿到B锁33[0m
33[43mThread-1 拿到B锁33[0m
33[41mThread-2 拿到A锁33[0m
死锁
'''

RLock替代Lock

from threading import Thread, RLock
import time
mutexA = RLock() class MyThread(Thread):
def run(self):
self.func1()
self.func2() def func1(self):
mutexA.acquire()
print('33[41m%s 拿到A锁33[0m' % self.name) mutexA.acquire()
print('33[42m%s 拿到B锁33[0m' % self.name)
mutexA.release() mutexA.release() def func2(self):
mutexA.acquire()
print('33[43m%s 拿到B锁33[0m' % self.name)
time.sleep(2) mutexA.acquire()
print('33[44m%s 拿到A锁33[0m' % self.name)
mutexA.release() mutexA.release() if __name__ == '__main__':
for i in range(10):
t = MyThread()
t.start()

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

Condition对象

Condition对象提供了对复杂线程同步问题的支持。

Condition class Condition(lock=None)
Condition可以创建并返回一个新Condition对象,并将对象L设置为lock,如果lock=None,L将被设置为一个新创建的RLock对象
acquire,release c.acquire(wait=1) c.release()
这些方法将调用L的对应方法,除非一个线程拥有锁L,否则该线程不能对c调用任何其他方法。
notify,notifyAll c.notify() c.notifyAll()
notify可以唤醒正在等待c的线程中的某一个线程。调用线程在调用c.notify()之前必须拥有L,并且notify不会释放L。除非被唤醒的线程可以再次得到L,否则该线程不会变为就绪状态。因此,调用线程通常会在调用notify之后调用release。notifyAll类似于notify,区别在于notifyAll将唤醒所有正在等待的线程而不只是其中的一个
wait c.wait(timeout=None)
wait将释放L, 然后挂起调用线程,直到其它线程对c调用notify或notifyAll。调用线程在调用c.wait()之前必须拥有L。

condition示例

import threading
import time class Seeker(threading.Thread):
def __init__(self, cond, name):
super(Seeker, self).__init__()
self.cond = cond
self.name = name def run(self):
time.sleep(1) # 确保先运行Hider中的方法
self.cond.acquire() # 2
print(self.name + ': 我已经把眼睛蒙上了')
self.cond.notify()
print(self.name + ": 速度啊大兄弟")
self.cond.wait() # 3
# 6
print(self.name + ': 我找到你了 大兄弟')
self.cond.notify()
self.cond.release()
# 7
print(self.name + ': 我赢了') # 8 class Hider(threading.Thread):
def __init__(self, cond, name):
super(Hider, self).__init__()
self.cond = cond
self.name = name def run(self):
self.cond.acquire()
self.cond.wait() # 1 #释放对琐的占用,同时线程挂起在这里,直到被notify并重新占有琐。
# 4
print(self.name + ': 我已经藏好了,你快来找我吧')
self.cond.notify()
self.cond.wait() # 5
# 8
self.cond.release()
print(self.name + ': 被你找到了,大爷的') cond = threading.Condition()
seeker = Seeker(cond, 'seeker')
hider = Hider(cond, 'hider')
seeker.start()
hider.start() '''
执行结果:
seeker: 我已经把眼睛蒙上了
seeker: 速度啊大兄弟
hider: 我已经藏好了,你快来找我吧
seeker: 我找到你了 大兄弟
seeker: 我赢了
hider: 被你找到了,大爷的
'''

Event对象

Event对象可以让任意数量的线程挂起并等待。等待事件对象e的所有线程在任何其他线程调用e.set()时将变为就绪状态。事件对象e有一个标记,可以记录该事件是否已经发送;在e被创建时,这个标记的初始值为False。Event就是这样一个类似于简化的Condition。

方法 作用
Event Event可以创建并返回一个新事件对象e,并且e的标记被设置为False
clear e.clear() 将e的标记设置为False
isSet e.isSet() 返回e的标记的值,True或False
set e.set() 将e的标记设置为True。所有等待e的线程将变为就绪
wait e.wait(timeout=None)
如果e的标记为True,wait将立即返回。否则,wait将挂起调用线程,直到其他一些线程调用

Event代码示例

import threading
import time
import logging logging.basicConfig(
level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
) def worker(event):
logging.debug('Waiting for redis ready...')
event.wait()
logging.debug(
'redis ready, and connect to redis server and do some work [%s]',
time.ctime())
time.sleep(1) def main():
readis_ready = threading.Event()
t1 = threading.Thread(target=worker, args=(readis_ready,), name='t1')
t1.start() t2 = threading.Thread(target=worker, args=(readis_ready,), name='t2')
t2.start() logging.debug(
'first of all, check redis server, make sure it is OK, and then trigger the redis ready event')
time.sleep(3) # simulate the check progress
readis_ready.set() if __name__ == "__main__":
main() '''
执行结果:
(t1 ) Waiting for redis ready...
(t2 ) Waiting for redis ready...
(MainThread) first of all, check redis server, make sure it is OK, and then trigger the redis ready event
(t1 ) redis ready, and connect to redis server and do some work [Thu Feb 14 14:58:53 2019]
(t2 ) redis ready, and connect to redis server and do some work [Thu Feb 14 14:58:53 2019]
'''

Semaphore对象

信号量(也被称为计数信号量,counting semaphore)是广义上的锁。Lock的状态可以被看作是True或False,信号量对象s的状态是一个0~n的数字,n是在s被创建设置的。信号量可以用于管理固定的资源池,相比信号量使用队列来实现这个功能更健壮一些。

方法 作用
Semaphore class Semaphore(n=1)
Semaphore可以创建并返回一个状态被设置为n的信号量对象s
acquire s.acquire(wait=True)
在s的状态大于0时,acquire将把状态值减1并返回True,在s的状态为0并且wait为True时,acquire将挂起调用线程并等待,直到其他一些线程调用了s.release。在s的状态为0,并且wait为False时,acquire将立即返回False
release s.release()
在s的状态大于0,或者在状态为0但是没有线程正在等待s时,release将把状态值增加1,在s的状态为0并且有线程正在等待s时,release将把s的状态设为0,并唤醒任意一个等待线程。调用release的线程将不再被挂起,该线程将保持就绪,并继续正常执行

Semphore代码示例

import threading
import time semaphore = threading.Semaphore(5) # 设置同时可以有5个线程可以获得信号量锁 def func():
if semaphore.acquire():
print(threading.currentThread().getName() + ' get semaphore')
time.sleep(2)
semaphore.release() for i in range(20):
t1 = threading.Thread(target=func)
t1.start() # 执行结果:每两秒并发执行5个线程任务,即每2秒打印5次

线程本地存储

threading模块提供了一个local类,线程可以使用这个类获得线程本地存储,也被称为每线程数据。线程本地存储对象可以set设置和get获取属性,可以在__dict__中获取到,这个本地存储对象是线程安全的,多个线程同时设置和获得对象的属性是不会有问题的。

示例代码:

import threading
L = threading.local()
print("in main thread, setting zop to 42")
L.zop = 42 def targ():
print("in subthread, setting zop to 23")
L.zop = 23
print("in subthread, setting zop is now ", L.zop) if __name__ == '__main__':
t = threading.Thread(target=targ)
t.start()
t.join()
print("in main thread, setting zop is now ", L.zop) # 执行结果
"""
in main thread, setting zop to 42
in subthread, setting zop to 23
in subthread, setting zop is now 23
in main thread, setting zop is now 42
"""

线程程序架构

只要线程程序必须处理某些外部对象,可以专门指定一个使用Queue对象的线程来实现这样的处理。通过这个Queue对象,外部接口线程可以通过这个Queue对象获得其他线程放入的工作请求。外部接口线程可以将结果放入到一个或多个其他Queue对象来返回这些结果。下面示例展示了如果将这种架构包装到一个通用的可重用类中:

线程程序简易架构示例1

import threading

try:
import Queue # Python 2
except ImportError:
import queue as Queue # Python 3 class ExternalInterfacing(threading.Thread):
def __init__(self, **kwargs):
super(ExternalInterfacing, self).__init__()
self.setDaemon(True)
self.workRequestQueue = Queue.Queue()
self.resultQueue = Queue.Queue()
self.start() def apply(self, externalCallable, *args, **kwargs):
"called by other threads as externalCallable would be"
self.workRequestQueue.put((externalCallable, args, kwargs))
return self.resultQueue.get() def run(self):
while 1:
externalCallable, args, kwargs = self.workRequestQueue.get()
self.resultQueue.put(externalCallable(args, kwargs))

搜了下资料发现threadpool的源码实现的基础架构就是这样的,threadpool源码有四百多行就不在这里分析了,写到这里了threadpool源码分析

Python线程模块threading的更多相关文章

  1. Python标准模块--threading

    1 模块简介 threading模块在Python1.5.2中首次引入,是低级thread模块的一个增强版.threading模块让线程使用起来更加容易,允许程序同一时间运行多个操作. 不过请注意,P ...

  2. python 线程模块

    Python通过两个标准库thread和threading提供对线程的支持.thread提供了低级别的.原始的线程以及一个简单的锁. threading 模块提供的其他方法: threading.cu ...

  3. {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器

    Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...

  4. 一句话介绍python线程、进程和协程

    一.进程: Python的os模块封装了常见的系统调用,其中就包括fork.而fork是linux常用的产生子进程的方法,简言之是一个调用,两个返回. 在python中,以下的两个模块用于进程的使用. ...

  5. Python 浅析线程(threading模块)和进程(process)

    线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 进程与线程 什么 ...

  6. <python的线程与threading模块>

    <python的线程与threading模块> 一 线程的两种调用方式 threading 模块建立在thread 模块之上.thread模块以低级.原始的方式来处理和控制线程,而thre ...

  7. python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型

    线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...

  8. python全栈开发 * 进程池,线程理论 ,threading模块 * 180727

    一.进程池 (同步 异步 返回值) 缺点: 开启进程慢 几个CPU就能同时运行几个程序 进程的个数不是无线开启的 应用: 100个任务 进程池 如果必须用多个进程 且是高计算型 没有IO型的程序 希望 ...

  9. Python的并发并行[1] -> 线程[0] -> threading 模块

    threading模块 / threading Module 1 常量 / Constants Pass 2 函数 / Function 2.1 setprofile()函数 函数调用: thread ...

随机推荐

  1. NodeJS笔记(六)-Express HTTP服务器启动后如何关闭

    npm start启动网站,提示“3000”端口已经被使用的问题 nodejs WEB服务器并不随着cmd的关闭而终止 查看任务管理器可以看到nodejs的启动进程 可以手动关闭 如果是一直处于cmd ...

  2. Gitbook在Windows上安装

    GitBook是基于Nodejs,使用Git/Github和Markdown制作电子书的命令行工具. 1.安装Nodejs 首先,安装Nodejs,官网地址:https://nodejs.org/en ...

  3. HDU 2544最短路 【dijkstra 链式前向星+优先队列优化】

    最开始学最短路的时候只会用map二维数组存图,那个时候还不知道这就是矩阵存图,也不懂得效率怎么样 经过几个月的历练再回头看最短路的题, 发现图可以用链式前向星来存, 链式前向星的效率是比较高的.对于查 ...

  4. SpringMVC中映射路径的用法之请求限制、命名空间

    SpringMVC中映射路径的请求限制 什么是SpringMVC请求限制? 在SpringMVC中,支持对请求的设置.如果不满足限制条件的话,就不让请求访问执行方法,这样可以大大提高执行方法 的安全性 ...

  5. 002-zookeeper 基本配置、安装启动 windows环境

    一. 概述 ZooKeeper是Hadoop的正式子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护.名字服务.分布式同步.组服务等.ZooKeeper的目标就是封装好复杂易出 ...

  6. tensorflow-用DASC结合Inception-v3对imagenet2012聚类实现

    一.目的 以imagenet2012作为数据集,用Inception-v3对图像提取特征作为输入,来训练一个自编码器. 以上作为预训练模型,随后在该自编码器的基础上,中间加入一个自表示层,将最终学习到 ...

  7. 2018-2019-2 网络对抗技术 20165236 Exp2 后门原理与实践

    2018-2019-2 网络对抗技术 20165236 Exp2 后门原理与实践 一.实验内容 (3.5分) (1)使用netcat获取主机操作Shell,cron启动 (0.5分) (2)使用soc ...

  8. minikube windows hyperx填坑记

    minikube windows hyperx填坑记 安装了一天半,还是没行,先放弃 开始 minikube start --vm-driver=hyperv --hyperv-virtual-swi ...

  9. 【Idea】Intellij Idea debug 模式如果发现异常,即添加异常断点在发生异常处

    前用eclipse的时候,可以根据所抛出的异常进行调试,比如:出现了空指针异常,我想知道是哪一行抛出的,在eclipse中我只需在debug模式下把空指针异常这个名字设置进去,当遇到空指针异常时,ec ...

  10. MySQL根据出生日期计算年龄

    以前使用mysql不是很多,对mysql的函数也不是很熟悉,遇到这个问题第一时间百度搜索,搜索到这两种方法,这两种方法是排在百度第一条的博客. 方法一 SELECT DATE_FORMAT(FROM_ ...