实验九 Linux多线程实验
学号:6130116217
专业班级:计算机科学与技术165班
课程名称:Linux程序设计实验
一、实验项目名称
Linux多线程实验
二、实验目的
熟练掌握互斥锁、条件变量以及信号量三种线程同步互斥机制。
三、实验基本原理
Linux线程同步与互斥
四、主要仪器设备及耗材
硬件: PC机;
软件:Windows OS,VMware,Fedora10.0或其他Linux发行版。
五、实验步骤
1. 编写Linux C程序,在主线程中创建一个新线程,且主线程和新线程使用互斥锁共享一个全局变量a(初始值为0), 主线程每隔2秒将a值增加一,并将a输出在屏幕上,新线程每隔3秒将a值减一并且将a输出在屏幕上,新线程执行2分钟后结束,主线程随即也结束。
vim 1.c
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t mutex;
int a;
void mainthread() {
while(1) {
sleep(2);
pthread_mutex_lock(&mutex);
a++;
printf("主线程a加1:a=%d\n",a);
pthread_mutex_unlock(&mutex);
}
}
void add(void *arg) {
for(int i=0; i<40; i++) {
sleep(3);
pthread_mutex_lock(&mutex);
a--;
printf("创建的线程a的值减去1:a=%d\n",a);
pthread_mutex_unlock(&mutex);
}
exit(0);
}
int main(int arg,char *argv[]) {
printf("hello world");
pthread_t id;
int ret;
pthread_mutex_init(&mutex,NULL);
ret=pthread_create(&id,NULL,(void *)add,NULL);
if(ret!=0) {
printf("error");
}
mainthread();
pthread_mutex_destroy(&mutex);
return 1;
}
编译运行结果
2. 调试教材例8.5,例8.7与例8.10
①例8.5
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#define MAX 10
pthread_t thread[2];
pthread_mutex_t mut;
int number=0, i;
void *thread1() {
printf ("thread1 : I'm thread 1\n");
for (i = 0; i < MAX; i++) {
printf("thread1 : number = %d\n",number);
pthread_mutex_lock(&mut);
number++;
pthread_mutex_unlock(&mut);
sleep(2);
}
printf("thread1 :主函数在等我完成任务吗?\n");
pthread_exit(NULL);
}
void *thread2() {
printf("thread2 : I'm thread 2\n");
for (i = 0; i < MAX; i++) {
printf("thread2 : number = %d\n",number);
pthread_mutex_lock(&mut);
number++;
pthread_mutex_unlock(&mut);
sleep(3);
}
printf("thread2 :主函数在等我完成任务吗?\n");
pthread_exit(NULL);
}
void thread_create(void) {
int temp;
memset(&thread, 0, sizeof(thread));
/*创建线程*/
if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) !=
0)
printf("线程1 创建失败!\n");
else
printf("线程1 被创建\n");
if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0)
printf("线程2 创建失败");
else
printf("线程2 被创建\n");
}
void thread_wait(void) {
/*等待线程结束*/
if(thread[0] !=0) {
pthread_join(thread[0],NULL);
printf("线程1 已经结束\n");
}
if(thread[1] !=0) {
pthread_join(thread[1],NULL);
printf("线程2 已经结束\n");
}
}
int main() {
/*用默认属性初始化互斥锁*/
pthread_mutex_init(&mut,NULL);
printf("我是主函数哦,我正在创建线程,呵呵\n");
thread_create();
printf("我是主函数哦,我正在等待线程完成任务阿,呵呵\n");
thread_wait();
return 0;
}
编译运行
②例8.7
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
struct node {
int n_number;
struct node *n_next;
} *head=NULL; /*[thread_func]*/
/*释放节点内存*/
static void cleanup_handler(void*arg) {
printf("Clean up handler of second thread.\n");
free(arg);
(void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg) {
struct node*p=NULL;
pthread_cleanup_push(cleanup_handler,p);
pthread_mutex_lock(&mtx);
//这个mutex_lock主要是用来保护wait等待临界时期的情况,
//当在wait为放入队列时,这时,已经存在Head条件等待激活
//的条件,此时可能会漏掉这种处理
//这个while要特别说明一下,单个pthread_cond_wait功能很完善,
//为何这里要有一个while(head==NULL)呢?因为pthread_cond_wait
//里的线程可能会被意外唤醒,如果这个时候head==NULL,
//则不是我们想要的情况。这个时候,
//应该让线程继续进入pthread_cond_wait
while(1) {
while(head==NULL) {
pthread_cond_wait(&cond,&mtx);
}
//pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
//然后阻塞在等待队列里休眠,直到再次被唤醒
//(大多数情况下是等待的条件成立而被唤醒,唤醒后,
//该进程会先锁定先pthread_mutex_lock(&mtx);,
//再读取资源用这个流程是比较清楚的
/*block-->unlock-->wait()return-->lock*/
p=head;
head=head->n_next;
printf("Got%dfromfrontofqueue\n",p->n_number);
free(p);
}
pthread_mutex_unlock(&mtx);//临界区数据操作完毕,释放互斥锁
pthread_cleanup_pop(0);
return 0;
}
int main(void) {
pthread_t tid;
int i;
struct node *p;
pthread_create(&tid,NULL,thread_func,NULL);
//子线程会一直等待资源,类似生产者和消费者,
//但是这里的消费者可以是多个消费者,
//而不仅仅支持普通的单个消费者,这个模型虽然简单,
//但是很强大
for(i=0; i<10; i++) {
p=(struct node*)malloc(sizeof(struct node));
p->n_number=i;
pthread_mutex_lock(&mtx);//需要操作head这个临界资源,先加锁,
p->n_next=head;
head=p;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);//解锁
sleep(1);
}
printf("thread1wannaendthecancelthread2.\n");
pthread_cancel(tid);
//关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,
//子线程会在最近的取消点,退出线程,而在我们的代码里,最近的
//取消点肯定就是pthread_cond_wait()了。
pthread_join(tid,NULL);
printf("Alldone--exiting\n");
return 0;
}
编译运行
③例8.10
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <time.h>
#include <stdlib.h>
sem_t full,empty; /*信号量,empty的值表示空缓冲区个数,full的值表示空缓冲区中消息的个数,用于生产者和消费者同步,默认值为0 */
pthread_mutex_t mutex; /*互斥量,用于访问缓冲区互斥,缓冲区为临界资源*/
#define BUFFERSIZE 5 /*5个缓冲区*/
struct msgbuf { /*缓冲区结构*/
pid_t id; /*线程id */
time_t mytime; /*时间*/
};
struct msgbuf msg[BUFFERSIZE];
int in=0,out=0;
/*生产者producer(),用于生产者计算当前的时间,把时间、第几次计算时间的序号(循环次数)和线程ID作为一个消息,把消息放入缓冲区,每个线程生产10个消息*/
void *producer(void *arg) {
int i;
time_t rt
for(i=1; i<=10; i++) {
sem_wait(&empty); /*缓冲区个数减1,若缓冲区个数小于等于0,则阻塞本线程*/
pthread_mutex_lock(&mutex);
/*加锁,申请缓冲区资源,若有其他线程在读写缓冲去,则阻塞本线程*/
msg[in].id=pthread_self(); /*获得线程ID,写入缓冲区*/
time(&(msg[in].mytime)); /*获得时间信息,写入缓冲区*/
in=(++in)%5;
printf("生产者%d第%d次写消息---, id=%u, time is: %s\n",arg, i,(unsigned)(msg[in].id),ctime(&(msg[in].mytime)));
pthread_mutex_unlock(&mutex);
/*解锁,释放缓冲区资源,若有线程在等待使用缓冲区,则唤醒该线程*/
sem_post(&full);
/*消息数加1,若有等待读缓冲去消息的消费者线程,则唤醒该线程*/
srand( (unsigned)time(&rt));
sleep(rand()%5);
}
}
/* 消费者consumer,用于消费者从缓冲区读出一个消息并显示消息,每个线程消费10个消息*/
void * consumer (void *arg) {
int i;
time_t rt
for(i=1; i<=10; i++) {
sem_wait(&full); /*消息数减1,若消息个数小于等于0,则阻塞本线程*/
pthread_mutex_lock(&mutex);
/*加锁,申请缓冲区资源,若有其他线程在读写缓冲去,则阻塞本线程*/
printf("消费者%d第%d次读取消息---, id=%u, time is: %s\n",
arg, i,(unsigned)(msg[out].id),ctime(&(msg[out].mytime)));
out=(++out)%5;
pthread_mutex_unlock(&mutex);
/*解锁,释放缓冲区资源,若有线程在等待使用缓冲区,则唤醒该线程*/
sem_post(&empty); /*空缓冲区数加1,若有写缓冲去消息的生产者线程,则唤醒该线程*/
srand( (unsigned)time(&rt));
sleep(3+rand()%5);
}
}
int main(int argc, char *argv[]) {
pthread_t pid1,pid2 ;
pthread_t cid1,cid2;
sem_init(&full,0,0); /*信号量初始化,初始消息个数为0 */
sem_init(&empty,0,5); /*信号量初始化,初始缓冲区个数为5 */
pthread_mutex_init(&mutex,NULL);
/*创建2个生产者线程和2个消费者线程*/
pthread_create(&pid1,NULL, producer,NULL);
pthread_create(&pid2,NULL, producer,NULL);
pthread_create(&cid1,NULL, consumer,NULL);
pthread_create(&cid2,NULL, consumer,NULL);
pthread_join(pid1,NULL); /*等待第1个生产者线程结束*/
pthread_join(pid2,NULL); /*等待第2个生产者线程结束*/
pthread_join(cid1,NULL); /*等待第1个消费者线程结束*/
pthread_join(cid2,NULL); /*等待第2个消费者线程结束*/
pthread_mutex_destroy(&mutex); /*释放互斥量*/
sem_destroy(&full); /*释放信号量*/
sem_destroy(&empty) ; /*释放信号量*/
return 0;
}
编译运行
六、实验数据及处理结果
七、思考讨论题
对互斥锁、条件变量以及信号量三种不同的线程同步机制进行比较
答:1.互斥锁、条件变量、信号量三者的差别:
(1) 互斥锁必须总是由给他上锁的线程解锁(因为此时其他线程根本得不到此锁),信号量没有这种限制:一个线程等待某个信号量,而另一个线程可以挂出该信号量
(2)每个信号量有一个与之关联的值,挂出时+1,等待时-1,那么任何线程都可以挂出一个信号,即使没有线程在等待该信号量的值。不过对于条件变量来说,如果pthread_cond_signal之后没有任何线程阻塞在pthread_cond_wait上,那么此条件变量上的信号丢失。
(3)在各种各样的同步技巧中,能够从信号处理程序中安全调用的唯一函数是sem_post作用域
2.
信号量: 进程间或线程间(linux仅线程间的无名信号量pthread semaphore)
互斥锁: 线程间
上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源
2.
(1)互斥量:互斥量提供对共享资源的保护访问,它的两种状态:lock和unlock,用来保证某段时间内只有一个线程使用共享资源,互斥量的数据类型是pthread_mutex_t
主要涉及函数:pthread_mutex_lock() pthread_mutex_trylock() pthread_mutex_unlock()
Pthreaf_mutex_init() pthread_mutex_destroy()
lock与unlock之间所锁定的区域为临界区域(如果只加锁不解锁程序会阻塞等待)
(2)信号量:信号量是一个特殊的整数值,主要用来控制多个线程(进程)对临界资源的互斥访问,线程根据信号量来判断是否有访问的资源,信号量是一种线程同步机制,信号量与信号不同。
信号量是一个计数器,可用于同步多线程对共享数据对象得访问,为了获得共享资源,线程需要执行以下操作:
1、测试控制该资源的信号量
2、若此信号量的值为正,则线程可以使用该资源,线程将信号量值减1,表示它使用了一个资源单位
3、若此信号量的值为0,则线程进入睡眠状态,直至信号量值大于0。当线程被唤醒后,它返回至第1步。
(3)条件变量: 只用互斥量很可能会引起死锁,为此引入了条件变量,条件变量允许线程阻塞和等待另一个线程发送的信号,使用条件变量可以以原子方式阻塞线程,直到满足某个条件为止,可以避免忙等。
条件变量常和互斥锁一起使用,互斥量主要用来保证对临界区的互斥进入,而条件变量则用于线程的阻塞等待,互斥锁定进入临界区以后,若条件不满足,线程便转为等待状态,等待条件满足后被唤醒执行,否则继续执行,执行完后开锁
条件变量的数据类型是pthread_cond_t
主要涉及函数:pthread_cond_init() pthread_cond_signal() pthread_cond_wait()
Pthread_cond_timewait() pthread_cond_broadcast()
Pthread_cond_wait()函数作用:
1.先对传入的互斥量mutex解锁(自动释放即解锁)
2.再wait阻塞等待(阻塞被别的线程的pthread_cond_singal()唤醒后必须先加锁再执行)
读写锁rwlock:
分为:rdlock(读锁):只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读,共享锁
wrlock(写锁):仅当没有线程持有某个给定的读写锁用于读或者写时,才能分配该读写锁用于写,独占锁
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;(初始化读写锁)
pthread_rwlock_rdlock(&rwlock):分配读锁
pthread_rwlock_wrlock(&rwlock):分配写锁
pthread_rwlock_unlock(&rwlock):解锁(只有解读写锁)
写锁解锁后多个线程在同等条件下分配读锁和写锁时先分配写锁(优先考虑分配写锁,但这不是必须的(系统不同))
八、参考资料
1.金国庆等,Linux程序设计(第二版),浙江大学出版社,2014年4月
2. Neil Matthew,《Linux程序设计》(第4版), 人民邮电出版社,2014年9月
3. 杨宗德,《Linux高级程序设计》(第三版),人民邮电出版社,2012年11月
4. Daniel P.,《深入理解Linux内核》(第三版),中国电力出版社,2013年1月