第1关:互斥锁
任务描述
由于同一进程中的多个线程共享全局数据,因此,在多线程编程中如果一个线程对全局变量A进行修改时,而此时又有一个线程正在读取该变量,则有可能会出现数据的不一致性。本关将介绍一种线程同步方式-互斥锁。
本关任务:学会使用互斥锁来实现线程间的同步。
相关知识
在多线程编程中,我们常遇到的问题是当多个线程同时访问共享数据时可能会产生冲突。比如:存在多个线程同时要对一个全局变量进行加一操作,我们知道加一操作需要以下三条指令完成:
从内存中将变量值读取到寄存器中;
将寄存器中的值加一操作;
将寄存器中的值写回内存中;
下图所示:假设有两个线程同时执行以上三条指令,则可能会出现以下情况,线程A将变量值(a=5)读取到寄存器中,并将寄存器中的值加一(a=6),而此时线程B正好将变量的值读取到寄存器中(a=5)。当线程A将寄存器中的值写入内存后,该变量的值完成了加一操作(a=6),而随后线程B也将加一的值写回内存中(a=6),那么最终变量的值只是完成的加一操作,而不是加一再加一。
对于多线程的程序,访问冲突的问题是很普遍的,Linux系统中为了解决线程同步问题引入了互斥锁(mutex)。互斥锁操作主要包括加锁、解锁和测试加锁三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁,因此能够保证同一时刻只运行一个线程执行一个关键部分的代码。
Linux系统中提供了如下几个函数来操作互斥锁:
以上函数我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 函数名。
初始化互斥锁
使用互斥锁前必须先进行初始化操作。在Linux中初始化互斥锁有两种方式,分别是:
(1) 静态赋值法;
(2)使用初始化函数。
1、静态赋值法
静态赋值法是将宏结构常量直接赋值给互斥锁,常见的宏结构常量有如下几个:
PTHREAD_MUTEX_INITIALIZER: (普通锁)当一个线程加锁后,其余请求锁的线程形成等待队列,解锁后按照优先级获取锁;
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP: (嵌套锁)允许一个线程对同一个锁进行多次加锁操作,并通过多次解锁来释放锁;
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP: (检错锁)在同一个线程请求同一个锁的情况下,返回EDEADLK,否则执行的动作与普通锁相同;
例如使用静态赋值法来初始化一个互斥锁变量:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2、函数赋值法
Linux系统提供一个pthread_mutex_init库函数来对互斥锁进行初始化。
pthread_mutex_init函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex attr_t *mutexattr);
参数说明:
mutex:互斥锁变量;
mutexattr:互斥锁属性,如果为NULL则使用默认属性,其他常见属性见下表;
函数返回值说明:
pthread_mutex_init返回值总为0。
加锁操作
对互斥锁初始化后,就可以给互斥锁进行加锁操作。Linux提供了两个库函数来对加锁,分别是:pthread_mutex_lock和pthread_mutex_trylock,这些函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数说明:
mutex:要被执行加锁操作的锁变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
pthread_mutex_lock和pthread_mutex_trylock区别:
用pthread_mutex_lock加锁时,如果mutex已经被锁住,当前尝试加锁的线程就会被阻塞,直到互斥锁被其他线程释放。而pthread_mutex_trylock函数则不同,如果mutex已经被锁住,它将立即返回,返回的错误码为EBUSY,而不是阻塞等待。
解锁操作
有加锁操作就相对应的有解锁操作。Linux提供了一个pthread_mutex_unlock函数来解锁操作,这个函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数说明:
mutex:要被执行解锁操作的锁变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
注销锁操作
当一个互斥锁使用完毕后,必须进行清除。Linux提供了一个pthread_mutex_destroy函数来注销一个互斥锁,这个函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数说明:
mutex:要被执行注销操作的锁变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
注意:如果使用静态初始化来初始化一个互斥锁,则无需使用pthread_mutex_destroy对其注销。
案例演示1:
编写一个程序,使用静态初始化方法来初始化一个互斥锁,并对一个全局变量进行加锁。详细代码如下所示:
#include <stdio.h>
#include <pthread.h>
int globalNumber = 1;
//初始化一个普通互斥锁number_mutex
pthread_mutex_t number_mutex = PTHREAD_MUTEX_INITIALIZER;
void *addNumer(void *arg)
{pthread_mutex_lock(&number_mutex);globalNumber++;pthread_mutex_unlock(&number_mutex);return NULL;
}
int main()
{pthread_t thread1, thread2;pthread_create(&thread1, NULL, addNumer, NULL);pthread_create(&thread2, NULL, addNumer, NULL);pthread_join(thread1, NULL);pthread_join(thread2, NULL);printf("globalNumber = %d\n", globalNumber);return 0;
}
将以上代码保存为mutexThread1.c文件,编译执行。可以看到globalNumber变量变为了3,尽管我们不对globalNumber变量进行加锁,结果可能也是3,但是有可能出现结果为2的情况。
案例演示2:
编写一个程序,使用pthread_mutex_init函数来初始化一个普通的互斥锁,并对一个全局变量进行加锁。详细代码如下所示:
#include <stdio.h>
#include <pthread.h>
int globalNumber = 1;
//初始化一个普通互斥锁number_mutex
pthread_mutex_t number_mutex;
void *addNumer(void *arg)
{pthread_mutex_lock(&number_mutex);globalNumber++;pthread_mutex_unlock(&number_mutex);return NULL;
}
int main()
{pthread_mutex_init(&number_mutex, PTHREAD_MUTEX_TIMED_NP);pthread_t thread1, thread2;pthread_create(&thread1, NULL, addNumer, NULL);pthread_create(&thread2, NULL, addNumer, NULL);pthread_join(thread1, NULL);pthread_join(thread2, NULL);printf("globalNumber = %d\n", globalNumber);pthread_mutex_destroy(&number_mutex);return 0;
}
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
补全ThreadHandler函数中代码,使用互斥锁对position和buffer变量加锁,使其同一时刻只能被一个线程访问。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。
开始你的任务吧,祝你成功!
解答:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//全局互斥锁变量
extern pthread_mutex_t mutex;//全局共享变量
extern char *buffer[3];
extern int position;/************************* 参数arg: 是线程函数的参数
*************************/
void *ThreadHandler(void *arg)
{/********** BEGIN **********/pthread_mutex_lock(&mutex);/********** END **********/buffer[position] = (char *)arg;sleep(1);position++;/********** BEGIN **********/pthread_mutex_unlock(&mutex); /********** END **********/pthread_exit(NULL);
}
第2关:自旋锁
在上一关中,我们介绍了如何使用互斥锁来同步线程,本关将介绍Linux系统中的另一种锁,它就是自旋锁。
本关任务:学会使用自旋锁来实现线程间的同步。
相关知识
在上一关中,我们学习了互斥锁的使用方法。在Linux系统中还有一类锁与互斥锁相似,它就是自旋锁。自旋锁的功能以及使用方法和互斥锁极为相似。自旋锁与互斥锁的主要区别在于,当执行加锁操作时,如果当前锁不可用,对于自旋锁来说,则阻塞后不会让出cpu,会一直忙等待,直到得到锁。而对于互斥锁来说,阻塞后休眠让出cpu。
由于自旋锁不会睡眠,自旋锁一直占用cpu,在未获得锁的情况下,一直运行,所以占用着cpu,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。因此,自旋锁通常用于驱动程序开发中,适合对短暂处理的资源进行加锁。
注意:在单处理器环境下,我们是不需要自旋锁,尽管调用了自旋锁的函数,里面也不是自旋锁的实现。
Linux系统中提供了如下几个函数来操作自旋锁:
以上函数我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 函数名。
初始化自旋锁
使用自旋锁前必须先进行初始化操作。初始化自旋锁的库函数是pthread_spin_init。
pthread_spin_init函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
参数说明:
lock:自旋锁变量;
pshared:自旋锁属性,常见属性见下表;
函数返回值说明: 调用成功,返回值总为0,否则返回一个非零的错误码。
加锁操作
对自旋锁初始化后,就可以给自旋锁进行加锁操作。Linux提供了两个库函数来对加锁,分别是:pthread_spin_lock和pthread_spin_trylock,这些函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
参数说明:
lock:要被执行加锁操作的锁变量
函数返回值说明:
调用成功,返回值为0,否则,返回一个非零的错误码。
pthread_spin_lock和pthread_spin_trylock区别:
用pthread_spin_lock加锁时,如果lock已经被锁住,当前尝试加锁的线程就会被阻塞,直到自旋锁被其他线程释放。而pthread_spin_trylock函数则不同,如果lock已经被锁住,它将立即返回,返回的错误码为EBUSY,而不是阻塞等待。
解锁操作
有加锁操作就相对应的有解锁操作。Linux提供了一个pthread_spin_unlock函数来解锁操作,这个函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_spin_unlock(pthread_spinlock_t *lock);
参数说明:
lock:要被执行解锁操作的锁变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
注销锁操作
当一个自旋锁使用完毕后,必须进行清除。Linux提供了一个pthread_spin_destroy函数来注销一个自旋锁,这个函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_spin_destroy(pthread_spinlock_t *lock);
参数说明:
lock:要被执行注销操作的锁变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
案例演示1:
编写一个程序,使用自旋锁对一个全局变量进行加锁。详细代码如下所示:
#include <stdio.h>
#include <pthread.h>
int globalNumber = 1;
//初始化一个普通自旋锁number_lock
pthread_spinlock_t number_lock;
void *addNumer(void *arg)
{pthread_spin_lock(&number_lock);globalNumber++;pthread_spin_unlock(&number_lock);return NULL;
}
int main()
{pthread_spin_init(&number_lock, PTHREAD_PROCESS_PRIVATE);pthread_t thread1, thread2;pthread_create(&thread1, NULL, addNumer, NULL);pthread_create(&thread2, NULL, addNumer, NULL);pthread_join(thread1, NULL);pthread_join(thread2, NULL);printf("globalNumber = %d\n", globalNumber);pthread_spin_destroy(&number_lock);return 0;
}
将以上代码保存为lockThread.c文件,编译执行。可以看到globalNumber变量变为了3,尽管我们不对globalNumber变量进行加锁,结果可能也是3,但是有可能出现结果为2的情况。
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
补全ThreadHandler函数中代码,使用自旋锁对position和buffer变量加锁,使其同一时刻只能被一个线程访问。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。
开始你的任务吧,祝你成功!
解答:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//全局自旋锁变量
extern pthread_spinlock_t lock;//全局共享变量
extern char *buffer[3];
extern int position;/************************* 参数arg: 是线程函数的参数
*************************/
void *ThreadHandler(void *arg)
{/********** BEGIN **********/pthread_spin_lock(&lock);/********** END **********/buffer[position] = (char *)arg;sleep(1);position++;/********** BEGIN **********/pthread_spin_unlock(&lock);/********** END **********/pthread_exit(NULL);
}
第3关:条件变量
在以上两关中,我们介绍了如何互斥锁和自旋锁的使用方式,并且使用互斥锁和自旋锁来同步线程,本关我们将学习另一种同步线程的方式。
本关任务:学会使用条件变量来实现线程间的同步。
相关知识
条件变量是利用线程间共享的全局变量进行同步的一种机制。条件变量宏观上类似if语句,符合条件就能执行某段代码,否则,只能等待条件成立。使用条件变量主要包括两个动作,分别为:(1)一个等待使用资源的线程等待"条件变量被设置为真";(2)一个正在使用资源的线程在使用完资源后"设置条件为真";这样就可以保证线程间同步。但是,这样存在一个关键的问题,那就是要保证条件变量能够被正确的修改,因此,条件变量要受到特殊的保护才行。实际上使用互斥锁来扮演着这样的一个保护者的角色。注意:使用的互斥锁必须是普通的互斥锁
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件的检测是在互斥锁的保护下进行的。线程在改变条件状态之前必须首先锁住互斥量。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,这些线程将重新锁定互斥锁并重新测试条件是否满足。
Linux 系统中提供了如下几个函数来操作条件变量:
初始化条件变量
使用条件变量前必须先进行初始化操作。在Linux中初始化条件变量有两种方式,分别是:
(1)静态赋值法;
(2)使用初始化函数。
静态赋值法
静态赋值法是直接将宏结构常量直接赋值给条件变量,将宏结构变量PTHREAD_COND_INITIALIZER赋值给条件变量即可,例如:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
函数赋值法
Linux 系统提供一个pthread_cond_init库函数来对条件变量进行初始化。
pthread_cond_init函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
参数说明:
cond:条件变量;
cond_attr:条件变量属性,由于该属性在实际中没有被实现,所以它的值通常是NULL;
函数返回值说明:
调用成功,返回值为0,否则,返回一个非零的错误码。
等待条件成立函数
Linux 提供了两个库函数用来判断等待条件成立,分别是:pthread_cond_wait和pthread_cond_timedwait,这些函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
参数说明:
cond:等待判断的条件变量;
mutex:互斥锁;
abstime:等待的时间;
函数返回值说明:
调用成功,返回值为0,否则,返回一个非零的错误码。
pthread_cond_wait和pthread_cond_timedwait区别:
pthread_cond_timedwait函数将阻塞直到条件变量获取变为真(获得信号)或者经过由abstime指定的时间,也就是说,如果在给定的时间前条件变量没有满足,则返回ETIMEOUT并结束等待。
激活条件变量
当线程被条件变量所阻塞后,需要对其激活。Linux 提供了两个函数来激活条件变量,分别是:pthread_cond_signal和pthread_cond_broadcast,这些函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
参数说明:
cond:需要被激活的条件变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
注意:pthread_cond_signal激活一个等待条件变量成立的线程,当存在多个等待线程时,按照入队顺序激活其中的一个;而pthread_cond_broadcast则是激活所有等待的线程。
注销条件变量
当一个条件变量使用完毕后,必须进行清除。Linux 提供了一个pthread_cond_destroy函数来注销一个条件变量,这个函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_cond_destroy(pthread_cond_t *cond);
参数说明:
cond:需要被注销的条件变量
函数返回值说明:
调用成功,返回值为0,否则返回一个非零的错误码。
注意:只有在没有线程等待一个条件变量时,该条件变量才能被注销,否则返回EBUSY。
案例演示1:
编写一个程序,使用条件变量对一个全局变量进行加锁。详细代码如下所示:
#include <stdio.h>
#include <pthread.h>
int globalNumber = 1;
//初始化一个普通互斥锁number_lock
pthread_mutex_t number_lock;
//初始化一个条件变量number_cond
pthread_cond_t number_cond;
void *addNumer(void *arg)
{pthread_mutex_lock(&number_lock);pthread_cond_wait(&number_cond, &number_lock);globalNumber++;pthread_mutex_unlock(&number_lock);return NULL;
}
int main()
{pthread_cond_init(&number_cond, NULL);pthread_t thread1, thread2;pthread_create(&thread1, NULL, addNumer, NULL);pthread_create(&thread2, NULL, addNumer, NULL);//激活所有等待的线程sleep(1); //保证有线程处于等待状态后再发激活信号pthread_cond_broadcast(&number_cond);pthread_join(thread1, NULL);pthread_join(thread2, NULL);printf("globalNumber = %d\n", globalNumber);pthread_cond_destroy(&number_cond);return 0;
}
将以上代码保存为condThread.c文件,编译执行。可以看到globalNumber变量变为了3。注意:我们需要在激活条件变量前要等待所有线程处于等待条件变量状态,这样才能激活等待的线程。
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
补全ThreadHandler1和ThreadHandler2函数中代码.
ThreadHandler1函数中对position变量进行加一操作(只有一个线程使用,无需加锁),当position变量被加一后,则只通知一个线程执行ThreadHandler2函数完成字符串赋值操作。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。
开始你的任务吧,祝你成功!
解答:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>//全局互斥锁变量和条件变量
extern pthread_mutex_t mutex;
extern pthread_cond_t cond;//全局共享变量
extern char *buffer[3];
extern int position;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
/************************* 参数arg: 是线程函数的参数
*************************/
void *ThreadHandler1(void *arg)
{int i;for(i = 0; i < 3; i++){usleep(500);position++;//通知ThreadHandler2函数执行赋值操作/********** BEGIN **********/pthread_cond_signal(&has_product);/********** END **********/}pthread_exit(NULL);
}/************************* 参数arg: 是线程函数的参数
*************************/
void *ThreadHandler2(void *arg)
{/********** BEGIN **********/pthread_mutex_lock(&mutex);pthread_cond_wait(&has_product, &mutex);/********** END **********/buffer[position] = (char *)arg;/********** BEGIN **********/pthread_mutex_unlock(&mutex);/********** END **********/pthread_exit(NULL);
}
第4关:项目实战
任务描述
本关任务:利用互斥锁和条件变量实现一个生产者消费者模型。
相关知识
生产者消费者模型一个著名的同步问题。它描述的是:多个生产者来生产产品,并将这些产品提供给消费者去消费。为使生产者和消费者能够并发执行。在两者之间设置了一个公共区域,生产者进入公共区域生产产品并放入其中。消费者进入公共区域并取走产品进行消费。
生产者消费者模型需要满足如下规则:当一个生产者进入公共区域生产产品时,其他生产者和消费者不能同时进入公共区域生产产品或消费产品。当一个消费者进入公共区域消费产品的时候,其它消费者和生产者不能同时进入该区域消费产品或生产产品。也就是说,任意时刻,最多只允许一个生产者或一个消费者进入公共区域。即生产者和消费者必须互斥的访问公共区域。
通过前3关的学习,我们现在知道如何互斥锁、自旋锁和条件变量来同步线程。那么利用以上3关的知识就可以简单的生产者消费者模型。
实现生产者消费者模型
一个普通的生产者消费者模型需要满足以下标准:
(1)生产者与生产者之间存在竞争即互斥关系;
(2)消费者与消费者之间存在竞争即互斥关系;
(3)生产者与消费者之间存在互斥与同步关系。
案例演示1:
通过互斥锁和条件变量实现生产者消费者模型。我们使用链表来存在数据,生产者向数据区生产随机数,消费者从数据区读取生产好的数据并打印出来。我们设置当生产者与消费者满足特定的生产与消费标准时,则退出程序。
详细的代码设计为:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
struct msg
{int data; //存放生产的数据struct msg *next;
};
pthread_cond_t cond; //条件变量
pthread_mutex_t mutex; //互斥锁
const int max_number = 10; //生产者与消费者的最大生产和消费任务
int producer_number = 0; //生产者已经生产的任务数
int consumer_number = 0; //消费者已经消费的任务数
struct msg *begin = NULL; //定义数据区头
struct msg *end = NULL; //定义数据区尾
/** 生产者线程*/
void *producer(void *arg)
{while(1){pthread_mutex_lock(&mutex);//判断生产者生产的数据量是否满足需求(一共生产max_number多个数据)if(producer_number == max_number){pthread_mutex_unlock(&mutex);pthread_exit(NULL);}//生产数据if(begin == NULL)begin = end = malloc(sizeof(struct msg));elseend = end->next = malloc(sizeof(struct msg));end->data = rand()%100;producer_number++;//激活所有消费者(注意:这里不能用pthread_cond_signal函数来激活)//pthread_cond_signal函数只能一次激活一个线程pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mutex);}
}
/** 消费者线程*/
void *consumer(void *arg)
{while(1){pthread_mutex_lock(&mutex);//判断当前是否有数据可用来消费while(begin == NULL){//判断消费者消费的数据量是否满足需求(一共消费max_number多个数据)if(consumer_number == max_number){pthread_mutex_unlock(&mutex);pthread_exit(NULL);}//如果没有数据可消费,则睡眠当前线程pthread_cond_wait(&cond, &mutex);}//消费数据printf("Consumer: %d\n", begin->data);consumer_number++;//将消费后的数据释放掉struct msg *tmp = begin;begin = begin->next;free(tmp);pthread_mutex_unlock(&mutex);}
}
int main(int argc, char *argv[])
{//设置随机数种子srand(time(NULL));//初始化条件变量和互斥锁pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);pthread_t thread[5];int i;//创建2个生产者用来生产数据for(i = 0; i < 2; i++)pthread_create(&thread[i], NULL, producer, NULL);//创建3个消费者用来消费数据for(i = 2; i < 5; i++)pthread_create(&thread[i], NULL, consumer, NULL);//等待生产者和消费者结束for(i = 0; i < 5; i++)pthread_join(thread[i], NULL);pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}
将以上代码保存为ProducerConsumer.c文件中,编译执行。
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
1、补全Consumer函数,该函数是用来消费由生产者产生的数据。
2、产生者生产的数据存放在一个结构体struct Data中的number变量中。
3、消费的方式是直接将数据打印出来即可(格式为:printf("%d\n", Data->number)),并且将该数据从链表中删除(参考案例演示1)。
4、当其中一个消费者遇到消费的数据为-1时,停止消费,并将其它的消费者也停止,退出线程(注意:-1数据不需要打印出来)。
5、提示:当遇到消费数据为-1时,退出线程,并且不删除该数据,那么其它消费线程也会消费到该条数据,并且也会退出,这样就可以实现所有消费者的退出。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。
开始你的任务吧,祝你成功!
解答:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>struct Data
{int number; //存放生产的数据struct Data *next;
};//定义数据区头和尾
extern struct Data *beginData; const int max_number = 10; //生产者与消费者的最大生产和消费任务
int consumer_number = 0; //消费者已经消费的任务数
//全局互斥锁变量和条件变量
extern pthread_mutex_t mutex;
extern pthread_cond_t cond;/************************* 参数arg: 是线程函数的参数
*************************/
void *Consumer(void *arg)
{while(1){/********** BEGIN **********/pthread_mutex_lock(&mutex);while(beginData == NULL){ pthread_cond_wait(&cond, &mutex);}if(beginData->number==-1){pthread_mutex_unlock(&mutex);pthread_exit(NULL);} //消费数据printf("%d\n", beginData->number);consumer_number++;//将消费后的数据释放掉struct msg *tmp = beginData;beginData = beginData->next;free(tmp);pthread_mutex_unlock(&mutex); /********** END **********/}pthread_exit(NULL);
}