Teknologian jakaminen

Linuxin moniprosessi ja monisäikeistys (8) Multi-threading

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Monisäikeinen

Säikeen määritelmä

Säie on suoritusyksikkö prosessissa.

Vastaa nykyisen prosessin ohjelmien suorittamisesta,

Prosessissa on vähintään yksi säie

Prosessissa voi olla useita säikeitä

Useat säikeet jakavat kaikki saman prosessin resurssit, ja jokainen säie osallistuu käyttöjärjestelmän yhtenäiseen ajoitukseen

Se voidaan ymmärtää yksinkertaisesti prosessina = muistiresurssit + pääsäie + lapsisäie +…

Säikeet ja prosessit

Läheisesti toisiinsa liittyvien tehtävien osalta usean säikeen käyttöä suositellaan samanaikaisuuden aikana. Tehtäville, jotka eivät liity toisiinsa ja jotka ovat suhteellisen riippumattomia, on suositeltavaa valita moniprosessi.

  • Prosessi: Käyttöjärjestelmän resurssien allokoinnin perusyksikkö, ohjelman suoritus- ja ajoitusyksikkö sekä ohjelman käynnissä oleva esiintymä.
  • Säie: Se on suorittimen ajoituksen ja lähettämisen perusyksikkö, suorittimen suorituksen pienin yksikkö, ohjelman suoritusvirran pienin yksikkö ja ohjelman suorittamisen pienin yksikkö.

Ero säikeiden ja prosessien välillä:

  • muistitilaa
    • Useat säikeet prosessissa jakavat saman muistitilan
    • Useilla prosesseilla on itsenäinen muistitila
  • Kommunikaatio prosessien/säikeiden välillä
    • Yksinkertainen viestintä säikeiden välillä
    • Prosessien välinen viestintä on monimutkaista

Viestiketjun resurssit

  • Jaetut prosessiresurssit
    • sama osoiteavaruus
    • tiedostokuvaustaulukko
    • Miten jokainen signaali käsitellään
    • nykyinen työhakemisto
    • käyttäjätunnus ja ryhmätunnus
  • Ainutlaatuiset resurssit
    • Lankapino
    • Jokaisella säikeellä on yksityiset kontekstitiedot
    • Säikeen tunnus
    • Rekisteriarvo
    • errno arvo
    • Signaalimaskin sanat ja aikataulutusprioriteetit

Viestiketjuun liittyvät komennot

Prosessin katselemiseen Linux-järjestelmässä on monia komentoja, mukaan lukien pidstat, top, ps, voit tarkastella prosessia, voit myös tarkastella
lanka käsittelyssä

pidstat-komento

Kun sysstat-työkalu on asennettava ubuntuun, pidstat voidaan tukea.

sudo apt install sysstat

Vaihtoehdot

-t: Näytä määritettyyn prosessiin liittyvät säikeet

-p: Määritä prosessin pid

Esimerkki

Näytä prosessiin 12345 liittyvät säikeet

sudo pidstat -t -p 12345

Näytä kaikkiin prosesseihin liittyvät säikeet

sudo pidstat -t

Näytä prosessiin 12345 liittyvät säikeet, tulostetaan sekunnin välein

sudo pidstat -t -p 12345 1

Näytä kaikkiin prosesseihin liittyvät säikeet, tulostetaan 1 sekunnin välein

sudo pidstat -t 1

ylin komento

Käytä yläkomentoa tarkastellaksesi tietyn prosessin säikeitä Sinun on käytettävä -H-vaihtoehtoa yhdessä -p:n kanssa määrittääksesi pid.

Vaihtoehdot

-H: Näytä lankatiedot

-p: Määritä prosessin pid

Esimerkki

Näytä prosessiin 12345 liittyvät säikeet

sudo top -H -p 12345

Näytä kaikkiin prosesseihin liittyvät säikeet

sudo top -H

ps-komento

Käytä ps-komentoa yhdessä -T-vaihtoehdon kanssa nähdäksesi kaikki prosessin säikeet

Vaihtoehdot

-T: Näytä lankatiedot

-p: Määritä prosessin pid

Esimerkki

Näytä prosessiin 12345 liittyvät säikeet

sudo ps -T -p 12345

Näytä kaikkiin prosesseihin liittyvät säikeet

sudo ps -T

Yleiset samanaikaisuusskenaariot

1. Moniprosessitila

Moniprosessitilassa jokainen prosessi on vastuussa erilaisista tehtävistä häiritsemättä toisiaan. Jokainen prosessi toimii eri muistitilassa vaikuttamatta toisiinsa.

  • etu:
    • Prosessin osoiteavaruus on riippumaton Kun prosessissa tapahtuu poikkeus, se ei vaikuta muihin prosesseihin.
  • puute:
    • Jokaiselle prosessille on varattava itsenäinen muistitila Prosessin luominen on kallista ja vie enemmän muistia.
    • Prosessien välinen yhteistyö ja prosessien välinen viestintä ovat monimutkaisempia
  • Sovellettava kohtaus:
    • Useat tehtävät eivät liity kovin läheisesti toisiinsa, joten moniprosessitilaa voidaan käyttää
    • Tehtävien välillä ei ole riippuvuuksia, ja moniprosessitilaa voidaan käyttää

2. Monisäikeinen tila

Monisäikeisessä tilassa prosessissa voi olla useita säikeitä, jotka jakavat saman muistitilan, ja säikeet voivat kommunikoida suoraan.

  • etu:
    • Viestintä säikeiden välillä on yksinkertaista
    • Saman prosessin useat säikeet voivat jakaa resursseja ja parantaa resurssien käyttöä.
  • puute:
    • Säikeillä ei ole itsenäisiä prosessiosoiteavaruuksia Pääsäikeen poistuttua myös muut säikeet.
    • Säikeiden vaihtaminen ja ajoittaminen vaativat resursseja Liian monet säikeet kuluttavat järjestelmän resursseja.
    • Synkronointi säikeiden välillä on monimutkaista, ja lankojen turvallisuuskysymykset on otettava huomioon
  • Sovellettava kohtaus:
    • Tehtävien välillä on riippuvuuksia, ja monisäikeistä tilaa voidaan käyttää
    • Viestintä tehtävien välillä on suhteellisen tiheää ja monisäikeistä tilaa voidaan käyttää.

Luo lanka

1. pthread_create()

pthread_create()-komentoa käytetään säikeen luomiseen Kun luominen on onnistunut, säie alkaa toimia.
Kun pthread_create() on kutsuttu onnistuneesti, se palauttaa arvon 0, muuten se palauttaa virhekoodin.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
  • 1
  • 2
  • 3
  • 4

Parametrin kuvaus:

  • säie: Osoitin pthread_t-tyyppiin, jota käytetään säikeen tunnuksen tallentamiseen.
  • attr: Thread-attribuutti, joka voi olla NULL, mikä osoittaa, että oletusattribuuttia käytetään.
  • start_routine: Säikeen syöttötoiminto.
  • arg: Säikeensyöttöfunktioon välitetyt parametrit.

palautusarvo:

  • 0: Luotu onnistuneesti.
  • UUDELLEEN: Riittämättömät resurssit, ketjun luominen epäonnistui.
  • EINVAL: Parametri on virheellinen.
  • ENOMEM: Muisti ei riitä, säikeen luominen epäonnistui.

Ilmoitus:

  • Kun lapsisäike on luotu onnistuneesti, se ajoitetaan itsenäisesti suoritettavaksi ja suoritetaan samanaikaisesti muiden säikeiden kanssa.
  • -lpthread-kirjasto on linkitettävä käännettäessä.

Esimerkki: Luo lanka

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("%sn",(char *)arg);
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //@param tid 线程ID
    //@param attr 线程属性
    //@param start_routine 线程函数
    //@param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }
    sleep(1); // 等待线程执行完毕
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2. pthread_exit() sulkee säikeen

pthread_exit():tä käytetään säikeestä poistumiseen Kun säiettä on suoritettu loppuun, pthread_exit() kutsutaan automaattisesti poistumaan.

Funktiootsikkotiedosto:

#include <pthread.h>

void pthread_exit(void *retval);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • retval: Arvo, joka palautetaan säikeen poistuessa.
  • Kun säietoiminto on suoritettu, pthread_exit() kutsutaan automaattisesti poistumaan.

3. pthread_join() odottaa säikeen päättymistä

pthread_join() käytetään odottamaan säikeen päättymistä,
Kun pthread_join() on kutsuttu, nykyinen säie estetään, kunnes säie päättyy.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • lanka: Säie ID.
  • retval: Osoitin säikeen palautusarvoon, jota käytetään säikeen poistuessa palautetun arvon tallentamiseen. (toissijainen osoitin)

palautusarvo:

  • 0: Odota menestystä.
  • EINVAL: Parametri on virheellinen.
  • ESRCH: Säietunnusta ei ole olemassa.
  • EDEADLK: Lanka on umpikujassa.

Esimerkki:

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, NULL);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
等待线程结束...
Hello, World!
  • 1
  • 2

Lankojen erotus

Langat jaetaan yhdistettäviin ja irrotettaviin

  • Voidaan yhdistää
    • Yhdistettävä säie voi vaatia takaisin resurssejaan, ja muut säikeet tappavat sen muistiresursseja (kuten pinoa) vasta, kun muut säikeet ottavat sen takaisin.
    • Säikeen luomisen oletustila on yhdistettävissä. Muut säikeet voivat kutsua pthread_join-funktiota odottamaan, että aliketju poistuu ja siihen liittyvät resurssit vapautuvat.
  • erotettavissa
    • Muut säikeet eivät voi kierrättää tai tappaa säikeen resursseja, kun se päättyy.
// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    //pthread_join(tid, NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行

    //线程分离
    pthread_detach(tid); //! 分离线程,不用等待线程结束后才退出程序,该线程的资源在它终止时由系统来释放。

    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

Luo useita säikeitä

Esimerkki 1: Luo useita säikeitä suorittaaksesi erilaisia ​​tehtäviä

// todo : 创建多个线程,执行不同的任务
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello_A(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}
// 线程函数
//@param arg 线程函数参数
void * print_hello_B(void *arg) {
    sleep(2); // 休眠2秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}


int main() {
    pthread_t tidA; //? 存储线程ID  typedef unsigned long int pthread_t;
    pthread_t tidB;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int retA = pthread_create(&tidA, NULL,print_hello_A, "A_ Hello, World!");
    if (retA!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    int retB = pthread_create(&tidB, NULL,print_hello_B, "B_ Hello, World!");
    if (retB!= 0){
        printf("pthread_create error!n");
        return 1;
    }
    
    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tidA, NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tidB, NULL);
    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

Esimerkki 2: Luo useita säikeitä suorittaaksesi saman tehtävän

// todo : 创建多个线程,执行相同任务
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//? 两个线程执行相同任务,对函数中的值修改了,会不会影响到其他线程的执行?
//! 在多线程编程中,如果多个线程执行相同的任务并且对共享资源进行修改,可能会影响到其他线程的执行。
//! 这是因为多个线程共享相同的内存空间,对共享资源的修改可能会导致竞态条件(race condition),
//! 从而导致不可预测的行为。
//! print_hello函数中的变量i是局部变量,每个线程都会有自己的i副本,因此对i的修改不会影响到其他线程。
//! 但是,如果涉及到共享资源(例如全局变量或静态变量),就需要考虑线程同步的问题,以避免竞态条件。


//*局部变量:每个线程都有自己的栈空间,因此局部变量是线程私有的,不会影响到其他线程。
//*共享资源:如果多个线程访问和修改同一个全局变量或静态变量,就需要使用同步机制(如互斥锁、信号量等)来确保线程安全。
//Linux:在Linux系统中,默认的线程栈大小通常是8MB。可以使用ulimit -s命令查看和修改当前用户的线程栈大小。例如,ulimit -s 1024将线程栈大小设置为1MB。
//Windows:在Windows系统中,默认的线程栈大小是1MB。可以通过编译器选项或在创建线程时指定栈大小来修改。

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {

    for (char i = 'a'; i < 'z' ; ++i) {
        printf("%cn", i);
        sleep(1); // 休眠1秒
    }
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid[2]={0}; //? 存储线程ID的数组  typedef unsigned long int pthread_t;


    for (int i = 0; i < 2; ++i) {
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,print_hello, NULL);
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0], NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1], NULL);


    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

Viestintä säikeiden välillä

Muu prosessien välinen viestintä koskee myös säikeiden välistä viestintää.

Pääsäie välittää parametrit lapsisäikeeseen

Kun alasäike luodaan pthread_create()-funktion kautta, pthread_create()-funktion neljäs parametri on alasäikeen funktiolle välitetty parametri.

Alasäike välittää parametrit pääsäikeelle

Kun poistut lapsisäikeestä pthread_exit()-funktion kautta, voit välittää parametreja pääsäikeeseen.

void pth_exit(void *retval);
  • 1

Kun odotat aliketjun loppua pthread_join()-funktion kautta, hanki aliketjun palautusparametrit.

int pthread_join (pthread_t __th, void **__thread_return);
//二级指针获取到了pthread_exit()函数参数指针的指向地址,通过该地址可以获取到子线程的返回参数。
  • 1
  • 2

Esimerkki:

// todo : 线程直接通讯,子线程向父线程传参
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("子线程开始,结束之时传递参数100的地址n");

    sleep(1); // 休眠1秒
    //! int num=100;//局部变量,函数结束释放内存
    static int num=100;//* 静态局部变量,函数结束不释放内存,延长生命周期
    pthread_exit(&num); // 线程退出
}



int main() {
    pthread_t tid; //? 存储线程ID  typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int retA = pthread_create(&tid, NULL,print_hello, NULL);
    if (retA!= 0){
        printf("pthread_create error!n");
        return 1;
    }



    printf("等待线程结束...n");
    void* num;//获取子进程传递的参数,num指向了子进程传递的参数
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, (void **)&num);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    printf("子线程结束,传递的参数为%dn",*(int*)num);
    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

Lanka mutex

Lanka mutex

Mutex on synkronointimekanismi, jota käytetään hallitsemaan pääsyä jaettuihin resursseihin.

Säikeiden tärkein etu on kyky jakaa tietoa globaalien muuttujien kautta, mutta tämä kätevä jakaminen maksaa:

On varmistettava, että useat säikeet eivät muokkaa samaa muuttujaa samanaikaisesti

Säie ei lue muuttujia, joita muut säikeet muokkaavat. Itse asiassa kaksi säiettä ei pääse kriittiseen osaan samanaikaisesti.

Mutex-lukon periaate

Mutex-lukon periaate on, että kun lanka yrittää päästä mutex-alueelle, jos mutex-alue on jo muiden säikeiden varassa, lanka estetään, kunnes mutex-alue vapautetaan.

Se on pohjimmiltaan muuttuja tyyppiä pthread_mutex_t, joka sisältää kokonaislukuarvon edustamaan mutex-alueen tilaa.
Kun arvo on 1, se tarkoittaa, että nykyinen kriittinen resurssi voi kilpailla pääsystä ja mutex-lukon saava säie voi päästä kriittiseen osaan. Tällä hetkellä arvo on 0 ja muut säikeet voivat vain odottaa.
Kun arvo on 0, se tarkoittaa, että nykyinen kriittinen resurssi on muiden säikeiden varaama, eikä se voi päästä kriittiseen osioon vaan voi vain odottaa.

Mutex-lukkojen ominaisuudet

typedef union
{
  struct __pthread_mutex_s __data; // 互斥锁的结构体
  char __size[__SIZEOF_PTHREAD_MUTEX_T];// 互斥锁的大小
  long int __align;// 互斥锁的对齐
} pthread_mutex_t;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • Mutex-lukko on pthread_mutex_t-tyyppinen muuttuja, joka edustaa mutex-lukkoa.
  • Jos kaksi säiettä käyttää samaa pthread_mutex_t-muuttujaa, ne käyttävät samaa mutex-lukkoa
  • Vastaavat muuttujat määritellään pthreadtypes.h otsikkotiedostossa, joka on yleinen runko, joka sisältää rakenteen.

Mutex-lukkojen käyttö

On kaksi päätapaa aloittaa säikeen mutex-lukot:

staattinen alustus

  • Määritä muuttuja tyyppiä pthread_mutex_t ja alusta se muotoon PTHREAD_MUTEX_INITIALIZER.
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
  • 1

dynaaminen alustus

Dynaaminen alustus Dynaaminen alustus sisältää pääasiassa kaksi toimintoa: pthread_mutex_init-funktio ja pthread_mutex_destroy-funktio

pthread_mutex_init()-funktio

Käytetään mutex-lukon alustamiseen, ja se hyväksyy kaksi parametria: mutex-lukon osoitteen ja mutex-lukon attribuutit.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • mutex: Osoitin pthread_mutex_t-tyyppiin, jota käytetään tallentamaan mutex-lukon osoite.
  • attr: mutex-lukon attribuutti, joka voi olla NULL, mikä osoittaa, että oletusattribuuttia käytetään.

palautusarvo:

  • 0: Alustus onnistui.
  • Virhekoodi palautetaan epäonnistuessa.
pthread_mutex_destroy()-funktio

Käytetään tuhoamaan mutex-lukon, se hyväksyy yhden parametrin: mutex-lukon osoitteen.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • mutex: Osoitin pthread_mutex_t-tyyppiin, jota käytetään tallentamaan mutex-lukon osoite.

palautusarvo:

  • 0: Tuhoaminen onnistui.
  • Virhekoodi palautetaan epäonnistuessa.

Esimerkki:

// todo :  互斥锁;创建两个线程,分别对全局变量进⾏ +1 操作
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>


static int global = 0;// 全局变量

//静态初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
//动态初始化互斥锁
pthread_mutex_t mut;// 互斥锁

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("子线程开始n");

    int loops = *(int *)arg;
    int i,tmp = 0;
    for (i = 0;i < loops;i++){
        pthread_mutex_lock(&mut);// 加锁
        printf("子线程%d,global=%dn",i,global);
        tmp = global;
        tmp++;
        global = tmp;
        pthread_mutex_unlock(&mut);// 解锁
    }
    printf("子线程结束n");
    pthread_exit(NULL); // 线程退出
}



int main() {

    // 动态初始化互斥锁
    //* @param mutex 互斥锁
    //* @param attr 互斥锁属性 NULL 是默认属性
    int r= pthread_mutex_init(&mut,NULL);
    if (r!= 0){
        printf("pthread_mutex_init error!n");
        return 1;
    }

    pthread_t tid[2]={0}; //? 存储线程ID  typedef unsigned long int pthread_t;
    int arg=20;
    for (int i = 0; i < 2; i++){
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,print_hello, &arg);
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }




    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0],NULL );//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1],NULL );

    printf("%dn",global);
    printf("主线程结束n");

    // 销毁动态创建的互斥锁
    //* @param mutex 互斥锁
    pthread_mutex_destroy(&mut);// 销毁互斥锁

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

Säikeen synkronointi

Säikeen synkronointi: tarkoittaa vierailijoiden hallittua pääsyä resursseihin muiden mekanismien kautta molemminpuolisen poissulkemisen perusteella (useimmissa tapauksissa).

Ehtomuuttuja: Säiekirjaston tarjoama mekanismi erityisesti säikeen synkronointia varten.

Tyypillinen sovellusskenaario säikeiden synkronointiin on tuottajien ja kuluttajien välillä.

Tuottaja- ja kuluttajakysymykset

Tässä mallissa se on jaettu tuottajasäikeeseen ja kuluttajasäikeeseen, ja useiden säikeiden synkronointiprosessia simuloidaan tämän säikeen kautta.

Tässä mallissa tarvitaan seuraavat komponentit:

  • Varasto: käytetään tuotteiden varastointiin, yleensä yhteisenä resurssina
  • Tuottajan lanka: käytetään tuotteiden valmistukseen
  • Kuluttajalanka: käytetään tuotteiden kulutukseen

periaate:

Kun tuotetta ei ole varastossa, kuluttajalangan on odotettava, kunnes tuote on olemassa, ennen kuin se voi kuluttaa sen.

Kun varasto on täynnä tuotteita, tuottajalangan on odotettava, kunnes kuluttajalanka kuluttaa tuotteet.

Esimerkki mutex-lukkoihin perustuvien tuottaja- ja kuluttajamallien toteuttamisesta

Pääketju on kuluttaja

n alasäiettä tuottajina

// todo :  基于互斥锁实现⽣产者与消费者模型主线程为消费者,n 个⼦线程作为⽣产者
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
static int n = 0; // 产品数量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁

//生产者执行函数
void * dofunc(void *arg) {
    int arg1 = *(int*)arg;
    for (int i = 0; i <arg1; i++) {
        //获取互斥锁
        pthread_mutex_lock(&mutex);
        //生产产品
        printf("生产者%ld生产了%d个产品n",pthread_self(),++n);//! pthread_self()返回当前线程ID
        //释放互斥锁
        pthread_mutex_unlock(&mutex);
        //休眠1秒
        sleep(1);
    }
    pthread_exit(NULL);
}


int main() {
    pthread_t tid[4]={0}; //? 存储线程ID  typedef unsigned long int pthread_t;
    int arr[4]={1,2,3,4};
    for (int i = 0; i < 4; i++) {
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,dofunc,&arr[i] );
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }
    //消费者执行

    for (int i = 0;i<10;i++) {
        //获取互斥锁
        pthread_mutex_lock(&mutex);
        while (n > 0){
            //消费产品
            printf("消费者%ld消费了1个产品:%dn",pthread_self(),n--);
        }
        //释放互斥锁
        pthread_mutex_unlock(&mutex);
        //休眠1秒
        sleep(1);
    }


    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0],NULL );//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1],NULL );
    pthread_join(tid[2],NULL );
    pthread_join(tid[3],NULL );

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

ehtomuuttuja

Ehtomuuttuja on synkronointimekanismi, jonka avulla säiettä odottaa tietyn ehdon täyttymistä ennen kuin se jatkaa ajamista.

Ehtomuuttujan periaate on, että se sisältää mutex-lukon ja odotusjonon.

Mutex-lukkoja käytetään suojaamaan odotusjonoja ja ehtomuuttujia.

Lisää kuvan kuvaus tähän

Ehtomuuttujan alustus

Ehtomuuttujan luonne on pthread_cond_t-tyyppiä

其他线程可以阻塞在这个条件变量上, 或者唤
醒阻塞在这个条件变量上的线程
typedef union
{
  struct __pthread_cond_s __data;
  char __size[__SIZEOF_PTHREAD_COND_T];
  __extension__ long long int __align;
} pthread_cond_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Ehtomuuttujien alustus on jaettu staattiseen ja dynaamiseen alustukseen.

staattinen alustus

Staattisesti alustettuja ehtomuuttujia varten sinun on ensin määritettävä muuttuja, jonka tyyppi on pthread_cond_t, ja alustettava se sitten muotoon PTHREAD_COND_INITIALIZER.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 1

Dynaaminen alustus pthread_cond_init()

Ehtomuuttujan alustamiseksi dynaamisesti sinun on ensin määritettävä muuttuja, jonka tyyppi on pthread_cond_t, ja kutsuttava sitten pthread_cond_init-funktiota sen alustamiseksi.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • cond: Osoitin pthread_cond_t-tyyppiin, jota käytetään ehtomuuttujan osoitteen tallentamiseen.
  • attr: ehtomuuttujan attribuutti, joka voi olla NULL oletusattribuutin käyttämiseksi.

palautusarvo:

  • 0: Alustus onnistui.
  • Virhekoodi palautetaan epäonnistuessa.

pthread_cond_destroy()

Sitä käytetään ehtomuuttujien tuhoamiseen, ja se hyväksyy yhden parametrin: ehtomuuttujan osoitteen.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • cond: Osoitin pthread_cond_t-tyyppiin, jota käytetään ehtomuuttujan osoitteen tallentamiseen.

palautusarvo:

  • 0: Tuhoaminen onnistui.
  • Virhekoodi palautetaan epäonnistuessa.

Ehtomuuttujien käyttö

Ehtomuuttujien käyttö jakautuu odottamiseen ja ilmoittamiseen

Odota pthread_cond_wait()

Odotusfunktio pthread_cond_wait() hyväksyy kolme parametria: ehtomuuttujan osoitteen, mutex-lukon osoitteen ja odotusajan.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • cond: Osoitin pthread_cond_t-tyyppiin, jota käytetään ehtomuuttujan osoitteen tallentamiseen.
  • mutex: Osoitin pthread_mutex_t-tyyppiin, jota käytetään tallentamaan mutex-lukon osoite.
  • abstime: aikakatkaisu, joka voi olla NULL, mikä tarkoittaa, ettei aikakatkaisua ole.

palautusarvo:

  • 0: Odota menestystä.
  • Virhekoodi palautetaan epäonnistuessa.

Ilmoita pthread_cond_signal()

ilmoitustoiminto
pthread_cond_signal() hyväksyy yhden parametrin: ehtomuuttujan osoitteen.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • cond: Osoitin pthread_cond_t-tyyppiin, jota käytetään ehtomuuttujan osoitteen tallentamiseen.

palautusarvo:

  • 0: Ilmoitus onnistui.
  • Virhekoodi palautetaan epäonnistuessa.

Ilmoita kaikille pthread_cond_broadcast()

Ilmoita kaikki toiminnot
pthread_cond_broadcast() hyväksyy yhden parametrin: ehtomuuttujan osoitteen.

Funktiootsikkotiedosto:

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Parametrin kuvaus:

  • cond: Osoitin pthread_cond_t-tyyppiin, jota käytetään ehtomuuttujan osoitteen tallentamiseen.

palautusarvo:

  • 0: Ilmoitus onnistui.
  • Virhekoodi palautetaan epäonnistuessa.

Esimerkki ehdollisiin muuttujiin perustuvien tuottaja- ja kuluttajamallien toteuttamisesta

Lisää kuvan kuvaus tähän

step 1 : 消费者线程判断消费条件是否满足 (仓库是否有产品),如果有产品可以消费,则可以正
常消费产品,然后解锁
step 2 : 当条件不能满足时 (仓库产品数量为 0),则调用 pthread_cond_wait 函数, 这个函数
            具体做的事情如下:
            在线程睡眠之前,对互斥锁解锁
            让线程进⼊到睡眠状态
            等待条件变量收到信号时 唤醒,该函数重新竞争锁,并获取锁后,函数返回 
step 3 :重新判断条件是否满足, 如果不满足,则继续调用 pthread_cond_wait 函数
step 4 : 唤醒后,从 pthread_cond_wait 返回,消费条件满足,则正常消费产品
step 5 : 释放锁,整个过程结束
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Miksi ehtomuuttujia on käytettävä yhdessä mutex-lukkojen kanssa?

Suojaa jaetut tiedot:

Mutex-lukot suojaavat jaetut tiedot ja varmistavat, että vain yksi säie pääsee käsiksi ja muokata tietoja samanaikaisesti.

Tämä välttää datakilpailut ja epäjohdonmukaisuudet.

Ehtomuuttujia käytetään viestinnän säikeiden välillä ilmoittamaan muille säikeille, että tietty ehto on täytetty.

Ehtomuuttujien toiminta ei kuitenkaan itsessään tarjoa suojaa jaetulle tiedolle, ja siksi sitä on käytettävä yhdessä mutex-lukon kanssa.

Vältä vääriä herätyksiä:

Eräs ehtomuuttujien ominaisuus on se, että voi tapahtua väärä herätys.

Eli lanka herää ilman erillistä ilmoitusta. Tämän tilanteen aiheuttamien virheellisten toimintojen välttämiseksi,

Langan on tarkistettava uudelleen, täyttyykö ehto heräämisen jälkeen.

Muteksin käyttäminen varmistaa, että muut säikeet eivät muokkaa jaettua dataa olosuhteiden tarkistuksen aikana, jolloin vältetään väärien herätysten aiheuttamat virheet.

Varmista, että ilmoitus on oikea:

Kun säie ilmoittaa muille säikeille ehtomuuttujien kautta, sen on varmistettava, että jaetut tiedot on päivitetty ennen ilmoitusta.

Mutex takaa tämän varmistaen, että kaikki tietojen päivitystoiminnot on suoritettu ennen lukon vapauttamista.

Samoin ilmoituksen vastaanottavan säikeen on säilytettävä mutex ennen ehdon tarkistamista varmistaakseen, että tiedot ovat stabiileja, kun ehto tarkistetaan.

Ota käyttöön monimutkaisia ​​synkronointimalleja:
Yhdistämällä mutex-lukot ehtomuuttujiin voidaan toteuttaa monimutkaisempia synkronointimalleja, kuten tuottaja-kuluttaja-ongelmia, lukija-kirjoitusongelmia jne. Mutexet suojaavat jaettua dataa, ja ehtomuuttujia käytetään säikeiden väliseen koordinointiin ja viestintään.

// todo :  条件变量
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>


static int number = 0;// 产品数量
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 条件变量

// 线程函数
//@param arg 线程函数参数
void * thread_handler(void *arg) {
    int cnt = atoi((char *)arg);// 获取线程参数
    int i,tmp;// 临时变量
    for(i = 0;i < cnt;i++){// 生产产品
        pthread_mutex_lock(&mtx);// 上锁
        printf("线程 [%ld] ⽣产⼀个产品,产品数量为:%dn",pthread_self(),++number);
        pthread_mutex_unlock(&mtx);// 解锁

        //! 唤醒cond阻塞的线程
        //! @param cond 条件变量
        //pthread_cond_signal(&cond);//! 只能唤醒一个线程,如果有多个线程在等待,则只有一个线程会被唤醒
        //唤醒所有线程
        pthread_cond_broadcast(&cond);
    }
    pthread_exit((void *)0);// 线程退出
}



int main(int argc,char *argv[]) {

    pthread_t tid[argc-1];// 线程ID
    int i;
    int err;
    int total_of_produce = 0;// 总共生产的产品数量
    int total_of_consume = 0;// 总共消费的产品数量
    bool done = false;// 是否完成生产
    //循环创建线程
    for (i = 1;i < argc;i++){
        total_of_produce += atoi(argv[i]);// 计算总共需要生产的产品数量
        // 创建线程
        err = pthread_create(&tid[i-1],NULL,thread_handler,(void *)argv[i]);
        if (err != 0){
            perror("[ERROR] pthread_create(): ");
            exit(EXIT_FAILURE);
        }
    }
    //消费者
    for (;;){
        //*先获取锁,再进行条件变量的等待
        pthread_mutex_lock(&mtx);// 上锁

        //*while循环来判断条件,避免虚假唤醒
        while(number == 0) {// 等待生产者生产产品
            //! 等待条件变量
            //! @param cond 条件变量
            //! @param mtx 互斥锁
            //! 函数中会释放互斥锁,并阻塞线程,
            //! 直到条件变量被唤醒,再重新竞争互斥锁,获取互斥锁并继续执行
            pthread_cond_wait(&cond, &mtx);
        }
        while(number > 0){
            total_of_consume++;// 总共消费的产品数量
            printf("消费⼀个产品,产品数量为:%dn",--number);// 消费产品
            done = total_of_consume >= total_of_produce;// 是否完成生产
        }
        pthread_mutex_unlock(&mtx);// 解锁

        if (done)// 是否完成生产
            break;
    }

    // 等待线程退出
    for(i = 0;i < argc-1;i++){
        pthread_join(tid[i],NULL);
    }

    return 0;

}
    //循环创建线程
    for (i = 1;i < argc;i++){
        total_of_produce += atoi(argv[i]);// 计算总共需要生产的产品数量
        // 创建线程
        err = pthread_create(&tid[i-1],NULL,thread_handler,(void *)argv[i]);
        if (err != 0){
            perror("[ERROR] pthread_create(): ");
            exit(EXIT_FAILURE);
        }
    }
    //消费者
    for (;;){
        //*先获取锁,再进行条件变量的等待
        pthread_mutex_lock(&mtx);// 上锁

        //*while循环来判断条件,避免虚假唤醒
        while(number == 0) {// 等待生产者生产产品
            //! 等待条件变量
            //! @param cond 条件变量
            //! @param mtx 互斥锁
            //! 函数中会释放互斥锁,并阻塞线程,
            //! 直到条件变量被唤醒,再重新竞争互斥锁,获取互斥锁并继续执行
            pthread_cond_wait(&cond, &mtx);
        }
        while(number > 0){
            total_of_consume++;// 总共消费的产品数量
            printf("消费⼀个产品,产品数量为:%dn",--number);// 消费产品
            done = total_of_consume >= total_of_produce;// 是否完成生产
        }
        pthread_mutex_unlock(&mtx);// 解锁

        if (done)// 是否完成生产
            break;
    }

    // 等待线程退出
    for(i = 0;i < argc-1;i++){
        pthread_join(tid[i],NULL);
    }

    return 0;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127