Linux基礎第七章 線程

来源:https://www.cnblogs.com/w-x-me/archive/2018/08/08/6412531.html
-Advertisement-
Play Games

前言 之前討論了進程,瞭解一個進程能做一件事情,如果想同時處理多件事情,那麼需要多個進程,但是進程間很不方便的一點是,進程間的數據交換似乎沒有那麼方便。Linux提供線程功能,能在一個進程中,處理多任務,而且線程之間的數據是完全共用的。 線程也有PCB,它的PCB和進程的PCB結構完全一樣,只是它里 ...


前言

之前討論了進程,瞭解一個進程能做一件事情,如果想同時處理多件事情,那麼需要多個進程,但是進程間很不方便的一點是,進程間的數據交換似乎沒有那麼方便。Linux提供線程功能,能在一個進程中,處理多任務,而且線程之間的數據是完全共用的。

線程也有PCB,它的PCB和進程的PCB結構完全一樣,只是它裡面保存的虛擬地址空間和創建它的進程的虛擬地址空間完全保持一致。

線程的創建

通過pthread_create函數可以創建一個線程,被創建的線程的常式,就是一個新的執行指令序列了。

#include <pthread.h>

void* thread_func(void* p )
{
    return NULL;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%d\n", (int)tid);
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%d\n", (int)tid);
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%d\n", (int)tid);
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%d\n", (int)tid);

    getchar();
}

 

  Compile and link with -lpthread.  

補充
intptr_t是一種整型,它的長度依賴機器位長,也就意味著它的長度和指針的長度一樣的。

 線程標識

線程使用pthread_t來標識線程,它也是一個非負整數,由系統分配,保證在進程範圍內唯一。pthread_t雖然在Linux下是非負整數,但是在其它平臺下不一定是,所以比較線程號是否想等,應該用pthread_equal

任何一個函數都可以調用pthread_self來獲取目前代碼運行的線程。

線程終止

終止方式 
常式返回 正常退出
調用pthread_exit 正常退出
響應pthread_cancel 異常退出

註意:

  • 線上程里調用exit是退出整個進程。

  • 在多線程的進程中,主線程調用pthread_exit,進程並不會退出,它的其他線程依舊在執行,但是主線程已經退出了。

  • 意味著:主線程和其他線程是幾乎是平等的。

  • 不平等的是,如果主線程的main函數return了,那麼其他線程也結束了,如果其他線程的入口函數return了,主線程不會跟著結束。

線程的回收

線程退出之後,它的PCB依舊在內核中存在,等著其它線程來獲取它的運行結果,可以通過pthread_join來回收線程。從這個角度看,線程和進程差不多,但是跟進程不同的時,線程沒有父線程的概念,同一個進程內的其它線程都可以來回收它的運行結果。

pthread_join會阻塞調用它的線程,一直到被join的線程結束為止。

pthread_joinwait/waitpid一樣,也是阻塞的調用,它除了有回收PCB的功能,也有等待線程結束的功能。

線程的使用場景

 客戶端使用場景

一般來說,線程用於比較複雜的多任務場景,比如:

 

這樣主線程可以基礎處理主線程的事情,不至於被覆雜的任務阻塞。比如:

 

這樣聊天界面不會卡死在那裡,否則如果網路情況很差,有可能導致界面卡死。

伺服器使用場景

伺服器一般的流程如下:

 

在伺服器上,一個線程來處理整個流程,會導致處理流程非常慢,導致主線程無法及時接收報文。一般會使用子線程來做具體的工作,而主線程只負責接收報文。

有時為了提高處理效率,會使用線程池

 

 

7.7 線程的同步

無論上述那種場景,都有一個報文隊列或者消息隊列,一般這個隊列是一個鏈表,主線程需要往鏈表中添加數據,而子線程從鏈表獲取數據。兩個線程同時操作一個全局變數是不安全的,應該避免不安全的訪問。無論這種全局變數是數組、鏈表、還是一個簡單的變數。

線程A:i = i + 1;
線程B:i = i + 1;

Snip20161009_30

7.7.1 不安全的案例

  • 多線程操作一個全局變數

#include <stdio.h> #include <signal.h> #include <pthread.h>   int result=0;   void add() {     int i;     for(i=0; i<100000; ++i)     {         result++;     } }   void* thread_func(void* p) {     add();     return NULL; }   int main() {     pthread_t t1;     pthread_t t2;       pthread_create(&t1, NULL, thread_func, NULL);     pthread_create(&t2, NULL, thread_func, NULL);       pthread_join(t1, NULL);     pthread_join(t2, NULL);       printf("%d\n", result);     return 0; }
  • 不安全的生產者消費者模型

#include <list>   struct task_t {     int task; };   list<task_t*> queue;   void* work_thread(void* arg) {     while(1)     {         if(queue.size() == 0) continue;           task_t* task = *queue.begin();         queue.pop_front();           printf("task value is %d\n", task->task);         delete task;     } }   void main(int argc, char* argv[]) {     pthread_t tid;     pthread_create(&tid, NULL, work_thread, NULL);       while(1)     {         int i;         cin >> i;         task_t* task = new task_t;         task->task = i;           queue.push_back(task);     }       pthread_join(tid, NULL); }

7.7.2 鎖(臨界量)

鎖能避免兩個線程同時訪問一個全局變數。
鎖會帶來兩個問題:

  • 效率低

  • 死鎖

    #include <stdio.h>
    #include <pthread.h>
    
    int result = 0;
    // 定義鎖,鎖一般也定義在全局
    //pthread_mutex_t mutex;  // 粗粒度的鎖
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    int result1 = 0;
    pthread_mutex_t mutex1;
    
    // 1.一個線程重覆加鎖兩次,會死鎖
    void func()
    {
        pthread_mutex_lock(&mutex);
    
        pthread_mutex_unlock(&mutex);
    }
    
    void foo()
    {
        pthread_mutex_lock(&mutex);
        func();
        pthread_mutex_unlock(&mutex);
    }
    
    // 2. 一個線程加鎖之後,忘記瞭解鎖
    void foo1()
    {
    
        pthread_mutex_lock(&mutex);
        if(...) // 這種場合容易產生忘記解鎖
            return;
        // ....
        // 忘記瞭解鎖
        pthread_mutex_unlock(&mutex);
    }
    
    void foo2()
    {
        // 因為別的線程忘記解鎖,所以本線程無法進行加鎖
        pthread_mutex_lock(&mutex); // 阻塞在這裡
        pthread_mutex_unlock(&mutex);
    }
    
    void* thread_func(void* ptr)
    {
        foo();
    
        int i=0;
        for(i=0; i<100000; ++i)
        {
            pthread_mutex_lock(&mutex1);
            result1++;//它的值由什麼決定
            pthread_mutex_unlock(&mutex1);
    
            // 兩個線程同時操作全局變數,結果不可靠
            //
            // 將該操作變成原子操作,或者至少不應該被能影響它操作的人打斷
            pthread_mutex_lock(&mutex);
            result ++;  // result++代碼被鎖保護了,不會被其他線程的result++影響
            pthread_mutex_unlock(&mutex);
        }
        return NULL;
    }
    
    int main()
    {
        // 使用鎖之前,要對它進行初始化
    //    pthread_mutex_init(&mutex, NULL);
        pthread_mutex_init(&mutex1, NULL);
    
        pthread_t t1, t2;
        pthread_create(&t1, NULL, thread_func, NULL);
        pthread_create(&t2, NULL, thread_func, NULL);
    
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
    
        printf("result is %d\n", result);
    }
    #include <stdio.h>
    #include <list>
    #include <iostream>
    using namespace std;
    
    struct task_t
    {
        int task;
    };
    
    // 全局的任務隊列
    list<task_t*> tasks;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    
    
    // pthred_cond_signal和pthread_cond_wait類似不可靠信號,signal不會累計
    // 當一個線程發送signal時,如果另外一個線程此時沒有調用wait函數,那麼這個signal就會消失掉
    
    void* work_thread(void* ptr)
    {
        while(1)
        {
            // 等待條件
            pthread_mutex_lock(&mutex);
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
    
            // 一旦條件滿足,就應該處理隊列中所有的任務
            while(1)
            {
                pthread_mutex_lock(&mutex);
                if(tasks.size() == 0) 
                {
                    pthread_mutex_unlock(&mutex); // 特別容易忘記解鎖
                    break;
                }
                task_t* task = *tasks.begin();
                tasks.pop_front();
                pthread_mutex_unlock(&mutex);
    
                // 處理任務
                printf("current task is %d\n", task->task);
    
                // new和delete(malloc和free)都是線程安全的
                delete task;
            }
        }
    }
    
    int main()
    {
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);
    
        pthread_t tid;
        pthread_create(&tid, NULL, work_thread, NULL);
    
        while(1)
        {
            int i;
            // 阻塞的,等待任務
            cin >> i;
    
            // 構造任務結構體
            task_t* task = new task_t;
            task->task = i;
    
            // 把任務丟到任務列表中
            pthread_mutex_lock(&mutex);
            tasks.push_back(task);
            pthread_mutex_unlock(&mutex);
    
            // 喚醒條件變數
            pthread_cond_signal(&cond);
        }
    }
    //運用析構函數
    
    #ifndef __AUTO_LOCK_H__
    #define __AUTO_LOCK_H__
    
    #include <pthread.h>
    
    class auto_lock
    {
    public:
        auto_lock(pthread_mutex_t& m);
        ~auto_lock();
    private:
        pthread_mutex_t& mutex;
    };
    
    #endif
    
    
    
    #include "auto_lock.h"
    
    auto_lock::auto_lock(pthread_mutex_t& m): mutex(m)
    {
        pthread_mutex_lock(&mutex);
    }
    
    auto_lock::~auto_lock()
    {
        pthread_mutex_unlock(&mutex);
    }
    
    
    
    #include <stdio.h>
    #include "auto_lock.h"
    
    pthread_mutex_t mutex;
    int result = 0;
    
    void* thread_func(void*ptr)
    {
        for(int i=0 ;i<100000; ++i)
        {
            auto_lock var1(mutex); // 重覆加鎖
            auto_lock var(mutex); // 在構造里自動加鎖
            result++;
        }
    }
    
    int main()
    {
        // 變成遞歸鎖   及迴圈鎖  
        pthread_mutexattr_t attr;//設計迴圈鎖屬性
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 
    
        // 用遞歸屬性去初始化這個鎖
        pthread_mutex_init(&mutex, &attr);
    
        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, thread_func, NULL);
        pthread_create(&tid2, NULL, thread_func, NULL);
        
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        
        printf("result is %d\n", result);
    }

     

相對的解決方法:

  • 讀寫鎖

  • #include <pthread.h>
    
    pthread_rwlock_t mutex;
    int result;
    
    void* thread_func(void* ptr)
    {
        pthread_rwlock_rdlock(&mutex);
        // 只能對數據讀
        result ++; // 寫數據的行為是會導致數據不正確
        pthread_rwlock_unlock(&mutex);
    
        pthread_rwlock_wrlock(&mutex);
        // 可以對數據讀寫
        pthread_rwlock_unlock(&mutex);
    }
    
    int main()
    {
    
        pthread_rwlock_init(&mutex, NULL);
    
        pthread_t tid;
        pthread_create(&tid, NULL, thread_func, NULL);
    }

     

  • 迴圈鎖

7.7.2.1 基本鎖

類型:pthread_mutex_t
定義的變數一般在全局:pthread_mutex_t g_mutex;
在使用之前要初始化:pthread_mutex_init(&g_mutex, NULL);
訪問敏感對象前加鎖:pthread_mutex_lock(&g_mutex);
訪問結束要解鎖:pthread_mutex_unlock(&g_mutex);

一把所可以負責多個全局變數的安全問題,但是負責的範圍越大,效率越低,代碼相對容易寫。負責全局變數的數量,被稱之為鎖的粒度。

死鎖問題

  1. 忘瞭解鎖會產生死鎖

  2. 重覆加鎖會導致死鎖

怎麼解決死鎖問題:

  1. 忘瞭解鎖:程式員自己要註意

  2. 重覆加鎖:使用迴圈鎖可以解決問題

7.7.2.2 迴圈鎖

解決重覆加鎖導致死鎖問題,迴圈鎖的特點是,同一個線程進行多次加鎖,不會阻塞。
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex); // 第二次加鎖不會阻塞,但是它會給mutex增加一個計數。
pthread_mutex_unlock(&mutex) // 減少計數
pthread_mutex_unlock(&mutex);//減少到0的時候,真正解鎖

怎麼設置迴圈鎖。

     pthread_mutexattr_t attr;      // 設置成迴圈鎖屬性      pthread_mutexattr_init(&attr);      pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);        // 此時mutex是一個迴圈鎖      pthread_mutex_init(&mutex, &attr);  

//頭文件
#ifndef __AUTO_LOCK_H__
#define __AUTO_LOCK_H__

#include <pthread.h>

class auto_lock
{
public:
auto_lock(pthread_mutex_t& m);
~auto_lock();
private:
pthread_mutex_t& mutex;
};

#endif

//頭文件的實現

#include "auto_lock.h"

auto_lock::auto_lock(pthread_mutex_t& m): mutex(m)
{
pthread_mutex_lock(&mutex);
}

auto_lock::~auto_lock()
{
pthread_mutex_unlock(&mutex);
}



//主函數
#include <stdio.h> #include "auto_lock.h" pthread_mutex_t mutex; int result = 0; void* thread_func(void*ptr) { for(int i=0 ;i<100000; ++i) { auto_lock var1(mutex); // 重覆加鎖 auto_lock var(mutex); // 在構造里自動加鎖 result++; } } int main() { // 變成遞歸鎖 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 用遞歸屬性去初始化這個鎖 pthread_mutex_init(&mutex, &attr); pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread_func, NULL); pthread_create(&tid2, NULL, thread_func, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("result is %d\n", result); }

 

7.7.2.3 讀共用寫排他鎖(讀寫鎖)

共用鎖/排他鎖
定義鎖:pthread_rwlock_t mutex;
初始化:pthread_rwlock_init(&mutex, NULL);
讀鎖定:pthread_rwlock_rdlock(&mutex);
寫鎖定:pthread_rwlock_wrlock(&mutex);
解鎖:pthread_rwlock_unlock(&mutex);

7.7.2.4 總結

  1. 無論是什麼鎖,都會導致性能下降,所以能不用就儘量不用

  2. 鎖能不能用於進程間同步?可以

 C++使用構造函數和析構函數自動加鎖解鎖

7.7.3 條件變數

條件變數是另外一種同步機制,它可以使線程在無競爭的等待條件發生。在之前講到的線程場景里,子線程往往要等到隊列有數據才運行,否則它應該休眠,以避免浪費CPU。但是如果用鎖來實現這種機制的話,會非常麻煩。

定義:pthread_cond_t g_cond;
初始化:pthread_cond_init(&g_cond);
等待:pthread_cond_wait(&g_cond, &g_mutex);
喚醒:pthread_cond_signal(&g_cond);
pthread_cond_broadcast(&g_cond);
驚群

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void* thread_func(void* ptr)
{
    sleep(1);

    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);

    printf("wait ok\n");
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);

    // 發信號時,線程不是正在調用pthread_cond_wait,而是在執行sleep(1),所以signal發送之後,就消失了,不會保留
    // 按照剛纔的說法,這個signal根本無效
    // 所以當一個線程發送多次的signal時,那麼最多只有一次是有作用的
    pthread_cond_signal(&cond);

    pthread_join(tid, NULL);

}

 

7.7.3.1 條件變數的等待和喚醒

如果沒有線程在等待條件,此時喚醒函數pthread_cond_signal不會喚醒任何的線程,也不會記錄。

如果有多個線程在執行pthread_cond_wait,而此時有一個線程調用pthread_cond_signal,那麼只會喚醒其中一個線程。

如果想喚醒所有線程,那麼調用pthread_cond_broadcast,該函數可以喚醒等待該條件的所有線程。

#include <stdio.h>
#include <pthread.h>

// 假如有三個線程同時調用pthread_cond_wait,一個線程調用pthread_cond_signal
//
pthread_mutex_t mutex;
pthread_cond_t cond;

void* thread_func(void* ptr)
{

    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);

    printf("wait ok\n");
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, thread_func, NULL);
    pthread_create(&tid2, NULL, thread_func, NULL);
    pthread_create(&tid3, NULL, thread_func, NULL);

    sleep(1);
    // 喚醒一個線程
//    pthread_cond_signal(&cond);
//    喚醒所有正在等待的線程
    pthread_cond_broadcast(&cond);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

}

 

7.7.4 信號量

信號量類似條件變數,但是信號量可以保存信號數量。

  • 定義: sem_t sem;

  • 初始化:sem_init(&sem, 0, 0);
    初始化的第二個參數,如果是0表示同一進程內的多線程之間的信號量,如果是非0,那麼該信號量可以使用在進程之間。第三個參數表示信號量的初始值。

  • 等待:sem_wait(&sem);
    sem_wait函數會導致該線程休眠,喚醒的條件是sem的值大於0。並且sem_wait調用結束後,會自動將sem值減1。

  • 喚醒:sem_post(&sem);
    sem_post只是簡單的將sem值+1

    #include <stdio.h>
    #include <semaphore.h>
    #include <pthread.h>
    
    sem_t sem;
    
    void* thread_func(void* ptr)
    {
        sleep(1);
        sem_wait(&sem);
        printf("wait ok\n");
    }
    
    int main()
    {
        sem_init(&sem, 0, 0);
    
        pthread_t tid1, tid2, tid3;
        pthread_create(&tid1, NULL, thread_func, NULL);
        pthread_create(&tid2, NULL, thread_func, NULL);
        pthread_create(&tid3, NULL, thread_func, NULL);
    
        // 發送信號
        sem_post(&sem);
        
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        pthread_join(tid3, NULL);
    }

     

7.8 重入

如果函數操作了全局變數,這個函數就不是可重入的函數了。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
int result = 0;
void foo()
{
    // 因為這個函數操作了全局變數
    result ++;
}

void* thread_func(void* ptr)
{
#if 0
    int i;
    for(i=0; i<10000; ++i)
    {
        // 該函數是不可重入的函數
        // 用鎖來保護它
        foo();
    }
#endif

    char p[] = "1 2 3 4 5 6 7 8 9 0";

    char* saveptr;
    char* sub = strtok_r(p, " ", &saveptr);
    while(sub)
    {
        usleep(1000); // 1毫秒        
        printf("%s, tid=%d\n", sub, (int)pthread_self());
        sub = strtok_r(NULL, " ", &saveptr);
    }

}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func, NULL);
    pthread_create(&tid2, NULL, thread_func, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    printf("result=%d\n", result);
}

 


Snip20161009_31
Snip20161009_32

7.9 分離的線程

分離的線程不用pthread_join,也無法通過pthread_join來獲取結果。因為它運行結束之後,它的PCB同時被釋放了。

#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <inttypes.h>

// intptr_t 整數類型:char short int long (long long)
//     整數:8 16 32 64
//     有些機器的int是32位,有的機器是64位
//     void*指針類型都是按照機器的字長決定
//
//     intptr_t是一個整數,並且它總是和指針的位元組數是一樣的

void* thread_func(void* ptr)
{
    // 用的是地址本身,而不是地址指向的值
    printf("%d\n", (int)(intptr_t)ptr);
    sleep(1);
}

int foo()
{
    char p[] = "hello world";
    int a = 100;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    pthread_t tid;
    pthread_create(&tid, &attr, thread_func, (void*)(intptr_t)a);

    // 該線程自生自滅
    // pthread_detach(tid);

    int ret = pthread_join(tid, NULL);
    printf("join error, ret=%d, errno=%d, EINVAL=%d\n", ret, errno, EINVAL);
}

int main()
{
    foo();
    sleep(2);
}

 

7.10 線程私有數據

線程可以定義私有數據,私有數據只供該線程使用。
線程私有數據可以在該線程調用函數中訪問,其他線程調用的函數中,不可訪問。

// 定義線程私有數據的key,是線上程設置和使用私有數據之前創建 pthread_key_t key; pthread_key_create(&key, 用來清理私有數據的函數指針);   // 設置私有數據,該函數被那個線程調用,那麼就是設置該線程私有數據 pthread_set_specific(key, data); void* data = pthread_get_specific(key);  
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_key_t key;

// 可能被線程A調用
// 也可能線程B調用
void foo()
{
    char* p = (char*)pthread_getspecific(key);
    printf("%s\n", p);
}

void my_malloc()
{
    // 去這個線程的記憶體池去申請記憶體
    void* mempool = pthread_getspecific(key);
  //  __my_malloc(mempool, ...);
}

void* thread_func(void* ptr)
{
    // setspecific,需要線上程中調用,當然也可以在主線程中調用
    // 為這個線程設置私有數據
    pthread_setspecific(key, ptr);

    foo();
    my_malloc();
    return NULL;
}

void free_func(void* ptr)
{
    printf("free call\n");
    free(ptr);
}

int main()
{
    pthread_key_create(&key, free_func);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func, strdup("thread1"));
    pthread_create(&tid2, NULL, thread_func, strdup("thread2"));

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

}
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 線程本地變數,每個線程都有一份拷貝
thread_local int result = 0;

void foo()
{
    // 全局變數
    thread_local static int a = 0;
    a++;
    printf("%d\n", a);
}



void* thread_func1(void* ptr)
{
    foo();
    foo();
    result = 100;
}

void* thread_func2(void* ptr)
{
    foo();
    foo();

    sleep(1);
//    printf("%d\n", result); // 100
    printf("%d\n", result); // thread_local時,這個值是0
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func1, NULL);
    pthread_create(&tid2, NULL, thread_func2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
}

 

7.11 線程取消

取消線程也結束線程,但是應該避免這種設計。

退出點函數:man pthreads搜索cancel關鍵字,找到這些退出點函數。

pthread_cancel線上程外部(其他線程)來退出另外一個線程A,當線程A調用了cancelpoint函數時,會退出。

如果希望調用cancelpoint函數不退出,應該設置當前的線程狀態為:不理會線程退出(cancelability disabled)
pthread_setcancelstate(...)

#include <stdio.h>
#include <pthread.h>

void* thread_func(void* ptr)
{
    // 因為這個線程沒有cancel point
    while(1)
    {
        // 關閉cancel檢測
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

        sleep(10);
    
        // 打開cancel檢測
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

        // 檢查cancel point
        pthread_testcancel(); 
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);

    // 讓線程退出
    pthread_cancel(tid);

    // 等待線程退出
    pthread_join(tid, NULL);
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [20180806]tune2fs調整保留塊百分比.txt--//生產系統一臺dg磁碟空間滿了.我前一陣子已經將*convert參數修改,增加磁碟,但是這個分區裡面的數據文件還可以增長,這樣依舊存--//在磁碟空間不足的情況,正常應該移動數據文件到別的分區,然後rename.突然想起建立分區時有一定 ...
  • 在Linux 中如何查看分區的文件系統類型,下麵總結幾種查看分區文件系統類型的方法。 1: df -T 命令查看 這個是最簡單的命令,文件系統類型在Type列輸出。只可以查看已經掛載的分區和文件系統類型。如下所示: [root@mylnx008 ~]# df -T /dev/sdbFilesyste... ...
  • 在Linux通用I/O模型中,I/O操作系列函數(系統調用)都是圍繞一個叫做文件描述符的整數展開。這不禁讓人產生疑問:這個整數代表什麼?一個數值代表一個文件嗎?隨便傳一個整數進去調用可以嗎? ...
  • 前言前篇說了作為運維在資料庫塊最起碼要會兩大技能,今天來說說第二技能--主從複製隨著業務的增長,一臺資料庫伺服器以滿足不了需求了,負載過重,這時候就需要減壓,實現負載均衡讀寫分離,一主一從或一主多從主伺服器只管寫,從伺服器管讀,從而提高效率減輕壓力。主從複製分類:主從同步:當用戶寫數據主伺服器必須和... ...
  • 用過linux的小伙伴可能都知道,每次使用sudo的時候需要輸入密碼,這樣很耽誤事,那麼接下來我會教大家如何去設置 1、輸入su root進入root模式2、輸入visudo會打開/etc/sudoers文件3、找到%sudo ALL=(ALL:ALL) ALL這一行修改為%sudo ALL=(AL ...
  • Linux 配置本地源 (Ubuntu / CentOS) LInux使用ISO鏡像配置本地源,離線安裝軟體包 ...
  • ● 操作系統做什麼 1. 電腦系統有4個組成部分:電腦硬體,操作系統,系統程式與應用程式和用戶。 2. 什麼是操作系統?(不同觀點) 控製程序 操作系統控制和協調不同用戶的各種應用程式之間的硬體使用。(操作系統是管理電腦硬體的程式,為應用程式提供基礎,充當電腦硬體和電腦用戶的中介) 資源管 ...
  • 問題重述: 最近環境中需要使用 fedora 28 來進行遠程桌面,於是就重新配置了一下vnc 在這裡面做一下記錄。 過程: yum 安裝: 工作機上安裝 vncviewer 遠程主機上安裝: vncserver 連接遠程桌面: 連接遠程桌面很簡單,只要使用 vncviewer 主機名:桌面號 畫面 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...