linux实验9

实验九 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月

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值