Linux系统编程——线程

一、线程基本概念

1.进程与线程

典型的UNIX/linux进程可以看成是只有一个控制线程,一个进程在同一时刻只做一件事情,有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。

  • 进程
    进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位
  • 线程
    线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源
  • 进程线程的区别
    进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,最好用线程。

2.使用线程的好处

(1)线程是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
(2)线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

二、线程相关函数

概述:
多线程开发在linux平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程互斥锁条件。其中线程操作有:创建退出等待
互斥锁操作有:创建销毁加锁解锁
条件操作有 :创建销毁触发广播等待
其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
在这里插入图片描述

1.线程的创建,退出,等待

(1)线程创建函数

  • 函数原型
#include <pthread.h>//需要引入的头文件
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
  • 参数:
    tidp:指向线程标识符的指针。
    attr:用来设置线程属性,如果没有则填NULL。
    start_rtn:表示线程运行函数的地址。
    arg:线程运行函数的参数,如果没有参数则填NULL

(补充):restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码.如 int *restrict ptr, ptr 指向的内存单元只能被 ptr 访问到,任何同样指向这个内存单元的其他指针都是未定义的,直白点就是无效指针(野指针)。

  • 返回值:
    成功返回0,失败返回错误编号。
  • 函数详解:
    当 pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
    新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作arg参数传入。
  • 函数用法示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
struct Test{

    int age;
    char name[32];
};
void *thread_func(void *arg)
{
    struct Test *t = (struct Test*)arg;
    printf("t->age : %d\n",t->age);
    printf("t->name : %s\n",t->name);
    return NULL;
}
int main()
{
    int ret;
    pthread_t tid;//线程标识符
    struct Test t1 = {18,"zhangSan"};
    printf("main thread.\n");
    ret = pthread_create(&tid, NULL, thread_func, &t1);//传入的参数为结构体
    if(ret)
        printf("ioctl: %s\n", strerror(errno));
    sleep(1);
    return 0;
}

在这里插入图片描述

(2)线程退出函数

  • 函数原型:
int pthread_exit(void *rval_ptr);
  • 参数:
    rval_ptr表示线程退出状态,是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
  • 函数返回值:成功:0;失败:错误号
  • 函数详解:
    在线程中禁止调用exit函数,否则会导致整个进程退出,取而代之的是调用pthread_exit函数,这个函数是使一个线程退出,如果主线程调用pthread_exit函数也不会使整个进程退出,不影响其他线程的执行。
    需要注意一点,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收
    如果在主线程中使用pthread_exit函数,会导致子线程还在,内存无法被回收,成为僵尸进程。引入pthread_join函数。

(3)线程等待函数

  • 函数原型
int pthread_join(pthread_t thread, void **rval_ptr);
  • 参数:
    thread:线程ID
    rval_ptr:存储线程结束状态,整个指针和pthread_exit的参数是同一块内存地址。

  • 返回值:成功:0;失败:错误号

  • 函数详解:
    详解:
    调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。

    可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。

    如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。

主线程获取子线程的退出状态例子

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

struct Test{
    int age;
    char name[32];
};

struct Test rstatus;//退出状态信息,需要全局变量
void *thread_func(void *arg)
{
    struct Test *t = (struct Test*)arg;
    printf("传入线程的参数age : %d\n",t->age);
    printf("传入线程的参数 name : %s\n",t->name);

    memset(&t,0,sizeof(t));

    //退出状态需要传递的状态信息
    rstatus.age = 20;
    strcpy(rstatus.name,"Lili");
    pthread_exit(&rstatus);
}
int main()
{
    int ret;
    pthread_t tid;//线程标识符
    struct Test *p;
    struct Test t1 = {18,"zhangSan"};
    printf("main thread.\n");
    ret = pthread_create(&tid, NULL, thread_func, &t1);//传入的参数为结构体
    if(ret){
        printf("ioctl: %s\n", strerror(errno));
    }
    printf("等待线程退出\n");

    pthread_join(tid,(void **)&p);
    printf("线程退出,退出的状态的状态信息:age : %d,name : %s\n",  p->age, p->name);

    return 0;
}


在这里插入图片描述

2.其他与线程相关的函数

(1)线程脱离函数

  • 函数原型:
int pthread_detach(pthread_t thread);

  • 参数:需要脱离的线程
  • 返回值:若成功返回0,否则返回错误编号
  • 函数详解:
     一个线程或者是可汇合joinable,默认值),或者是脱离的(detached)。
    当一个可汇合的线程终止时
    它的线程ID和退出状态将留存到另一个线程对它调用pthread_join
    当一个脱离的线程终止时:像守护进程一样,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止。

如果一个线程需要知道另一线程什么时候终止,那就最好保持第二个线程的可汇合状态

(2)获取线程ID函数

  • 函数原型:
pthread_t pthread_self(void);
  • 返回值:调用线程的ID
  • 函数详解:
    对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,因为不同系统对线程ID的定义可能不一样。我们应该要用pthread_self函数获取一个线程的ID
    此函数可与pthread_detach函数配合使用,想让自己脱离的线程使用,就如以下语句:
pthread_detach(pthread_self());

(3)线程ID比较函数

  • 函数原型:
int pthread_equal(pthread_t tid1, pthread_t tid2);
  • 参数:需要比较的两个线程的ID
  • 返回值:若相等则返回非0值,否则返回0

三、与互斥锁相关的函数

1.互斥概述

互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。

在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。操作系统并不会做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都获取了锁,也还是会出现数据不一致的问题。

互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy

2.互斥相关函数

(1)创建互斥锁函数

  • 函数作用:初始化互斥锁
  • 函数原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
  • 参数:
    mutex:是只想要初始化的互斥锁的指针
    attr:用于指定互斥锁属性,如果为NULL则使用默认属性(缺省属性)

互斥锁属性
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

  • 返回值 :若成功返回0,否则返回错误编号

(2)销毁互斥锁函数

  • 函数作用:销毁互斥锁
  • 函数原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 参数:
    mutex:是只想要销毁的互斥锁的指针
  • 返回值:若成功返回0,否则返回错误编号

(3)其他锁操作函数(加锁、解锁、测试加锁)

  • 函数说明:
     锁操作主要包括加锁pthread_mutex_lock()解锁pthread_mutex_unlock()测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
  • 函数原型
  int pthread_mutex_lock(pthread_mutex_t *mutex);
  int pthread_mutex_unlock(pthread_mutex_t *mutex);
  int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 参数:想要操作的互斥锁的指针
  • 返回值:若成功返回0,否则返回错误编号
  • pthread_mutex_lockpthread_mutex_trylock区别
     
      pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

线程与互斥锁应用例子

当不加互斥量执行以下程序

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int num = 0;//定义全局变量,为共有资源
void *thread_func1(void *arg)
{
    int i;
    printf("t1:the id of the pthread is:%ld\n",(unsigned long)pthread_self());

    for(i = 0;i<3;i++){
        num++;
        sleep(1);
        printf("this is thread 1 ,the num is %d\n ",num);

    }


    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    int i;
    printf("t2:the id of the pthread is:%ld\n",(unsigned long)pthread_self());
    for(i = 0;i<3;i++){
        num++;
        sleep(1);
        printf("this is thread 2 ,the num is %d\n ",num);

    }
    pthread_exit(NULL);
}
int main()
{
    int ret;

    pthread_t ptid1;//线程标识符
    pthread_t ptid2;//线程标识符

    ret = pthread_create(&ptid1, NULL, thread_func1, NULL);
    if(ret!=0){
        perror ("pthread_1_create");
    }

    ret = pthread_create(&ptid2,NULL, thread_func2, NULL);
    if(ret!=0){
        perror ("pthread_1_create");
    }


    pthread_join(ptid1,NULL);
    pthread_join(ptid2,NULL);

    return 0;
}

对全局变量num的访问存在竞争随机,
在这里插入图片描述
加入互斥量的代码:

include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int num = 0;//定义全局变量,为共有资源
pthread_mutex_t mutex;//定义互斥量

void *thread_func1(void *arg)
{
    int i;
    printf("t1:the id of the pthread is:%ld\n",(unsigned long)pthread_self());
    pthread_mutex_lock(&mutex);
    for(i = 0;i<3;i++){
        num++;
        sleep(1);
        printf("this is thread 1 ,the num is %d\n ",num);

    }
    pthread_mutex_unlock(&mutex);

    pthread_exit(NULL);
}
void *thread_func2(void *arg)
{
    int i;
    printf("t2:the id of the pthread is:%ld\n",(unsigned long)pthread_self());
    pthread_mutex_lock(&mutex);
    for(i = 0;i<3;i++){
        num++;
        sleep(1);
        printf("this is thread 2 ,the num is %d\n ",num);

    }
    pthread_mutex_unlock(&mutex);

    pthread_exit(NULL);
}
int main()
{
    int ret;

    pthread_t ptid1;//线程标识符
    pthread_t ptid2;//线程标识符

    ret = pthread_mutex_init(&mutex,NULL);//创建互斥锁
    if(ret!=0){
        perror("mutex creat error");
    }

    ret = pthread_create(&ptid1, NULL, thread_func1, NULL);
    if(ret!=0){
        perror ("pthread_1_create");
    }

    ret = pthread_create(&ptid2,NULL, thread_func2, NULL);
    if(ret!=0){
        perror ("pthread_1_create");
    }


    pthread_join(ptid1,NULL);
    pthread_join(ptid2,NULL);

    ret = pthread_mutex_destroy(&mutex);//销毁互斥锁
    if(ret!=0){
        perror("mutex destory error");
    }
    return 0;
}

加入互斥锁后可见,仅在一个线程执行完加锁内容后,另一个线程才能执行
在这里插入图片描述

三.与条件变量相关的函数

1.线程条件控制概述

条件变量:本身不是锁,但可以造成线程阻塞,通常与互斥锁配合使用;
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待 "条件变量的条件成立 "而挂起;另一个线程使 "条件成立 "(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

2.线程条件控制函数

(1)创建及销毁条件变量

①创建条件变量

  • 函数原型:
int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);
  • 参数:
    cond:想要初始化的条件变量的指针
    attr:用于指定互斥锁属性,如果为NULL则使用默认属性(缺省属性),与互斥量创建函数类似。
  • 返回值:函数成功返回0;任何其他返回值都表示错误

②销毁条件变量

  • 函数原型:
int pthread_cond_destroy(pthread_cond_t *cond);
  • 参数:
    cond:想要销毁的条件变量的指针
  • 返回值:函数成功返回0;任何其他返回值都表示错误

(2)等待函数

①无条件等待

  • 函数原型:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 参数说明:
    cond:条件变量指针
    mutex:互斥锁指针
  • 返回值:函数成功返回0;任何其他返回值都表示错误

②计时等待

  • 函数原型:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, struct timespec *restrict abstime);
  • 参数说明:
    cond:条件变量指针
    mutex:互斥锁指针
    abstime:abstime是一个绝对时间,Linux中常用的时间结构有struct timespecstruct timeval

这里面的超时时间是一个绝对值,也就是距离1970-1-1 日的时间值,而不是一个时间段。比如说当前时间为2021-5-15 17:14:00.100,我们想通过这个函数设置最大超时为2500ms,那么就需要设置abstime时间为2021-5-15 17:14:02.600.


#include <sys/time.h>//需要引入的头文件

struct timespec
{
    time_t tv_sec;        /* Seconds. */
    long   tv_nsec;       /* Nanoseconds. */
};
struct timeval {
        time_t tv_sec;  
        suseconds_t tv_usec;
}; 

abstime参数设置时例:

	struct timespec abstime;
    struct timeval now;
    gettimeofday(&now, NULL);
    abstime.tv_sec = now.tv_sec + 5;
    abstime.tv_nsec = now.tv_usec * 1000;
  • 返回值:函数成功返回0;任何其他返回值都表示错误
  • 等待函数说明

pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数函数把调用线程放到等待条件的线程列表上然后对互斥量解锁,这两个操作都是原子操作。即:

在使用pthread_cond_timedwait()函数时,必须有三步:
1.加互斥锁:pthread_mutex_lock(&__mutex)
2.等待:pthread_cond_wait(&__cond, &__mutex) //解锁->等待->加锁
3.解互斥锁:pthread_mutex_unlock(&__mutex)

这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。
pthread_cond_wait返回时,互斥量再次被锁住
pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个abstime。abstime指定了等待的时间,它是通过timespec结构指定:

struct timespec abstime;
struct timeval now;
gettimeofday(&now, NULL);
abstime.tv_sec = now.tv_sec + 5;
abstime.tv_nsec = now.tv_usec * 1000;
pthread_cond_timedwait(&__cond, &__mutex, &__abstime) 

(3)触发函数

  • 函数原型:
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
  • 参数:需要解除在条件变量指针
  • 返回值:返回值:若成功返回0,否则返回错误编号
  • 函数说明:
    这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。

条件变量与互斥量结合示例

  • 当线程2操作全局变量num为3时,线程1执行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>


int num = 0;//定义全局变量,为共有资源

pthread_mutex_t mutex;//定义互斥量
pthread_cond_t  cond;//定义条件变量

void *thread_func1(void *arg)
{
    int i;
    printf("t1:the id of the pthread is:%ld\n",(unsigned long)pthread_self());

    while(1){
        pthread_mutex_lock(&mutex);

        while(num!=3)
        pthread_cond_wait(&cond,&mutex);//等待条件变量唤醒
        printf("this is thread 1 ,the num is %d\n ",num);
        printf("t1 is run ========!!!!!\n");

        num = 0;
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }

}
void *thread_func2(void *arg)
{
    printf("t2:the id of the pthread is:%ld\n",(unsigned long)pthread_self());

    while(1){

        printf("this is thread 2 ,the num is %d\n ",num);
        pthread_mutex_lock(&mutex);
        num++;
        if(num == 3){
            pthread_cond_signal(&cond);//唤醒等待的线程
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}
int main()
{
    int ret;

    pthread_t ptid1;//线程标识符
    pthread_t ptid2;//线程标识符

    ret = pthread_mutex_init(&mutex,NULL);//创建互斥锁
    if(ret!=0){
        perror("mutex creat error");
    }

    ret = pthread_cond_init(&cond,NULL);//创建条件变量
    if(ret!=0){
        perror("cond creat error");
    }




    ret = pthread_create(&ptid1, NULL, thread_func1, NULL);
    if(ret!=0){
        perror ("pthread_1_create");
    }

    ret = pthread_create(&ptid2,NULL, thread_func2, NULL);
    if(ret!=0){
        perror ("pthread_1_create");
    }


    pthread_join(ptid1,NULL);
    pthread_join(ptid2,NULL);

    ret = pthread_mutex_destroy(&mutex);//销毁互斥锁
    if(ret!=0){
        perror("mutex destory error");
    }

    ret = pthread_cond_destroy(&cond);//销毁互斥锁
    if(ret!=0){
        perror("cond destory error");
    }
    return 0;
}

在这里插入图片描述

  • 生产者消费者问题
    在这里插入图片描述
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define CONSUMERS_COUNT 2
#define PRODUCERS_COUNT 2
struct Products{
    int data;
    struct Products * next;
};
struct Products *head = NULL;//链表当做共有资源

pthread_mutex_t mutex;//定义互斥量
pthread_cond_t  cond;//定义条件变量
void *consumer(void *arg)
{
    struct Products *temp;
    int num = *(int*)arg;
    free(arg);

    while(1){

        pthread_mutex_lock(&mutex);

        while(head == NULL){
            printf("%d 号消费者: 暂无产品,等待生产者生产\n",num);
            pthread_cond_wait(&cond,&mutex);//等待条件变量唤醒
        }
        printf("%d 号消费者:结束等待\n",num);
        printf("%d 号消费者:开始使用产品\n",num);
        //拿走产品
        temp = head;
        head = temp ->next;
        pthread_mutex_unlock(&mutex);
        printf("%d 号消费者:消费了%d\n",num,temp->data);
        free(temp);
        sleep(1);
    }
}
void *producer(void *arg)
{
    int num = *(int *)arg;
    struct Products *temp;
    free(arg);
    while(1){
        printf("%d 号生产者 开始生产产品\n",num);
        temp = (struct Products*)malloc(sizeof(struct Products));
        temp->data = rand()%1000 + 1;//生产产品;
        printf("%d 号生产者,生产了%d产品\n",num,temp->data);
        //放产品
        pthread_mutex_lock(&mutex);
        temp->next = head;
        head = temp;
        printf("%d 号生产者,放入了产品\n",num);
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}
int main()
{
    int ret;
    int i;
    int *p ;
    pthread_t consumePids[CONSUMERS_COUNT];//消费者线程标识符
    pthread_t producePids[PRODUCERS_COUNT];//生产者线程标识符


    ret = pthread_mutex_init(&mutex,NULL);//创建互斥锁
    if(ret!=0){
        perror("mutex creat error");
    }

    ret = pthread_cond_init(&cond,NULL);//创建条件变量
    if(ret!=0){
        perror("cond creat error");
    }
    for(i = 0;i<CONSUMERS_COUNT;i++){//创建消费者线程
        p = (int *)malloc(sizeof(int));
        *p = i;
        ret = pthread_create(&consumePids[i], NULL, consumer, (void*)p);
        if(ret!=0){
            perror ("pthread_1_create");
        }
    }
    for(i = 0;i<PRODUCERS_COUNT;i++){//创建生产者线程
        p = (int *)malloc(sizeof(int));
        *p = i;
        ret = pthread_create(&producePids[i], NULL, producer, (void*)p);
        if(ret!=0){
            perror ("pthread_1_create");
        }
    }
	 for(i =0 ;i < CONSUMERS_COUNT;i++)
   	 pthread_join(consumePids[i],NULL);//等待消费者进程退出

    for(i =0 ;i < PRODUCERS_COUNT;i++)
    pthread_join(producePids[i],NULL);//等待生产者进程退出

    ret = pthread_mutex_destroy(&mutex);//销毁互斥锁
    if(ret!=0){
        perror("mutex destory error");
    }

    ret = pthread_cond_destroy(&cond);//销毁条件变量
    if(ret!=0){
        perror("cond destory error");
    }
    return 0;
}

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值