• pthread使用


    阅读Android源码的时候,发现有线程相关的东西,有点陌生了。所以重新温习一下POSIX标准的线程的几种使用方式,本文不涉及深层次原理性的解读,纯粹提供集中线程使用代码,以供熟悉API使用。

    进程与线程

    做应用开发的时候,对进程和线程的理解非常有限,只是知道进程是应用运行的基本单位,而线程是CPU任务调用的基本单位,一个进程可以包含多个线程等。随着工作逐渐接触内核之后发现,应用层面的认识在内核中有不同的根本性的解读。

    进程:如果简而言之的话(或许在面试的时候可以这么回答),进程就是一个正在运行的程序实体。进一步的解释可能要从内核角度看了,在内核看来,进程意味着一个实体,这些实体在初始化时占有或者共享一定的系统资源,在其生命周期中会对占有的资源进程维护,对共享的资源进行调度,在生命周期结束后,内核会回收其所占有的资源。这些资源包括初始化时会占有的内存、调度时共享的CPU、网络等。``

    进程的内存分布主要如下:

    • 文本段:存放代码数据
    • 数据段:存放静态数据
    • 堆:可扩展内存,用于存放new出来的数据
    • 栈:记录局部变量、函数调用链等数据

    进程的创建有两种方式:

    fork()实现:被fork出的进程被称为fork函数调用进程的子进程,调用进程被称为父进程。为了区别进程,每个进程都有自己的进程id即PID,同时进程有一个字段PPID,用于表示当前进程的父进程是什么。内核通过复制父进程的方式创建子进程。子进程会继承父进程除文本段外的所有数据,共享父进程文本段数据。子进程创建以后,对自身数据段、堆栈的数据修改将不会影响到父进程。其实通常的做法是,子进程创建拥有了自己的虚拟地址空间,但其中的数据段、堆栈对映的物理地址和父进程是相同的,一旦子进程对数据有修改,才会为子进程重新分配物理空间。

    execve()实现:另外可以通过执行程序的方式,创建一个新进程,通过execve()族函数实现,具体函数和平台有关。这种方式创建的进程不再从创建进程处继承数据,而是加载新的文本段。

    不管是什么方式创建的进程,创建新进程的进程都被称为父进程,新进程被称为子进程,那么最开始的那个祖宗进程是谁?由谁创建?答案是init进程,这是个极其特殊的进程,Linux kernel在启动的最后,会启动用户空间的第一个进程,也就是init,它的进程id始终为1。随后init会fork出其它用户空间进程,于是,init进程和它的子孙进程一起组成了一个树状结构,这也是进程树概念的由来。

    线程:可以看做是一种特殊形式的进程,特殊之处在于,除了有自己单独的栈空间用于存放局部变量和函数调用外,共享进程的所有数据(文本段、数据段、堆)。所以对于当线程对共享数据访问时需要足够注意。线程共享变量之间的控制,主要通过Mutex(互斥量)、pthread_cond_t 条件变量实现,后面会有详细解读。还有什么呢,对了,进程默认会有一个主线程,嗯。

    有了对进程、线程的原理性认知之后,应用开发时需要刻意记住的规则,变得有迹可循起来。

    线程的创建和运行

    POSIX中的线程,需要引入pthread的头文件:#include

    线程的一般使用步骤是:

    1. 声明线程函数:作为线程被CPU调度时需要执行的函数,其函数原型如下

      void *downloadfile(void *filename) {
          // 需要执行的代码
      	pthread_exit((void *)data); // 可选项:当线程退出时,返回给调用者数据时使用
      }
      
      • 1
      • 2
      • 3
      • 4
      • pthread_exit((void *)data):一个运行中的函数可以通过调用该函数退出线程。函数参数data需要被转为一个(void *)类型,这是线程退出的返回值。
    2. 声明线程对象:线程对象类型为pthread_t,是一个线程区别于其它线程的标志。该对象会在创建线程阶段被初始化。

    3. 声明线程属性:线程属性类型为:pthread_attr_t,线程属性的声明、初始化、赋值一般都在创建线程前,前后同一段时间执行。TODO:默认是什么?NULL

      pthread_attr_t thread_attr; // 线程属性声明
      pthread_attr_init(&thread_attr); // 线程属性初始化
      pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_JOINABLE); // 线程属性赋值
      
      • 1
      • 2
      • 3

      属性声明和初始化函数没什么好说的,需要注意的是pthread_attr_setdetachstate函数的第二个参数。通常有两个可选参数:

      • PTHREAD_CREATE_JOINABLE:该属性的线程被终止时,会保留虚拟内存、堆栈等资源,由父线程释放,释放前父线程可以获取子线程的返回值。主线程将会等待子线程执行完毕,才会最终退出。
      • PTHREAD_CREATE_DETACHED:该属性的线程终止后,资源由系统自动回收,父线程将无法获得子线程的返回值。
    4. 创建线程:创建线程,由pthread_create函数实现,线程创建成功后,得到CPU时间后就开始执行线程函数了。原型如下:

      int pthread_create(pthread_t *th, const pthread_attr_t *attr, void *(* func)(void *), void *arg);
      
      • 1

      四个参数的含义分别如下:

      • th:前面申明的线程对象指针,用于给该对象赋值。
      • attr:线程属性指针,用于规定新创建线程的相关行为。
      • void *(* func)(void *):函数指针,线程执行时,执行该指针指向的函数。
      • arg:线程函数调用对应的参数。
    5. 销毁线程属性:不用的内存就销毁,养成好习惯

      int pthread_attr_destroy(pthread_attr_t *attr);
      
      • 1
    6. 等待线程结束:主线程执行(默认线程)到这里,会等待新创建的子线程退出,退出时,可以通过第二个参数,将子线程返回值返回给调用者。

      int pthread_join(pthread_t t, void **res);
      
      • 1
    7. 主线程结束:可以直接通过调用该函数退出线程,参数用于将子线程返回值返回,不需要返回值可以为NULL

      void pthread_exit(void *res);
      
      • 1

    综上,一个线程最基本的使用伪代码如下:

    #include 
    // 1. 声明线程函数
    void *fun_run(void* param) {
        long progress;
        pthread_exit((void *) progress);
    }
    pthread_t t // 2. 申明线程对象
    pthread_attr_t attr; // 3. 声明并初始化线程属性
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    // 4.创建线程
    pthread_create(t, attr, fun_run, param);
    pthread_attr_destroy(attr); // 5. 回收线程属性内存
    int downloadtime; 
    pthread_join(t, (void**)&downloadtime); // 等待线程执行完毕,并获取返回值
    pthread_exit(nullptr); // 退出主线程 非必须
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    以下是一段使用线程的完整代码,可直接复制编译运行:

    #include 
    #include 
    #include 
    
    using namespace std;
    
    #define NUM_OF_TASKS 5
    
    void *downloadfile(void *filename) { // 线程函数,执行下载任务
        printf("I am downloading the file %s!\n", (char *)filename);
    //    sleep(10);
        _sleep(10);
        long downloadtime = rand() % 100; // 随机一个下载时间
        printf("I finish downloading the file within %d minutes!\n", downloadtime);
        pthread_exit((void *)downloadtime); // 线程退出,并返回返回下载所需时间
    }
    
    int main(int argc, char *argv[]) {
        char files[NUM_OF_TASKS][20]={"file1.avi","file2.rmvb","file3.mp4","file4.wmv","file5.flv"};
        pthread_t threads[NUM_OF_TASKS]; // 声明5个线程
        int rc; 
        int t;
        int downloadtime;
    
        pthread_attr_t thread_attr; // 线程属性声明
        pthread_attr_init(&thread_attr); // 线程属性初始化
        pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE); // 线程属性赋值为:需要等待线程结束
    
        for(t = 0; t < NUM_OF_TASKS; t++) {
            printf("creating thread %d, please help me to download %s\n", t, files[t]);
            rc = pthread_create(&threads[t], &thread_attr, downloadfile, (void *)files[t]); // 创建之前申明的5个线程
            if (rc){
                printf("ERROR; return code from pthread_create() is %d\n", rc);
                exit(-1);
            }
        }
    
        pthread_attr_destroy(&thread_attr); // 销毁线程属性
    
        for(t = 0; t < NUM_OF_TASKS; t++){
            pthread_join(threads[t],(void**)&downloadtime); // 
            printf("Thread %d downloads the file %s in %d minutes.\n",t,files[t],downloadtime);
        }
    
        pthread_exit(NULL);
    }
    
    • 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

    上述代码执行结果为:

    creating thread 0, please help me to download file1.avi
    creating thread 1, please help me to download file2.rmvb
    I am downloading the file file1.avi!
    creating thread 2, please help me to download file3.mp4
    I am downloading the file file2.rmvb!
    creating thread 3, please help me to download file4.wmv
    I am downloading the file file3.mp4!
    creating thread 4, please help me to download file5.flv
    I am downloading the file file4.wmv!
    I am downloading the file file5.flv!
    I finish downloading the file within 41 minutes!
    I finish downloading the file within 41 minutes!
    I finish downloading the file within 41 minutes!
    I finish downloading the file within 41 minutes!
    I finish downloading the file within 41 minutes!
    Thread 0 downloads the file file1.avi in 41 minutes.
    Thread 1 downloads the file file2.rmvb in 41 minutes.
    Thread 2 downloads the file file3.mp4 in 41 minutes.
    Thread 3 downloads the file file4.wmv in 41 minutes.
    Thread 4 downloads the file file5.flv in 41 minutes.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    线程数据

    线程数据被分为三类,1、线程栈中的本地数据。2、线程私有数据。3、进程共享全局数据,分别看看它们的特点于应用。

    线程栈本地数据

    本地数据比较容易理解,每个线程都有一块叫做线程栈的内存空间。在线程中,临时申明的变量、函数之间的调用关系等都会被存入栈空间中。这个栈所占空间的大小有一个系统默认值(8192即8M),可以通过ulimt -a/s查看:

    homer:/ # ulimit -s
    8192
    homer:/ # ulimit -a
    -t: time(cpu-seconds)     unlimited
    -f: file(blocks)          unlimited
    -c: coredump(blocks)      0
    -d: data(KiB)             unlimited
    -s: stack(KiB)            8192
    -l: lockedmem(KiB)        65536
    -n: nofiles(descriptors)  32768
    -p: processes             7816
    -i: sigpending            7816
    -q: msgqueue(bytes)       819200
    -e: maxnice               40
    -r: maxrtprio             0
    -m: resident-set(KiB)     unlimited
    -v: address-space(KiB)    unlimited
    -x: filelocks             unlimited
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    一个进程中,每个线程栈中间都有一小块隔离区,当线程调用超过了自己的栈空间范围,触碰到了隔离区,则会引起断错了。如果线程栈空间不够用,可以通过线程属性,修改线程的栈空间大小:

    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
    
    • 1

    线程私有数据

    一个进程中的线程,除了自己的栈空间外,其它空间都是线程之间共享的。如果这个时候需要一个线程的全局变量(基于在不同的函数之间传递数据所需)怎么办?这就要用到线程私有数据类型了(Thread Specific Data)。

    顾名思义,就是线程自己独享的数据,它有一个特点是:一次创建,可供多个线程使用。区别在于,它的值由各自线程维护和使用。相关函数调用如下:

    • 创建变量:int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))

      key:将被创建的私有数据类型变量

      void (*destructor)(void*):清理函数,在线程释放该线程存储时调用。如果该函数为NULL,将会调用默认的清理函数。

    • 各线程中为变量赋值:int pthread_setspecific(pthread_key_t key, const void *value)

      key:被赋值的变量

      value:变量值

    • 各线程中从变量取值:void *pthread_getspecific(pthread_key_t key)

      返回变量之前取的值

    一个完整(可以直接复制编译通过)的私有数据demo如下:

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    pthread_key_t key;// 全局变量
    
    struct person {
        int age;
        string name;
    };
    
    void *run1(void *arg) {
        struct person homer;
        _sleep(3);
        homer.age = 10;
        homer.name = "homer";
        pthread_setspecific(key, &homer); // 为全局线程私有变量赋值
        printf("run1 data ptr is --> 0x%p\n", &(homer));
        printf("run1 data ptr frome pthread_getspecific(key) is --> 0x%p\n", (person *)pthread_getspecific(key));
        printf("run1 data value is %s:%d\n",
               ((person *)pthread_getspecific(key))->name, ((person *)pthread_getspecific(key))->age);
    }
    
    void *run2(void *arg) {
        int temp = 20;
        _sleep(2);
        pthread_setspecific(key, &temp); // 好吧,原来这个函数这么简单
        printf("run2 data ptr is --> 0x%p\n", &temp);
        printf("run2 data ptr frome pthread_getspecific(key) is --> 0x%p\n", (int *)pthread_getspecific(key));
        printf("run2 data value is %d\n", *((int *)pthread_getspecific(key)));
    }
    
    int main(int argc, char *argv[]) {
        pthread_t t1, t2;
        pthread_key_create(&key, NULL); // 这里是构建一个pthread_key_t类型,确实是相当于一个key
        pthread_create(&t1, NULL, run1, NULL);
        pthread_create(&t2, NULL, run2, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_key_delete(key);
        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

    执行后的结果如下:

    run2 data ptr is --> 0x02A5FF04
    run2 data ptr frome pthread_getspecific(key) is --> 0x02A5FF04
    run2 data value is 20
    run1 data ptr is --> 0x0281FEEC
    run1 data ptr frome pthread_getspecific(key) is --> 0x0281FEEC
    run1 data value is homer:10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    进程全局数据

    进程中的全局变量,顾名思义,就是整个进程共享的。进程的子线程都可以访问,所以就需要一个机制,防止线程之间对该共享变量肆意访问造成数据的不确定性。这就要引入数据保护机制,即锁的概念了。

    进程共享变量锁

    共享数据的保护,使用的是Mutex,全称为Mutual Exclusion,很多地方都把它叫做互斥锁。但是对它的解释我有点不太能理顺,我有一版自己的理解。这里就说说我的理解,不喜欢的可以评论区讨论。

    我的理解是,在锁机制中,还有一个钥匙的概念(可以抽象为一种访问权限),钥匙并不实际存在对应的一个对象类,但是却是打开互斥锁的关键。一个互斥锁对象对应一把钥匙。在共享变量访问前,相关的代码段通过pthread_mutex_lock函数,申请了一把对应的钥匙,并把该代码段用锁锁起来,此时锁是关闭状态,只有拥有钥匙的代码段才能够打开,其它代码段再调用pthread_mutex_lock函数就无法获取钥匙开锁,于是只能等待。拥有钥匙的代码段执行完成之后,通过pthread_mutex_unlock函数,将锁打开,然后将钥匙归还,以便其他代码段获取钥匙。

    互斥锁相关的类和函数顺序一般如下:

    • 声明互斥锁变量:互斥锁变量通常是全局变量,类型为pthread_mutex_t,申明伪代码:pthread_mutex_t lock;
    • 初始化锁变量:pthread_mutex_init(&lock, NULL); 第二个参数为互斥锁属性,暂时没见到应用场景,不予讨论。
    • 对操作共享变量的代码段上锁:pthread_mutex_lock(&lock);上锁后,代码段获得了唯一的钥匙,其它线程再通过该函数上锁时将因无法获取钥匙而等待,达到保护共享变量数据的目的。
    • 操作完成,开锁,释放钥匙:pthread_mutex_unlock(&lock);
    • 释放互斥锁空间:pthread_mutex_destroy(&lock);

    下面是一段典型的应用代码:模拟异常交易,jerry给tom在不同的地方同时打钱,在多线程中极易发生在某一时刻,两者账户资金总量只合与初始值不等的情况。锁保护后,即可保证所有时刻,资金总量保持不变。

    #include 
    #include 
    #include 
    
    int money_of_tom = 100;
    int money_of_jerry = 100;
    pthread_mutex_t lock;
    
    void *transfer(void *notused) {
        pthread_t tid = pthread_self();
        printf("Thread %u is transfering money!\n", (unsigned int)tid);
        pthread_mutex_lock(&lock); // 获取钥匙,开锁。没获取到则一直等待
        _sleep(rand() % 10);
        money_of_tom += 10;
        _sleep(rand() % 10);
        money_of_jerry -= 10;
        pthread_mutex_unlock(&lock); // 释放锁钥匙,让其它地方可以获取钥匙
        printf("Thread %u finish transfering money!\n", (unsigned int)tid);
        pthread_exit((void *)0);
    }
    
    int main(int argc, char *argv[]) {
        pthread_t t1;
        pthread_t t2;
        int t;
    
        pthread_mutex_init(&lock, NULL); // 初始化锁
        pthread_create(&t1, NULL, transfer, NULL); // 创建线程,模拟异地交易
        pthread_create(&t2, NULL, transfer, NULL); // 创建线程,模拟异地交易
    
        for(t = 0; t < 10; t++){ // 不断查询两个人钱的总数,也需要获取钥匙
            pthread_mutex_lock(&lock);
            printf("money_of_tom + money_of_jerry = %d\n", money_of_tom + money_of_jerry);
            pthread_mutex_unlock(&lock);
        }
        pthread_mutex_destroy(&lock); // 释放互斥锁
        pthread_exit(NULL);
    }
    
    • 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

    对应的打印如下:

    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    money_of_tom + money_of_jerry = 200
    Thread 2 is transfering money!
    Thread 3 is transfering money!
    Thread 2 finish transfering money!
    Thread 3 finish transfering money!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上面的代码,如果将锁相关的行注释掉,可能会产生不一样的结果。

    共享变量锁与条件变量

    上一节关于互斥锁的应用有个问题,那就是如果一个线程在调用pthread_mutex_lock函数时,获取不到钥匙,它就会一直阻塞在哪里,直到其它线程释放钥匙。另外一种获取钥匙的函数是pthread_mutex_trylock:该函数的特点是,调用线程回去尝试获取钥匙,如果获取不到则直接返回。

    有一个场景是,当一个线程运行需要达到某种条件才能运行时,即使获得钥匙,因为没有满足条件就只能释放钥匙。只要条件不满足,就会一直去重复获取锁、检查条件不满足、释放锁的循环,这会消耗大量的本应分配给其它线程的CPU资源。条件变量的引入,可以解决这个问题。

    条件变量需要被锁保护,当线程获得钥匙后,会检查条件是否满足,如果不满足就会释放钥匙,线程还是阻塞在这里,区别时线程会进入休眠状态,等待条件满足后被唤醒执行。休眠的线程让有条件执行的线程可以获得CPU资源,这种类似通知的模型大大解约了资源。

    通知其实是靠条件变量来实现的。先来介绍一下条件变量的相关结构体和函数:

    typedef void	*pthread_cond_t;
    int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *a);
    int pthread_cond_destroy(pthread_cond_t *cv);
    int pthread_cond_signal(pthread_cond_t *cv);
    int pthread_cond_broadcast(pthread_cond_t *cv);
    int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *external_mutex);
    int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *external_mutex, const struct timespec *t);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    pthread_cond_t是一个由各平台自己实现,POSIX规定,需要使用pthread_cond_init()函数初始化。重点关注一下pthread_cond_wait()函数,通过它可以解释大多数条件变量使用的问题。

    该函数有两个参数,第一个参数就是条件变量指针,第二个参数是一个互斥量类型指针pthread_mutex_t *

    重点需要关注的是pthread_cond_wait函数,该函数通常和pthread_mutex_lock一起使用:

    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    
    • 1
    • 2

    这两个函数的含义如下:

    1. 首先获得锁钥匙
    2. 判断条件是否满足,如果不满足则释放钥匙,线程阻塞并进入休眠状态等待被唤醒。
    3. 条件变量唤醒线程时,会从pthread_cond_wait函数往下执,接着判断条件是否满足。回到第二部的流程。

    所以,通常pthread_cond_wait函数都是被放在一个条件循环语句中执行的。

    pthread_cond_signal()pthread_cond_broadcast()都能唤醒被pthread_cond_wait()阻塞的线程。区别在于前者唤醒一个线程,而后者唤醒所有相同条件变量的线程。pthread_cond_destroy()用于释放条件变量。pthread_cond_timedwait()pthread_cond_wait()类似,只是多了一个超时时间。

    下面介绍一个具体的例子(来源刘超的操作系统系列课程):三个线程代表三个员工,一个老板。老板给员工派发任务,任务是有限的,员工需要抢活干。建议先看主函数部分。

    #include 
    #include 
    #include 
    
    #define NUM_OF_TASKS 3
    #define MAX_TASK_QUEUE 11
    
    char tasklist[MAX_TASK_QUEUE]="ABCDEFGHIJ";
    int head = 0;
    int tail = 0;
    
    int quit = 0;
    
    pthread_mutex_t g_task_lock;
    pthread_cond_t g_task_cv; // 申明信号变量
    
    void *coder(void *notused) {
        pthread_t tid = pthread_self();
    
        while(!quit){ // 只要员工没被开,就一直要抢任务做
            pthread_mutex_lock(&g_task_lock); // 共享变量访问,抢钥匙
            while(tail == head){ // 看一下有没有任务
                if(quit){
                    pthread_mutex_unlock(&g_task_lock);
                    pthread_exit((void *)0);
                }
                printf("No task now! Thread %u is waiting!\n", (unsigned int)tid);
                pthread_cond_wait(&g_task_cv, &g_task_lock); // 没有任务,线程阻塞
                printf("Have task now! Thread %u is grabing the task !\n", (unsigned int)tid);
            }
            char task = tasklist[head++]; // 有任务,当前线程抢到了任务
            pthread_mutex_unlock(&g_task_lock); // 解锁,完成任务
            printf("Thread %u has a task %c now!\n", (unsigned int)tid, task);
            _sleep(5);
            printf("Thread %u finish the task %c!\n", (unsigned int)tid, task);
        }
    
        pthread_exit((void *)0);
    }
    
    int main(int argc, char *argv[]) {
        pthread_t threads[NUM_OF_TASKS]; // 三个员工
        int t;
    
        pthread_mutex_init(&g_task_lock, NULL); // 初始化互斥量
        pthread_cond_init(&g_task_cv, NULL); // 初始化条件变量
    
        for(t = 0; t < NUM_OF_TASKS; t++) { // 初始化三个员工
            pthread_create(&threads[t], NULL, coder, NULL); // 员工开始工作了,接着看线程函数部分
        }
    
        _sleep(5); // 等待,老板招完人并不是马上派活
    
        for(t = 1; t <= 4; t++){
            pthread_mutex_lock(&g_task_lock); //共享数据访问,上个锁
            tail += t; // 派活
            printf("I am Boss, I assigned %d tasks, I notify all coders!\n", t);
            pthread_cond_broadcast(&g_task_cv); // 叫三个员工全部起来抢活干,但是因为当前线程还没把锁打开,员工还在等锁打开
            pthread_mutex_unlock(&g_task_lock); // 老板把锁打开,将钥匙交出去让员工抢。
            _sleep(20);
        }
    	// 活没了,解雇所有员工(让所有线程退出)后,老板再退出
        pthread_mutex_lock(&g_task_lock); 
        quit = 1;
        pthread_cond_broadcast(&g_task_cv); // 通知员工离开
        pthread_mutex_unlock(&g_task_lock);
    	// 释放所有资源
        pthread_mutex_destroy(&g_task_lock);
        pthread_cond_destroy(&g_task_cv);
        pthread_exit(NULL);
    }
    
    • 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

    相关辅助理解的注释已经放在代码中了,相关打印如下:

    No task now! Thread 2 is waiting!
    No task now! Thread 3 is waiting!
    No task now! Thread 4 is waiting!
    I am Boss, I assigned 1 tasks, I notify all coders!
    Have task now! Thread 2 is grabing the task !
    Thread 2 has a task A now!
    Have task now! Thread 3 is grabing the task !
    No task now! Thread 3 is waiting!
    Have task now! Thread 4 is grabing the task !
    No task now! Thread 4 is waiting!
    Thread 2 finish the task A!
    No task now! Thread 2 is waiting!
    I am Boss, I assigned 2 tasks, I notify all coders!
    Have task now! Thread 2 is grabing the task !
    Thread 2 has a task B now!
    Have task now! Thread 3 is grabing the task !
    Thread 3 has a task C now!
    Have task now! Thread 4 is grabing the task !
    No task now! Thread 4 is waiting!
    Thread 3 finish the task C!
    No task now! Thread 3 is waiting!
    Thread 2 finish the task B!
    No task now! Thread 2 is waiting!
    I am Boss, I assigned 3 tasks, I notify all coders!
    Have task now! Thread 4 is grabing the task !
    Thread 4 has a task D now!
    Have task now! Thread 3 is grabing the task !
    Thread 3 has a task E now!
    Have task now! Thread 2 is grabing the task !
    Thread 2 has a task F now!
    Thread 4 finish the task D!
    No task now! Thread 4 is waiting!
    Thread 2 finish the task F!
    No task now! Thread 2 is waiting!
    Thread 3 finish the task E!
    No task now! Thread 3 is waiting!
    I am Boss, I assigned 4 tasks, I notify all coders!
    Have task now! Thread 4 is grabing the task !
    Thread 4 has a task G now!
    Have task now! Thread 3 is grabing the task !
    Thread 3 has a task H now!
    Have task now! Thread 2 is grabing the task !
    Thread 2 has a task I now!
    Thread 2 finish the task I!
    Thread 2 has a task J now!
    Thread 3 finish the task H!
    No task now! Thread 3 is waiting!
    Thread 4 finish the task G!
    No task now! Thread 4 is waiting!
    Thread 2 finish the task J!
    Have task now! Thread 4 is grabing the task !
    
    • 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

    以上,就是线程相关的所有操作了。

  • 相关阅读:
    CPDA|运营人如何从负基础学会数据分析(SQL)
    【Matlab】曲线拟合
    基于SSM的房屋租售网站
    如何将jpg转化为png?
    Redis (一)
    asp.net学生成绩评估系统VS开发sqlserver数据库web结构c#编程计算机网页项目
    钱就是道,因为钱具备道的灵魂!
    浅谈 深度学习、机器学习、人工智能
    Mysql 索引原理和优化方式
    Zookeeper 中的 CAP
  • 原文地址:https://blog.csdn.net/qq_25333681/article/details/127932371