Python多線程編程

1、線程和進(jìn)程

計(jì)算機(jī)的核心是CPU,它承擔(dān)了所有的計(jì)算任務(wù)。它就像一座工廠,時(shí)刻在運(yùn)行。

假定工廠的電力有限,一次只能供給一個(gè)車間使用。也就是說,一個(gè)車間開工的時(shí)候,其他車間都必須停工。背后的含義就是,單個(gè)CPU一次只能運(yùn)行一個(gè)任務(wù)。

進(jìn)程就好比工廠的車間,它代表CPU所能處理的單個(gè)任務(wù)。任一時(shí)刻,CPU總是運(yùn)行一個(gè)進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)。

一個(gè)車間里,可以有很多工人。他們協(xié)同完成一個(gè)任務(wù)。

線程就好比車間里的工人。一個(gè)進(jìn)程可以包括多個(gè)線程。

車間的空間是工人們共享的,比如許多房間是每個(gè)工人都可以進(jìn)出的。這象征一個(gè)進(jìn)程的內(nèi)存空間是共享的,每個(gè)線程都可以使用這些共享內(nèi)存。

可是,每間房間的大小不同,有些房間最多只能容納一個(gè)人,比如廁所。里面有人的時(shí)候,其他人就不能進(jìn)去了。這代表一個(gè)線程使用某些共享內(nèi)存時(shí),其他線程必須等它結(jié)束,才能使用這一塊內(nèi)存。

一個(gè)防止他人進(jìn)入的簡(jiǎn)單方法,就是門口加一把鎖。先到的人鎖上門,后到的人看到上鎖,就在門口排隊(duì),等鎖打開再進(jìn)去。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex),防止多個(gè)線程同時(shí)讀寫某一塊內(nèi)存區(qū)域。

還有些房間,可以同時(shí)容納n個(gè)人,比如廚房。也就是說,如果人數(shù)大于n,多出來的人只能在外面等著。這好比某些內(nèi)存區(qū)域,只能供給固定數(shù)目的線程使用。

這時(shí)的解決方法,就是在門口掛n把鑰匙。進(jìn)去的人就取一把鑰匙,出來時(shí)再把鑰匙掛回原處。后到的人發(fā)現(xiàn)鑰匙架空了,就知道必須在門口排隊(duì)等著了。這種做法叫做"信號(hào)量"(Semaphore),用來保證多個(gè)線程不會(huì)互相沖突。

不難看出,mutex是semaphore的一種特殊情況(n=1時(shí))。也就是說,完全可以用后者替代前者。但是,因?yàn)閙utex較為簡(jiǎn)單,且效率高,所以在必須保證資源獨(dú)占的情況下,還是采用這種設(shè)計(jì)。

2、多線程與多進(jìn)程

從上面關(guān)于線程和進(jìn)程的的通俗解釋來看,多線程和多進(jìn)程的含義如下:
多進(jìn)程:允許多個(gè)任務(wù)同時(shí)進(jìn)行
多線程:允許單個(gè)任務(wù)分成不同的部分運(yùn)行

3、Python多線程編程

3.1 單線程

在好些年前的MS-DOS時(shí)代,操作系統(tǒng)處理問題都是單任務(wù)的,我想做聽音樂和看電影兩件事兒,那么一定要先排一下順序。

# coding=UTF-8
from time import ctime,sleep


def music():
    for i in range(2):
        print 'I was listening to music. %s' %ctime()
        sleep(1)

def movie():
    for i in range(2):
        print 'I was at the movies! %s' %ctime()
        sleep(5)

if __name__ == '__main__':
    music()
    movie()
    print 'all over %s' %ctime()

我們先聽了一首音樂,通過for循環(huán)來控制音樂的播放了兩次,每首音樂播放需要1秒鐘,sleep()來控制音樂播放的時(shí)長(zhǎng)。接著我們又看了一場(chǎng)電影,每一場(chǎng)電影需要5秒鐘,因?yàn)樘每戳耍晕乙餐ㄟ^for循環(huán)看兩遍。在整個(gè)休閑娛樂活動(dòng)結(jié)束后,我通過print "all over %s" %ctime() 看了一下當(dāng)前時(shí)間,差不多該睡覺了。
運(yùn)行結(jié)果:

I was listening to music. Mon Aug 21 15:32:18 2017
I was listening to music. Mon Aug 21 15:32:19 2017
I was at the movies! Mon Aug 21 15:32:20 2017
I was at the movies! Mon Aug 21 15:32:25 2017
all over Mon Aug 21 15:32:30 2017

其實(shí),music()和movie()更應(yīng)該被看作是音樂和視頻播放器,至于要播放什么歌曲和視頻應(yīng)該由我們使用時(shí)決定。所以,我們對(duì)上面代碼做了改造:

# coding=UTF-8

import threading
from time import ctime,sleep

def music(func):
    for i in range(2):
        print ("I was listening to %s. %s" %(func,ctime()))
        sleep(1)

def movie(func):
    for i in range(2):
        print ("I was at the %s ! %s" %(func,ctime()))
        sleep(5)

if __name__ == '__main__':
    music(u'愛情買賣')
    movie(u'阿凡達(dá)')
    print("all over %s" %ctime())

運(yùn)行結(jié)果:

I was listening to 愛情買賣. Mon Aug 21 15:38:55 2017
I was listening to 愛情買賣. Mon Aug 21 15:38:56 2017
I was at the 阿凡達(dá) ! Mon Aug 21 15:38:57 2017
I was at the 阿凡達(dá) ! Mon Aug 21 15:39:02 2017
all over Mon Aug 21 15:39:07 2017

3.2 多線程

Python3 通過兩個(gè)標(biāo)準(zhǔn)庫(kù) _thread (python2中是thread模塊)和 threading 提供對(duì)線程的支持。
_thread 提供了低級(jí)別的、原始的線程以及一個(gè)簡(jiǎn)單的鎖,它相比于 threading 模塊的功能還是比較有限的。

3.2.1使用_thread模塊

調(diào)用_thread模塊中的start_new_thread()函數(shù)來產(chǎn)生新線程。
先用一個(gè)實(shí)例感受一下:

# coding=UTF-8

import thread
import time


# 為線程定義一個(gè)函數(shù)
def print_time(threadName, delay):
    count = 0
    while count < 5:
        time.sleep(delay)
        count += 1
        print("%s: %s" % (threadName, time.ctime(time.time())))


# 創(chuàng)建兩個(gè)線程
try:
    thread.start_new_thread(print_time, ("Thread-1", 2,))
    thread.start_new_thread(print_time, ("Thread-2", 4,))
except:
    print ("Error: unable to start thread")

while 1:
    pass

print ("Main Finished")

代碼輸出為:

Thread-1: Mon Aug 21 15:49:00 2017
Thread-2: Mon Aug 21 15:49:02 2017
Thread-1: Mon Aug 21 15:49:02 2017
Thread-1: Mon Aug 21 15:49:04 2017
Thread-2: Mon Aug 21 15:49:06 2017
Thread-1: Mon Aug 21 15:49:06 2017
Thread-1: Mon Aug 21 15:49:08 2017
Thread-2: Mon Aug 21 15:49:10 2017
Thread-2: Mon Aug 21 15:49:14 2017
Thread-2: Mon Aug 21 15:49:18 2017

注意到,在主線程寫了:

while 1:
   pass

這是讓主線程一直在等待.
如果去掉上面兩行,那就直接輸出并結(jié)束程序執(zhí)行:

"Main Finished"
3.2.2使用threading模塊

threading 模塊除了包含 thread 模塊中的所有方法外,還提供的其他方法:
threading.currentThread(): 返回當(dāng)前的線程變量。
threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list。正在運(yùn)行指線程啟動(dòng)后、結(jié)束前,不包括啟動(dòng)前和終止后的線程。
threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果。
除了使用方法外,線程模塊同樣提供了Thread類來處理線程,Thread類提供了以下方法:
run(): 用以表示線程活動(dòng)的方法。
start():啟動(dòng)線程活動(dòng)。
join([time]): 等待至線程中止。這阻塞調(diào)用線程直至線程的join() 方法被調(diào)用中止-正常退出或者拋出未處理的異常-或者是可選的超時(shí)發(fā)生。
isAlive(): 返回線程是否活動(dòng)的。
getName(): 返回線程名。
setName(): 設(shè)置線程名。

直接創(chuàng)建線程

接上面的聽音樂和看電影的例子,我們可以直接使用threading.Thread 創(chuàng)建線程,并指定執(zhí)行的方法以及傳遞的參數(shù):

# coding=UTF-8

import threading
from time import ctime, sleep

def music(func):
    for i in range(2):
        print ("I was listening to %s. %s" %(func,ctime()))
        sleep(1)

def movie(func):
    for i in range(2):
        print ("I was at the %s! %s" %(func,ctime()))
        sleep(5)

threads = []
t1 = threading.Thread(target=music,args=(u'愛情買賣',))
threads.append(t1)
t2 = threading.Thread(target=movie,args=(u'阿凡達(dá)',))
threads.append(t2)

if __name__ == '__main__':
    for t in threads:
        t.start()

    print ("all over %s" % ctime())

結(jié)果輸出為:

I was listening to 愛情買賣. Tue Aug 22 10:41:33 2017
 all over Tue Aug 22 10:41:33 2017
I was at the 阿凡達(dá)! Tue Aug 22 10:41:33 2017
I was listening to 愛情買賣. Tue Aug 22 10:41:34 2017
I was at the 阿凡達(dá)! Tue Aug 22 10:41:38 2017

構(gòu)造線程類

我們也可以通過直接從 threading.Thread 繼承創(chuàng)建一個(gè)新的子類,并實(shí)例化后調(diào)用 start() 方法啟動(dòng)新線程,即它調(diào)用了線程的 run() 方法:

# coding=UTF-8

import threading
import time

exitFlag = 0


class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print ("開始線程:" + self.name)
        print_time(self.name, self.counter, 5)
        print ("結(jié)束線程:" + self.name)


def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 開始新線程
thread1.start()
thread2.start()
print ("退出主線程")

結(jié)果輸出為:

開始線程:Thread-1
開始線程:Thread-2
退出主線程
Thread-1: Tue Aug 22 10:52:55 2017
Thread-2: Tue Aug 22 10:52:56 2017
Thread-1: Tue Aug 22 10:52:56 2017
Thread-1: Tue Aug 22 10:52:57 2017
Thread-2: Tue Aug 22 10:52:58 2017
Thread-1: Tue Aug 22 10:52:58 2017
Thread-1: Tue Aug 22 10:52:59 2017
結(jié)束線程:Thread-1
Thread-2: Tue Aug 22 10:53:00 2017
Thread-2: Tue Aug 22 10:53:02 2017
Thread-2: Tue Aug 22 10:53:04 2017
結(jié)束線程:Thread-2

從結(jié)果可以看到,為什么我們開啟了兩個(gè)線程之后,主線程立即退出了?因?yàn)槲覀儧]有使用join方法,對(duì)于主線程來說,thread1和thread2是子線程,使用join方法,會(huì)讓主線程等待子線程執(zhí)行解說再繼續(xù)執(zhí)行。

join()方法

我們修改一下代碼:

# coding=UTF-8

import threading
import time

exitFlag = 0


class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print ("開始線程:" + self.name)
        print_time(self.name, self.counter, 5)
        print ("退出線程:" + self.name)


def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 開啟新線程
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print("退出主線程")

結(jié)果就變?yōu)椋?/p>

開始線程:Thread-1
開始線程:Thread-2
Thread-1: Tue Aug 22 11:45:53 2017
Thread-2: Tue Aug 22 11:45:54 2017
Thread-1: Tue Aug 22 11:45:54 2017
Thread-1: Tue Aug 22 11:45:55 2017
Thread-2: Tue Aug 22 11:45:56 2017
Thread-1: Tue Aug 22 11:45:56 2017
Thread-1: Tue Aug 22 11:45:57 2017
退出線程:Thread-1
Thread-2: Tue Aug 22 11:45:58 2017
Thread-2: Tue Aug 22 11:46:00 2017
Thread-2: Tue Aug 22 11:46:02 2017
退出線程:Thread-2
退出主線程

可以看到 退出主線程 在最后才被打印出來。

setDaemon()方法

有一個(gè)方法常常拿來與join方法做比較,那就是setDaemon()方法。我們首先來看一下setDaemon()方法的使用效果:

# coding=UTF-8

import threading
import time

exitFlag = 0

class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print ("開始線程:" + self.name)
        print_time(self.name, self.counter, 5)
        print ("退出線程:" + self.name)


def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 開啟新線程
thread1.setDaemon(True)
thread2.setDaemon(True)
thread1.start()
thread2.start()

print ("退出主線程")

結(jié)果輸出為:

開始線程:Thread-1
開始線程:Thread-2
退出主線程

可以看到,在主線程結(jié)束之后,程序就終止了,也就是說兩個(gè)子線程也被終止了,這就是setDaemon方法的作用。主線程A中,創(chuàng)建了子線程B,并且在主線程A中調(diào)用了B.setDaemon(),這個(gè)的意思是,把主線程A設(shè)置為守護(hù)線程,這時(shí)候,要是主線程A執(zhí)行結(jié)束了,就不管子線程B是否完成,一并和主線程A退出.這就是setDaemon方法的含義,這基本和join是相反的。此外,還有個(gè)要特別注意的:必須在start() 方法調(diào)用之前設(shè)置,如果不設(shè)置為守護(hù)線程,程序會(huì)被無限掛起。

兩個(gè)疑問

我們剛才介紹了兩種使用多線程的方式,一種是直接調(diào)用threading.Thread 創(chuàng)建線程,另一種是從 threading.Thread 繼承創(chuàng)建一個(gè)新的子類,并實(shí)例化后調(diào)用 start() 方法啟動(dòng)進(jìn)程。學(xué)到這里,我就拋出了兩個(gè)疑問,為什么第一種方法中我們可以為不同的線程指定運(yùn)行的方法,而第二種我們都運(yùn)行的是同一個(gè)方法,那么它內(nèi)部的實(shí)現(xiàn)機(jī)制是什么呢?第二個(gè)疑問是,第二種方法中,我們沒有實(shí)例化start()方法,那么run和start這兩個(gè)方法的聯(lián)系是什么呢?
首先,start方法和run方法的關(guān)系如下:用start方法來啟動(dòng)線程,真正實(shí)現(xiàn)了多線程運(yùn)行,這時(shí)無需等待run方法體代碼執(zhí)行完畢而直接繼續(xù)執(zhí)行下面的代碼。通過調(diào)用Thread類的start()方法來啟動(dòng)一個(gè)線程,這時(shí)此線程處于就緒(可運(yùn)行)狀態(tài),并沒有運(yùn)行,一旦得到cpu時(shí)間片,就開始執(zhí)行run()方法,這里方法 run()稱為線程體,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容,Run方法運(yùn)行結(jié)束,此線程隨即終止。

而run()方法的源碼如下,可以看到,如果我們指定了target即線程執(zhí)行的函數(shù)的話,run方法可以轉(zhuǎn)而調(diào)用那個(gè)函數(shù),如果沒有的話,將不執(zhí)行,而我們?cè)谧远x的Thread類里面重寫了這個(gè)run 方法,所以程序會(huì)執(zhí)行這一段。

def run(self):
"""Method representing the thread's activity.

You may override this method in a subclass. The standard run() method
invokes the callable object passed to the object's constructor as the
target argument, if any, with sequential and keyword arguments taken
from the args and kwargs arguments, respectively.

"""
try:
    if self._target:
        self._target(*self._args, **self._kwargs)
finally:
    # Avoid a refcycle if the thread is running a function with
    # an argument that has a member that points to the thread.
    del self._target, self._args, self._kwargs

線程同步

如果多個(gè)線程共同對(duì)某個(gè)數(shù)據(jù)修改,則可能出現(xiàn)不可預(yù)料的結(jié)果,為了保證數(shù)據(jù)的正確性,需要對(duì)多個(gè)線程進(jìn)行同步。
使用 Thread 對(duì)象的 Lock 和 Rlock 可以實(shí)現(xiàn)簡(jiǎn)單的線程同步,這兩個(gè)對(duì)象都有 acquire 方法和 release 方法,對(duì)于那些需要每次只允許一個(gè)線程操作的數(shù)據(jù),可以將其操作放到 acquire 和 release 方法之間。如下:
多線程的優(yōu)勢(shì)在于可以同時(shí)運(yùn)行多個(gè)任務(wù)(至少感覺起來是這樣)。但是當(dāng)線程需要共享數(shù)據(jù)時(shí),可能存在數(shù)據(jù)不同步的問題。
考慮這樣一種情況:一個(gè)列表里所有元素都是0,線程"set"從后向前把所有元素改成1,而線程"print"負(fù)責(zé)從前往后讀取列表并打印。
那么,可能線程"set"開始改的時(shí)候,線程"print"便來打印列表了,輸出就成了一半0一半1,這就是數(shù)據(jù)的不同步。為了避免這種情況,引入了鎖的概念。
鎖有兩種狀態(tài)——鎖定和未鎖定。每當(dāng)一個(gè)線程比如"set"要訪問共享數(shù)據(jù)時(shí),必須先獲得鎖定;如果已經(jīng)有別的線程比如"print"獲得鎖定了,那么就讓線程"set"暫停,也就是同步阻塞;等到線程"print"訪問完畢,釋放鎖以后,再讓線程"set"繼續(xù)。
經(jīng)過這樣的處理,打印列表時(shí)要么全部輸出0,要么全部輸出1,不會(huì)再出現(xiàn)一半0一半1的尷尬場(chǎng)面。

實(shí)例:

# coding=UTF-8

import threading
import time


class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print ("開啟線程:" + self.name)
        # 獲取鎖,用于線程同步
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # 釋放鎖,開啟下一個(gè)線程
        threadLock.release()


def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1


threadLock = threading.Lock()
threads = []

# 創(chuàng)建新進(jìn)程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 開啟新進(jìn)程
thread1.start()
thread2.start()

# 添加線程到線程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有線程完成
for t in threads:
    t.join()
print("退出主線程")

輸出為:

開啟線程:Thread-1
開啟線程:Thread-2
Thread-1: Tue Aug 22 15:08:20 2017
Thread-1: Tue Aug 22 15:08:21 2017
Thread-1: Tue Aug 22 15:08:22 2017
Thread-2: Tue Aug 22 15:08:24 2017
Thread-2: Tue Aug 22 15:08:26 2017
Thread-2: Tue Aug 22 15:08:28 2017
退出主線程

線程優(yōu)先級(jí)隊(duì)列( Queue)

Python 的 Queue 模塊中提供了同步的、線程安全的隊(duì)列類,包括FIFO(先入先出)隊(duì)列Queue,LIFO(后入先出)隊(duì)列LifoQueue,和優(yōu)先級(jí)隊(duì)列 PriorityQueue。
這些隊(duì)列都實(shí)現(xiàn)了鎖原語,能夠在多線程中直接使用,可以使用隊(duì)列來實(shí)現(xiàn)線程間的同步。
Queue 模塊中的常用方法:
Queue.qsize() 返回隊(duì)列的大小
Queue.empty() 如果隊(duì)列為空,返回True,反之False
Queue.full() 如果隊(duì)列滿了,返回True,反之False
Queue.full 與 maxsize 大小對(duì)應(yīng)
Queue.get([block[, timeout]])獲取隊(duì)列,timeout等待時(shí)間
Queue.get_nowait() 相當(dāng)Queue.get(False)
Queue.put(item) 寫入隊(duì)列,timeout等待時(shí)間
Queue.put_nowait(item) 相當(dāng)Queue.put(item, False)
Queue.task_done() 在完成一項(xiàng)工作之后,Queue.task_done()函數(shù)向任務(wù)已經(jīng)完成的隊(duì)列發(fā)送一個(gè)信號(hào)
Queue.join() 實(shí)際上意味著等到隊(duì)列為空,再執(zhí)行別的操作

# coding=UTF-8

import threading
import time
import Queue

exitFlag = 0


class myThread(threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q

    def run(self):
        print("開啟線程:" + self.name)
        process_data(self.name, self.q)
        print("退出線程:" + self.name)


def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print ("%s processing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)


threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []
threadID = 1

# 創(chuàng)建新線程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充隊(duì)列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待隊(duì)列為空
while not workQueue.empty():
    pass

# 通知線程是否退出
exitFlag = 1

# 等待所有線程完成
for t in threads:
    t.join()

print("退出主線程")

上面的代碼每次執(zhí)行的結(jié)果是不一樣的,取決于哪個(gè)進(jìn)程先獲得鎖,一次運(yùn)行的輸出如下:

開啟線程:Thread-1
開啟線程:Thread-2
開啟線程:Thread-3
Thread-2 processing One
Thread-1 processing Two
Thread-3 processing Three
Thread-2 processing Four
Thread-1 processing Five
退出線程:Thread-3
退出線程:Thread-2
退出線程:Thread-1
退出主線程

參考文章

進(jìn)程與線程的一個(gè)簡(jiǎn)單解釋:
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
多線程就這么簡(jiǎn)單:
http://www.cnblogs.com/fnng/p/3670789.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 1、線程和進(jìn)程 計(jì)算機(jī)的核心是CPU,它承擔(dān)了所有的計(jì)算任務(wù)。它就像一座工廠,時(shí)刻在運(yùn)行。 假定工廠的電力有限,一...
    文哥的學(xué)習(xí)日記閱讀 14,365評(píng)論 0 9
  • 來源:數(shù)據(jù)分析網(wǎng)Threading 模塊從 Python 1.5.2 版開始出現(xiàn),用于增強(qiáng)底層的多線程模塊 thr...
    PyChina閱讀 1,740評(píng)論 0 5
  • 線程 引言&動(dòng)機(jī) 考慮一下這個(gè)場(chǎng)景,我們有10000條數(shù)據(jù)需要處理,處理每條數(shù)據(jù)需要花費(fèi)1秒,但讀取數(shù)據(jù)只需要0....
    不浪漫的浪漫_ea03閱讀 371評(píng)論 0 0
  • 引言&動(dòng)機(jī) 考慮一下這個(gè)場(chǎng)景,我們有10000條數(shù)據(jù)需要處理,處理每條數(shù)據(jù)需要花費(fèi)1秒,但讀取數(shù)據(jù)只需要0.1秒,...
    chen_000閱讀 519評(píng)論 0 0
  • 1. 區(qū)分重載方法 規(guī)則很簡(jiǎn)單,每個(gè)重載的方法都必須有一個(gè)獨(dú)一無二的參數(shù)類型列表。以返回值來區(qū)分重載方法是行不通的...
    Megamind_China閱讀 349評(píng)論 0 0