Linux多線程_i++

@TOC


📝進程和線程

  • 進程是資源分配的基本單位
  • 線程是調度的基本單位
  • 線程共享進程數據,但也擁有⾃⼰的⼀部分數據:
  • 線程ID
  • 一組寄存器
  • errno
  • 信號屏蔽字
  • 調度優先級

🌠 進程的多個線程共享

同⼀地址空間,因此TextSegmentDataSegment都是共享的,如果定義⼀個函數,在各線程中都可以調⽤,如果定義⼀個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:

  • 文件描述符表
  • 每種信號的處理方式(SIG_IGN、SIG_ DFL或者自定義的信號處理函數)
  • 當前工作目錄
  • 用户id和組id

進程和線程的關係如下圖:

Linux多線程_全局變量_02

🌉關於進程線程的問題

linux如何看待之前學習的單進程?具有⼀個線程執⾏流的進程

在Linux中,單進程是資源分配基本單位,有獨立內存與CPU時間片,由PCB管理。其指令順序執行,阻塞操作會致進程暫停。單進程難以利用多核並行,實現併發受限。它獨立性高,崩潰不影響其他進程,但容錯性差,邏輯簡單利於調試維護。

🌠Linux線程控制

🌉POSIX線程庫

  • 與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的
  • 要使用這些函數庫,要通過引入頭文<pthread .h>
  • 鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項

🌠創建線程

功能:創建一個新的線程原型:

int pthr/ead_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void*), void *arg);
參數:
thread:返回線程ID
attr:設置線程的屬性,attr為NULL表示使用默認屬性start_routine:是個函數地址,線程啓動後要執行的函數arg:傳給線程啓動函數的參數
返回值:成功返回0;失敗返回錯誤碼

錯誤檢查:

  • 傳統的⼀些函數是,成功返回0,失敗返回-1,並且對全局變量errno賦值以指⽰錯誤。pthreads函數出錯時不會設置全局變量errno(⽽⼤部分其他POSIX函數會這樣做)。⽽是將錯誤代碼通過返回值返回
  • pthreads同樣也提供了線程內的errno變量,以⽀持其它使⽤errno的代碼。對於pthreads函數的錯誤,建議通過返回值業判定,因為讀取返回值要⽐讀取線程內的errno變量的開銷更⼩
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
void *rout(void *arg)
{
    int i;
    for (;;)
    {
        printf("I'am thread 1\n");
        sleep(1);
    }
}
int main(void)
{
    pthread_t tid;
    int ret;
    if ((ret = pthread_create(&tid, NULL, rout, NULL)) != 0)
    {
        fprintf(stderr, "pthread_create : %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }
    int i;
    for (;;)
    {
        printf("I'am main thread\n");
        sleep(1);
    }
}
#include <pthread .h>
//獲取線程ID
pthread_t pthread_self(void);

打印出來的tid是通過pthread庫中有函數pthread_self 得到的,它返回一個pthread_t類型的變量,指代的是調用pthread_self 函數的線程的“ID”。

怎麼理解這個“ID”呢?這個“ID”是pthread庫給每個線程定義的進程內唯一標識,是pthread庫維持的。

由於每個進程有自己獨立的內存空間,故此“ID”的作用域是進程級而非系統級(內核不認識)。

其實pthread庫也是通過內核提供的系統調用(例如clone)來創建線程的,而內核會為每個線程創建系統全局唯一的“ID”來唯一標識這個線程。

使⽤PS命令查看線程信息 運⾏代碼後執⾏:

$ ps -aL | head -1 && ps -aL \ grep mythreadPID LWP TTY
T工ME CMD
2711838 2711838 pts/ 23500:00:00 mythread
2711838 2711839 pts/23500:00:00  mythread
-L選項:打印線程信息

LWP是什麼呢?LWP得到的是真正的線程ID。之前使用pthread_self得到的這個數實際上是一個地址,在虛擬地址空間上的一個地址,通過這個地址,可以找到關於這個線程的基本信息,包括線程ID,線程棧,寄存器等屬性。

在ps -aL 得到的線程ID,有一個線程ID和進程ID相同,這個線程就是主線程,主線程的棧在虛擬地址空間的棧上,而其他線程的棧在是在共享區(堆棧之間),因為pthread系列函數都是pthread庫提供給我們的。而pthread庫是在共享區的。所以除了主線程之外的其他線程的棧都在共享區。

🌉線程終⽌

如果需要只終⽌某個線程⽽不終⽌整個進程,可以有三種⽅法:

  1. 從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit
  2. .線程可以調用pthread_ exit終止自己。
  3. 一個線程可以調用pthread_ cancel終止同一進程中的另一個線程。
pthread_exit函數
功能:線程終⽌
原型:
 	void pthread_exit(void *value_ptr);
 	參數:
 	value_ptr:value_ptr
不要指向⼀個局部變量。
返回值:
	⽆返回值,跟進程⼀樣,線程結束的時候⽆法返回到它的調⽤者(⾃⾝)

需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是⽤malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。

pthread_cancel函數
功能:取消⼀個執⾏中的線程
原型:
	 int pthread_cancel(pthread_t thread);
參數:
	 thread:線程ID
返回值:成功返回 0  ;失敗返回錯誤碼

🌉線程等待

為什麼需要線程等待?

  • 已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
  • 創建新的線程不會復⽤剛才退出線程的地址空間。
功能:等待線程結束
原型
	lint pthread_join(pthread_t thread, void **value_ptr);
參數:
	thread :線程ID
	value_ptr:它指向一個指針,後者指向線程的返回值
返回值:成功返回0;失敗返回錯誤碼

調⽤該函數的線程將掛起等待,直到id為thread的線程終⽌。thread線程以不同的⽅法終⽌,通過pthread_join得到的終⽌狀態是不同的,總結如下:

  1. 如果thread線程通過return返回,value_ptr所指向的單元裏存放的是thread線程函數的返回值。
  2. 如果thread線程被別的線程調用pthread_cancel異常終掉,value_ptr所指向的單元裏存放的是常數PTHREAD_CANCELED
  3. 如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
  4. 如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ ptr參數。
#include <stdio.h>
#include <stdlib.h>
void *thread1(void* args)
{
    printf("thread 1 returning ... \n");
    int *p = (int*)malloc(sizeof(int));
    *p = 1;
    return (void*)p;
}

void* thread2(void *args)
{
    printf("thread 2 exiting .. \n");
    int *p = (int*)malloc(sizeof(int));
    *p = 2;
    pthread_exit((void*)p);
}

void* thread3(void* args)
{
    while(1)
    {
        printf("thread 3 is running ... \n");
        sleep(1);
    }

    return nullptr;
}

int main(void)
{
    pthread_t tid;
    void *ret;

    //thread 1 return 
    pthread_create(&tid, nullptr, thread1, nullptr);
    pthread_join(tid, &ret);
    printf("thread return, thread id 0x%lX, return code:%d\n", tid, *(int*)ret);
    free(ret);

    // thread 2 exit
    pthread_create(&tid, nullptr, thread2, nullptr);
    pthread_join(tid, &ret);
    printf("thread return , thread id 0x%lX, return code:%d\n", tid, *(int*)ret);
    free(ret);

    // thread 3 cancel by other
    pthread_create(&tid, nullptr, thread3, nullptr);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if( ret == PTHREAD_CANCELED)
        printf("thread return ,thread id: 0x%lX, return code:PTHREAD_CANCELD\n", tid);
    else
        printf("thread return , thread id :0x%lX, return code: nullptr", tid);
}

Linux多線程_i++_03

Linux多線程_i++_04

Linux多線程_i++_05

🌉分離線程

默認情況下,新創建的線程是joinable的,線程退出後,需要對其進⾏pthread_join操作,否則⽆法釋放資源,從⽽造成系統泄漏。 如果不關⼼線程的返回值,join是⼀種負擔,這個時候,我們可以告訴系統,當線程退出時,⾃動釋放線程資源。

int pthread_detach(pthread_t thread);

可以是線程組內其他線程對⽬標線程進⾏分離,也可以是線程⾃⼰分離:

pthread_detach(pthread_self());

joinable和分離是衝突的,⼀個線程不能既是joinable⼜是分離的。

void* thread_run(void* args)
{
    pthread_detach(pthread_self());
    printf("%s\n", (char*)args);
    return nullptr;
}


int main()
{
    pthread_t tid;
    if(pthread_create(&tid, nullptr, thread_run, (void*)"thread1 run ...") != 0)
    {
        printf("create thread error\n");
        return 1;
    }

    int ret = 0;
    sleep(1); //很重要, 要讓線程先分離, 再等待


    if(pthread_join(tid, nullptr) == 0)
    {
        printf("pthread wait success\n");
        ret = 0;
    }
    else
    {
        printf("pthread wait failed\n");
        ret =1;
    }

    return ret;
}

Linux多線程_全局變量_06

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <thread>


//線程的局部存儲
int shared_value = 100;

std::string toHex(pthread_t tid)
{
    //4,進程內的函數,線程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

void* start(void* args)
{
    std::string name = static_cast<const char*>(args);
    sleep(1);
    while(true)
    {
        printf("I am a new thread , name: %s, shared_value: %d , &share_value: %p\n", name.c_str(), shared_value, &shared_value );
        sleep(1);
    }

    return nullptr;
}


int main()
{
    pthread_attr_t attr;

    pthread_t tid;
    pthread_create(&tid, nullptr, start, (void*)"thread-1");
    std::cout<<"I am a new thread, name: main, "<< toHex(pthread_self())
             <<", NEW thread id: " << toHex(tid) << std::endl;

    while(true)
    {
        printf("main thread ,shared_value: %d, &shared_value: %p\n", shared_value, &shared_value);
        shared_value += 10;
        sleep(1);
    }
    pthread_join(tid, nullptr);
    return 0;
}

Linux多線程_#include_07

while true; do ps -aL | head -1; ps -aL | grep mythread; sleep 1; done

Linux多線程_#include_08

//線程的局部存儲
__thread int shared_value = 100;

Linux多線程_全局變量_09

Linux多線程_#include_10

oid *start1(void *args)
{
    std::string name = static_cast<const char *>(args);
    int a = 100;
    while (true)
    {
        std::cout << name << " local val a: " << a << std::endl;
        a += 100;
        if(1000 == a)
        {
            break;
        }
        sleep(1);
    }

    return nullptr;
}

int main()
{
    // pthread_t tid1, tid2;
    pthread_t tid;
    pthread_create(&tid, nullptr, start1, (void *)"thread-1");
    //pthread_detach(tid);

    sleep(5);

    void *ret = nullptr;
    int n = pthread_join(tid, &ret); //
    std::cout << " new thread exit code: " << (long long int)ret << " , n = " << n << std::endl;

    return 0;
}

🌉創建多線程處理類

#include <queue>

class ThreadData
{
public:
    ThreadData()
    {
    }

    void Init(const std::string &name, int a, int b)
    {
        _name = name;
        _a = a;
        _b = b;
    }

    void Excute()
    {
        _result = _a + _b;
    }

    int Result() { return _result; }
    std::string Name() { return _name; }
    void SetId(pthread_t tid) { _tid = tid; }
    pthread_t Id() { return _tid; }
    int A() { return _a; }
    int B() { return _b; }

    ~ThreadData()
    {
    }

private:
    std::string _name;
    int _a;
    int _b;
    int _result;
    pthread_t _tid;
};

class outData
{
};

// 5全局變量在線程內部是共享的
int gval = 100;
std::queue<int> q;
char buffer[4096];

#define NUM 10

std::string toHex(pthread_t tid)
{
    //進程內的函數, 線程共享
    char tranbuffer[64];
    snprintf(tranbuffer, sizeof(buffer), "0x%lx", tid);
    return tranbuffer;
}

void *routine(void *args)
{
    // std::string name = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        // 3,不加保護的情況下, 顯示器文件就是共享資源!
        std::cout << "我是新線程, 我的名字是: " << td->Name()
                  << ", my tid is : " << toHex(pthread_self()) 
                  << ", 全局變量(會修改) : " << gval << std::endl;

        gval++;
        td->Excute();
        sleep(1);
        break;
    }

    //8,返回值問題:返回參數, 可以是變量, 數字, 對象!
    // 8.1 理論上, 堆空間也是共享的! 誰拿着堆空間的入口地址,誰就能訪問該堆區
    // int* p = new int(10);
    // return (void*)p; 

    return td;
}

int main()
{
    ThreadData td[NUM];
    // 準備我們要加工處理的數據
    for (int i = 0; i < NUM; i++)
    {
        char id[64];
        snprintf(id, sizeof(id), "thread-%d", i);
        td[i].Init(id, i * 10, i * 20);
    }

    // 創建多線程
    for (int i = 0; i < NUM; i++)
    {
        pthread_t id;
        pthread_create(&id, nullptr, routine, &td[i]);
        td[i].SetId(id);
    }

    //等待多個線程
    for(int i = 0; i < NUM; i++)
    {
        pthread_join(td[i].Id(), nullptr);
    }

    //彙總處理結果
    for(int i = 0; i < NUM; i++)
    {
        printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());
    }

    // 1,新線程和main線程誰先運行,不確定
    // 2,線程創建出來, 要對進程的時間片進行瓜分
    // 8,傳參問題: 傳遞參數,可以是變量,數字,對象!

    // pthread_t tid1;
    // ThreadData *td = new ThreadData("thread-1", 10, 20);
    // pthread_create(&tid1, nullptr, routine, td);

    return 0;
}

Linux多線程_#include_11

Linux多線程_i++_12

void* routine2(void* args)
{
    std::string name = static_cast<const char* >(args);
    while(true)
    {
        //3,在不加保護的情況下,顯示器文件就是共享資源!
        std::cout<< "我是新線程,我的名字是:" << name << ", my tid is : "<< toHex(pthread_self()) << ", 全局變量(只是檢測):" << gval <<std::endl;
        sleep(1);
        // 6, 線程一旦出現異常, 可能會導致其他線程全部崩潰
        // 6.1信號
        int* p = nullptr;
        *p = 100;
    }

    // return 0;
}
int main()
{
    // 
    pthread_t tid1;
    ThreadData *td = new ThreadData("thread-1", 10, 20);
    pthread_create(&tid1, nullptr, routine2, td);

    // 7,線程創建後也是 要等待和被回收的!
    // 7.1 理由: a。類似殭屍進程的問題, b, 為了知道新線程的執行結果
    ThreadData *rtd = nullptr;
    int n = pthread_join(tid1, (void**)&rtd);//我們可以保證,執行完畢,任務一定處理完了,結果變量一定已經被寫入了!
    if(n != 0 )
    {
        std::cerr << "join error: " << n << ", " << strerror(n) << std::endl;
        return 1;
    }

    std::cout<< "join suceess!, ret: " << rtd->Result() << std::endl;
    delete td;
}

Linux多線程_#include_13

🌠lamda表達式構造對象

lamda表達式構造對象也可以替換使用處理新線程方法

void* routine(void * args)
{
    std::string name = static_cast<const char *>(args);
    while(true)
    {
        std::cout<< "我是新線程 , 我的名字是: " << std::endl;
        sleep(1);
    }

    return 0;
}

int main()
{

    pthread_t tid ;
    int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    if(n != 0)
    {
        std::cout << "create thread error: " <<strerror(n) <<std::endl;
        return 1;
    }

    while(true)
    {
        std::cout << "我是main線程..." <<std::endl;
        sleep(1);
    }
    return 0;
}

Linux多線程_#include_14

while true; do ps -aL | head -1 && ps -aL | grep mythread ; sleep 1; done;

Linux多線程_#include_15

int main()
{

    std::thread t([] () {
        while(true)
        {
            std::cout<< "我是新線程, 我的名字 :new thread " << std::endl;
            sleep(1);
        }
    });

    while(true)
    {
        std::cout<< "我是main線程..." << std::endl;
        sleep(1);
    }



    return 0;
}

Linux多線程_#include_16

Linux多線程_i++_17


🚩總結