• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 多線程的同步與互斥(互斥鎖、條件變量、讀寫鎖、自旋鎖、信號量)

    標簽: linux多線程

    文章目錄

    一、同步與互斥的概念

    二、互斥鎖(同步)

    三、條件變量(同步)

    1、線程的條件變量實例1

    2、線程的條件變量實例2

    3、虛假喚醒(spurious wakeup)

    四、讀寫鎖(同步)

    五、自旋鎖(同步)

    六、信號量(同步與互斥)

     

    一、同步與互斥的概念

    ??現代操作系統基本都是多任務操作系統,即同時有大量可調度實體在運行。在多任務操作系統中,同時運行的多個任務可能:

    • 都需要訪問/使用同一種資源
    • 多個任務之間有依賴關系,某個任務的運行依賴于另一個任務。

    【同步】:

    ??是指散步在不同任務之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先后次序來運行,這種先后次序依賴于要完成的特定的任務。最基本的場景就是:兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先后次序運行。比如 A 任務的運行依賴于 B 任務產生的數據。

    【互斥】:

    ??是指散步在不同任務之間的若干程序片斷,當某個任務運行其中一個程序片段時,其它任務就不能運行它們之中的任一程序片段,只能等到該任務運行完這個程序片段后才可以運行。最基本的場景就是:一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。

    二、互斥鎖(同步)

    ??在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。這個過程有點類似于,公司部門里,我在使用著打印機打印東西的同時(還沒有打印完),別人剛好也在此刻使用打印機打印東西,如果不做任何處理的話,打印出來的東西肯定是錯亂的。
    ??在線程里也有這么一把鎖——互斥鎖(mutex),互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即上鎖( lock )和解鎖( unlock )

    【互斥鎖的特點】:

    1. 原子性:把一個互斥量鎖定為一個原子操作,這意味著操作系統(或pthread函數庫)保證了如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以成功鎖定這個互斥量;

    2. 唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量;

    3. 非繁忙等待:如果一個線程已經鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不占用任何cpu資源),直到第一個線程解除對這個互斥量的鎖定為止,第二個線程則被喚醒并繼續執行,同時鎖定這個互斥量。

    【互斥鎖的操作流程如下】:

    1. 在訪問共享資源后臨界區域前,對互斥鎖進行加鎖;

    2. 在訪問完成后釋放互斥鎖導上的鎖。在訪問完成后釋放互斥鎖導上的鎖;

    3. 對互斥鎖進行加鎖后,任何其他試圖再次對互斥鎖加鎖的線程將會被阻塞,直到鎖被釋放。對互斥鎖進行加鎖后,任何其他試圖再次對互斥鎖加鎖的線程將會被阻塞,直到鎖被釋放。

    #include <pthread.h>
    #include <time.h>
    // 初始化一個互斥鎖。
    int pthread_mutex_init(pthread_mutex_t *mutex, 
    						const pthread_mutexattr_t *attr);
    
    // 對互斥鎖上鎖,若互斥鎖已經上鎖,則調用者一直阻塞,
    // 直到互斥鎖解鎖后再上鎖。
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    // 調用該函數時,若互斥鎖未加鎖,則上鎖,返回 0;
    // 若互斥鎖已加鎖,則函數直接返回失敗,即 EBUSY。
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    // 當線程試圖獲取一個已加鎖的互斥量時,pthread_mutex_timedlock 互斥量
    // 原語允許綁定線程阻塞時間。即非阻塞加鎖互斥量。
    int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
    const struct timespec *restrict abs_timeout);
    
    // 對指定的互斥鎖解鎖。
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    // 銷毀指定的一個互斥鎖。互斥鎖在使用完畢后,
    // 必須要對互斥鎖進行銷毀,以釋放資源。
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

    【Demo】(阻塞模式):

    //使用互斥量解決多線程搶占資源的問題
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <string.h>
     
    char* buf[5]; //字符指針數組  全局變量
    int pos; //用于指定上面數組的下標
     
    //1.定義互斥量
    pthread_mutex_t mutex;
     
    void *task(void *p)
    {
        //3.使用互斥量進行加鎖
        pthread_mutex_lock(&mutex);
     
        buf[pos] = (char *)p;
        sleep(1);
        pos++;
     
        //4.使用互斥量進行解鎖
        pthread_mutex_unlock(&mutex);
    }
     
    int main(void)
    {
        //2.初始化互斥量, 默認屬性
        pthread_mutex_init(&mutex, NULL);
     
        //1.啟動一個線程 向數組中存儲內容
        pthread_t tid, tid2;
        pthread_create(&tid, NULL, task, (void *)"zhangfei");
        pthread_create(&tid2, NULL, task, (void *)"guanyu");
        //2.主線程進程等待,并且打印最終的結果
        pthread_join(tid, NULL);
        pthread_join(tid2, NULL);
     
        //5.銷毀互斥量
        pthread_mutex_destroy(&mutex);
     
        int i = 0;
        printf("字符指針數組中的內容是:");
        for(i = 0; i < pos; ++i)
        {
            printf("%s ", buf[i]);
        }
        printf("\n");
        return 0;
    }
    

    【Demo】(非阻塞模式):

    #include <stdio.h>
    #include <pthread.h>
    #include <time.h>
    #include <string.h>
     
    int main (void)
    {
        int err;
        struct timespec tout;
        struct tm *tmp;
        char buf[64];
        pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
        
        pthread_mutex_lock (&lock);
        printf ("mutex is locked\n");
        clock_gettime (CLOCK_REALTIME, &tout);
        tmp = localtime (&tout.tv_sec); 
        strftime (buf, sizeof (buf), "%r", tmp);
        printf ("current time is %s\n", buf);
        tout.tv_sec += 10;
        err = pthread_mutex_timedlock (&lock, &tout);
        clock_gettime (CLOCK_REALTIME, &tout);
        tmp = localtime (&tout.tv_sec);
        strftime (buf, sizeof (buf), "%r", tmp);
        printf ("the time is now %s\n", buf);
        if (err == 0)
            printf ("mutex locked again\n");
        else 
            printf ("can`t lock mutex again:%s\n", strerror (err));
        return 0;
    }
    

    三、條件變量(同步)

    ??與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直 到某特殊情況發生為止。通常條件變量和互斥鎖同時使用
    ??條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步 的一種機制,主要包括兩個動作:

    • 一個線程等待"條件變量的條件成立"而掛起;
    • 另一個線程使 “條件成立”(給出條件成立信號)。

    【原理】:

    ??條件的檢測是在互斥鎖的保護下進行的。線程在改變條件狀態之前必須首先鎖住互斥量。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量 可以被用來實現這兩進程間的線程同步。

    【條件變量的操作流程如下】:

    1. 初始化:init()或者pthread_cond_tcond=PTHREAD_COND_INITIALIER;屬性置為NULL;

    2. 等待條件成立:pthread_wait,pthread_timewait.wait()釋放鎖,并阻塞等待條件變量為真 timewait()設置等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait);

    3. **條件變量:pthread_cond_signal,pthread_cond_broadcast(**所有等待線程)

    4. 清除條件變量:destroy;無線程等待,否則返回EBUSY清除條件變量:destroy;無線程等待,否則返回EBUSY

    #include <pthread.h>
    // 初始化條件變量
    int pthread_cond_init(pthread_cond_t *cond,
    						pthread_condattr_t *cond_attr);
    
    // 阻塞等待
    int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
    
    // 超時等待
    int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,
    						const timespec *abstime);
    
    // 解除所有線程的阻塞
    int pthread_cond_destroy(pthread_cond_t *cond);
    
    // 至少喚醒一個等待該條件的線程
    int pthread_cond_signal(pthread_cond_t *cond);
    
    // 喚醒等待該條件的所有線程
    int pthread_cond_broadcast(pthread_cond_t *cond);  
    

    1、線程的條件變量實例1

    ??Jack開著一輛出租車來到一個站點停車,看見沒人就走了。過段時間,Susan來到站點準備乘車,但是沒有來,于是就等著。過了一會Mike開著車來到了這個站點,Sunsan就上了Mike的車走了。如圖所示:
    在這里插入圖片描述

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <unistd.h>  
    #include <pthread.h>  
      
    pthread_cond_t taxicond = PTHREAD_COND_INITIALIZER;  
    pthread_mutex_t taximutex = PTHREAD_MUTEX_INITIALIZER;  
      
    void *traveler_arrive(void *name)  
    {  
        char *p = (char *)name;  
      
        printf ("Travelr: %s need a taxi now!\n", p);  
        // 加鎖,把信號量加入隊列,釋放信號量
        pthread_mutex_lock(&taximutex);  
        pthread_cond_wait(&taxicond, &taximutex);  
        pthread_mutex_unlock(&taximutex);  
        printf ("traveler: %s now got a taxi!\n", p);  
        pthread_exit(NULL);  
    }  
      
    void *taxi_arrive(void *name)  
    {  
        char *p = (char *)name;  
    
        printf ("Taxi: %s arrives.\n", p);
        // 給線程或者條件發信號,一定要在改變條件狀態后再給線程發信號
        pthread_cond_signal(&taxicond);  
        pthread_exit(NULL);  
    }  
      
    int main (int argc, char **argv)  
    {  
        char *name;  
        pthread_t thread;  
        pthread_attr_t threadattr; // 線程屬性 
        pthread_attr_init(&threadattr);  // 線程屬性初始化
      
        // 創建三個線程
        name = "Jack";  
        pthread_create(&thread, &threadattr, taxi_arrive, (void *)name);  
        sleep(1);  
        name = "Susan";  
        pthread_create(&thread, &threadattr, traveler_arrive, (void *)name);  
        sleep(1);  
        name = "Mike";  
        pthread_create(&thread, &threadattr, taxi_arrive, (void *)name);  
        sleep(1);  
      
        return 0;  
    }
    

    2、線程的條件變量實例2

    ??Jack開著一輛出租車來到一個站點停車,看見沒人就等著。過段時間,Susan來到站點準備乘車看見了Jack的出租車,于是就上去了。過了一會Mike開著車來到了這個站點,看見沒人救等著。如圖所示:
    在這里插入圖片描述

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
     
    int travelercount = 0;
    pthread_cond_t taxicond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t taximutex = PTHREAD_MUTEX_INITIALIZER;
     
    void *traveler_arrive(void *name)
    {
        char *p = (char *)name;
     
        pthread_mutex_lock(&taximutex);
     
        printf ("traveler: %s need a taxi now!\n", p);
        travelercount++;
        pthread_cond_wait(&taxicond, &taximutex);
                
        pthread_mutex_unlock(&taximutex);
        printf ("traveler: %s now got a taxi!\n", p);
        pthread_exit(NULL);
    }
     
    void *taxi_arrive(void *name)
    {
        char *p = (char *)name;
        printf ("Taxi: %s arrives.\n", p);
        for(;;)
        {
            if(travelercount)
            {
                pthread_cond_signal(&taxicond);
                travelercount--;
                break;
            }
        }
        pthread_exit(NULL);
    }
     
    int main (int argc, char **argv)
    {
        char *name;
        pthread_t thread;
        pthread_attr_t threadattr;
        pthread_attr_init(&threadattr);
     
        name = "Jack";
        pthread_create(&thread, &threadattr, taxi_arrive, name);
        sleep(1);
        name = "Susan";
        pthread_create(&thread, &threadattr, traveler_arrive, name);
        sleep(3);
        name = "Mike";
        pthread_create(&thread, &threadattr, taxi_arrive, name);
        sleep(4);
     
        return 0;
    }
    

    3、虛假喚醒(spurious wakeup)

    ??虛假喚醒(spurious wakeup)在采用條件等待時,我們使用的是:

    while(條件不滿足)
    {  
       condition_wait(cond, mutex);  
    }  
    // 而不是:  
    If( 條件不滿足 )
    {  
       Condition_wait(cond,mutex);  
    }   
    

    這是因為可能會存在虛假喚醒”spurious wakeup”的情況。

    ??也就是說,即使沒有線程調用condition_signal, 原先調用condition_wait的函數也可能會返回此時線程被喚醒了,但是條件并不滿足,這個時候如果不對條件進行檢查而往下執行,就可能會導致后續的處理出現錯誤。

    ??虛假喚醒在linux的多處理器系統中/在程序接收到信號時可能回發生。在Windows系統和JAVA虛擬機上也存在。在系統設計時應該可以避免虛假喚醒,但是這會影響條件變量的執行效率,而既然通過while循環就能避免虛假喚醒造成的錯誤,因此程序的邏輯就變成了while循環的情況。

    四、讀寫鎖(同步)

    ??讀寫鎖與互斥量類似,不過讀寫鎖允許更改的并行性,也叫共享互斥鎖。互斥量要么是鎖住狀態,要么就是不加鎖狀態,而且一次只有一個線程可以對其加鎖。讀寫鎖可以有3種狀態:讀模式下加鎖狀態、寫模式加鎖狀態、不加鎖狀態

    ??一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖(允許多個線程讀但只允許一個線程寫)

    【讀寫鎖的特點】:

    • 如果有其它線程讀數據,則允許其它線程執行讀操作,但不允許寫操作;

    • 如果有其它線程寫數據,則其它線程都不允許讀、寫操作

    【讀寫鎖的規則】:

    • 如果某線程申請了讀鎖,其它線程可以再申請讀鎖,但不能申請寫鎖;

    • 如果某線程申請了寫鎖,其它線程不能申請讀鎖,也不能申請寫鎖。

    讀寫鎖適合于對數據結構的讀次數比寫次數多得多的情況。

    #include <pthread.h>
    // 初始化讀寫鎖
    int pthread_rwlock_init(pthread_rwlock_t *rwlock, 
    						const pthread_rwlockattr_t *attr); 
    
    // 申請讀鎖
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock ); 
    
    // 申請寫鎖
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock ); 
    
    // 嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖,
    // 如果有任何的讀者或寫者持有該鎖,則立即失敗返回。
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 
    
    // 解鎖
    int pthread_rwlock_unlock (pthread_rwlock_t *rwlock); 
    
    // 銷毀讀寫鎖
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

    【Demo】:

    // 一個使用讀寫鎖來實現 4 個線程讀寫一段數據是實例。
    // 在此示例程序中,共創建了 4 個線程,
    // 其中兩個線程用來寫入數據,兩個線程用來讀取數據
    #include <stdio.h>  
    #include <unistd.h>  
    #include <pthread.h>  
    
    pthread_rwlock_t rwlock; //讀寫鎖  
    int num = 1;  
      
    //讀操作,其他線程允許讀操作,卻不允許寫操作  
    void *fun1(void *arg)  
    {  
        while(1)  
        {  
            pthread_rwlock_rdlock(&rwlock);
            printf("read num first == %d\n", num);
            pthread_rwlock_unlock(&rwlock);
            sleep(1);
        }
    }
      
    //讀操作,其他線程允許讀操作,卻不允許寫操作  
    void *fun2(void *arg)
    {
        while(1)
        {
            pthread_rwlock_rdlock(&rwlock);
            printf("read num second == %d\n", num);
            pthread_rwlock_unlock(&rwlock);
            sleep(2);
        }
    }
     
    //寫操作,其它線程都不允許讀或寫操作  
    void *fun3(void *arg)
    {
        while(1)
        {
            pthread_rwlock_wrlock(&rwlock);
            num++;
            printf("write thread first\n");
            pthread_rwlock_unlock(&rwlock);
            sleep(2);
        }
    }
     
    //寫操作,其它線程都不允許讀或寫操作  
    void *fun4(void *arg)
    {
        while(1)
        {  
            pthread_rwlock_wrlock(&rwlock);  
            num++;  
            printf("write thread second\n");  
            pthread_rwlock_unlock(&rwlock);  
            sleep(1);  
        }  
    }  
      
    int main()  
    {  
        pthread_t ptd1, ptd2, ptd3, ptd4;  
          
        pthread_rwlock_init(&rwlock, NULL);//初始化一個讀寫鎖  
          
        //創建線程  
        pthread_create(&ptd1, NULL, fun1, NULL);  
        pthread_create(&ptd2, NULL, fun2, NULL);  
        pthread_create(&ptd3, NULL, fun3, NULL);  
        pthread_create(&ptd4, NULL, fun4, NULL);  
          
        //等待線程結束,回收其資源  
        pthread_join(ptd1, NULL);  
        pthread_join(ptd2, NULL);  
        pthread_join(ptd3, NULL);  
        pthread_join(ptd4, NULL);  
          
        pthread_rwlock_destroy(&rwlock);//銷毀讀寫鎖  
          
        return 0;  
    }  
    

    五、自旋鎖(同步)

    ??自旋鎖與互斥量功能一樣,唯一一點不同的就是互斥量阻塞后休眠讓出cpu,而自旋鎖阻塞后不會讓出cpu,會一直忙等待,直到得到鎖。
    ??自旋鎖在用戶態使用的比較少,在內核使用的比較多!自旋鎖的使用場景:鎖的持有時間比較短,或者說小于2次上下文切換的時間
    ??自旋鎖在用戶態的函數接口和互斥量一樣,把pthread_mutex_xxx()中mutex換成spin,如:pthread_spin_init()。

    六、信號量(同步與互斥)

    ??信號量廣泛用于進程或線程間的同步和互斥,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。

    ??編程時可根據操作信號量值的結果判斷是否對公共資源具有訪問的權限,當信號量值大于 0 時,則可以訪問,否則將阻塞。PV 原語是對信號量的操作,一次 P 操作使信號量減1,一次 V 操作使信號量加1。

    #include <semaphore.h>
    // 初始化信號量
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    // 信號量 P 操作(減 1)
    int sem_wait(sem_t *sem);
    
    // 以非阻塞的方式來對信號量進行減 1 操作
    int sem_trywait(sem_t *sem);
    
    // 信號量 V 操作(加 1)
    int sem_post(sem_t *sem);
    
    // 獲取信號量的值
    int sem_getvalue(sem_t *sem, int *sval);
    
    // 銷毀信號量
    int sem_destroy(sem_t *sem);
    

    【信號量用于同步】:
    在這里插入圖片描述

    // 信號量用于同步實例
    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
     
    sem_t sem_g,sem_p;   //定義兩個信號量
    char ch = 'a';
     
    void *pthread_g(void *arg)  //此線程改變字符ch的值
    {
        while(1)
        {
            sem_wait(&sem_g);
            ch++;
            sleep(1);
            sem_post(&sem_p);
        }
    }
     
    void *pthread_p(void *arg)  //此線程打印ch的值
    {
        while(1)
        {
            sem_wait(&sem_p);
            printf("%c",ch);
            fflush(stdout);
            sem_post(&sem_g);
        }
    }
     
    int main(int argc, char *argv[])
    {
        pthread_t tid1,tid2;
        sem_init(&sem_g, 0, 0); // 初始化信號量為0
        sem_init(&sem_p, 0, 1); // 初始化信號量為1
        
        // 創建兩個線程
        pthread_create(&tid1, NULL, pthread_g, NULL);
        pthread_create(&tid2, NULL, pthread_p, NULL);
        
        // 回收線程
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        
        return 0;
    }
    

    【信號量用于互斥】:
    在這里插入圖片描述

    // 信號量用于互斥實例
    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <semaphore.h>
     
    sem_t sem; //信號量
     
    void printer(char *str)
    {
        sem_wait(&sem);//減一,p操作
        while(*str) // 輸出字符串(如果不用互斥,此處可能會被其他線程入侵)
        {
            putchar(*str);  
            fflush(stdout);
            str++;
            sleep(1);
        }
        printf("\n");
        
        sem_post(&sem);//加一,v操作
    }
     
    void *thread_fun1(void *arg)
    {
        char *str1 = "hello";
        printer(str1);
    }
     
    void *thread_fun2(void *arg)
    {
        char *str2 = "world";
        printer(str2);
    }
     
    int main(void)
    {
        pthread_t tid1, tid2;
        
        sem_init(&sem, 0, 1); //初始化信號量,初始值為 1
        
        //創建 2 個線程
        pthread_create(&tid1, NULL, thread_fun1, NULL);
        pthread_create(&tid2, NULL, thread_fun2, NULL);
        
        //等待線程結束,回收其資源
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL); 
        
        sem_destroy(&sem); //銷毀信號量
        
        return 0;
    }
    

     

    版權聲明:本文為wangquan1992原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/wangquan1992/article/details/108123042

    智能推薦

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    猜你喜歡

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    基于TCP/IP的網絡聊天室用Java來實現

    基于TCP/IP的網絡聊天室實現 開發工具:eclipse 開發環境:jdk1.8 發送端 接收端 工具類 運行截圖...

    19.vue中封裝echarts組件

    19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...

    精品国产乱码久久久久久蜜桃不卡