Bootstrap

OS实验二 线程的创建和运行

实验二 线程的创建和运行

1.实验目的

(1)全面理解Iinux线程的运行机制,掌握编写线程的方法
(2)理解同步机制的作用,掌握解决该问题的算法思想,正确使用同步机制。
(3)学习使用Linux线程同步机制解决互斥和同步问题。

2.实验内容

一、Linux中线程的创建和运行
在主函数中创建一个线程,其功能是循环输出3次“This is the second pthread.”;主线程也循环输出3次“This is the main pthread.”,观察并分析它们的执行过程。注意编译时使用-lpthread选项,比如命令gcc –o pthread pthread.c -lpthread
#include <stdio.h>
#include <pthread.h>		//线程所用头函数  
/*定义线程的执行函数*/
void thread(void)  //必须使用void作为返回类型
{
    int i;
    for(i=0;i<3;i++)
    {
	printf(“This is the second pthread.\n”); 
                                           //显示自己是子线程
    	 sleep(1);
    }
}
main()
{  /*定义线程内部标识 */
 pthread_t threadid;
 int i,ret; 
	/*创建一个子线程并指定执行函数,函数不带参数*/
ret=pthread_create(&threadid,NULL,(void*)thread,NULL);    
if(ret!=0)
{ printf ("Create pthread error!\n");
 exit (1);}
/*主线程循环输出3次*/
  pthread_join(threadid,NULL); //等待子线程结束 
for(i=0;i<3;i++)	
  {
   printf("This is the main pthread.\n");
	sleep(1);
    } exit(0);
   }

(1) 运行结果:在这里插入图片描述
2) 回答问题:Linux中线程是怎样创建和运行的?
通过函数pthread_create()来创建一个线程,如果该函数的返回值为0,则线程创建成功。在pthread_create()中第三个参数标识了线程开始运行的函数的初始位置,会跳转到运行函数的位置来运行线程中的程序。

(3)int pthread_create(pthread_t *thread, pthread_attr_t *attr, void* (*start_routine)(void *), void *arg//指向传递给线程的参数 );
参数的作用分别是什么?

第一个参数是指线程标识符的指针
第二个参数是线程的属性(与pthread_attr定义的线程的属性有关)
第三个是线程开始运行的函数的初始位置
第四个是线程运行函数的参数

(4)pthread_join()函数的作用是什么?

	该函数是使一个线程等待另一个线程,其中的第一个参数为等待线程的pthread_t的值,可以保证所有线程都运行完成之后再退出。如果不使用该函数,则程序在主线程运行结束后就会退出,而此时其他线程还没执行或者没有执行完,所以需要pthread_join()来阻塞主线程。
二、编写多线程程序

设有两个并发执行的线程A、B,共享变量N,假设N初值为0。
A:N=N+1;
B:print(N);N=0;
编写程序,完成以上功能。观察结果并分析结果
提示:共享变量N可以设置为全局变量。如果要让线程交替执行可使用sleep函数。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int i;
int n;// 全局变量,如果未赋初值,就代表其会被编译器赋值为0. 但是目标文件中是不会为这个全局变量分
void A()
{
	do
	{
		for(i=0;i<100;i++)
		n=n+1;
		printf("n=%d in thread A\n",n);
		sleep(1);
	}while(1);
}

void B()
{
	do
	{
		printf("n=%d in thread B\n",n);
		n=0;
		printf("n has been clear 0!\n");
		printf("n=%d in thread B after clear 0\n",n);
		sleep(10);
	}while(1);
}

main()
{  /*定义线程内部标识 */
 	pthread_t threadid1,threadid2;
 	int ret1,ret2; /*创建一个子线程并指定执行函数,函数不带参数*/
	ret1=pthread_create(&threadid1,NULL,(void*)A,NULL);    
	ret2=pthread_create(&threadid2,NULL,(void*)B,NULL);

	pthread_join(threadid1,NULL); //等待子线程结束 
	pthread_join(threadid2,NULL);
}

//在先调度B的情况下,B线程先打印,然后休息10秒;此时A线程抢到cpu,执行A线程,在睡眠10秒结束后,B再次抢到cpu,执行线程B,以此无限循环。
//在先调度A的情况下,A线程先打印,之后B线程抢到了cpu,再执行B线程之后,全局变量被重新赋值为0,A线程再抢到cpu之后,对全局变量从0开始重新循环累加,B线程再抢到就赋值为0.
//先调度B

[root@192 ex-2]# ./ex2-2
[root@192 ex-2]# ./ex2-2
n=0 in thread B
n has been clear 0!
n=0 in thread B after clear 0
n=100 in thread A
n=200 in thread A
n=300 in thread A
n=400 in thread A
n=500 in thread A
n=600 in thread A
n=700 in thread A
n=800 in thread A
n=900 in thread A
n=1000 in thread A
n=1000 in thread B
n has been clear 0!
n=0 in thread B after clear 0
n=100 in thread A
n=200 in thread A
n=300 in thread A
n=400 in thread A
n=500 in thread A
n=600 in thread A
n=700 in thread A
n=800 in thread A
n=900 in thread A
n=1000 in thread A
n=1000 in thread B
n has been clear 0!
n=0 in thread B after clear 0
n=100 in thread A
n=200 in thread A
n=300 in thread A

//先调度A
[root@192 ex-2]# ./ex2-2
[root@192 ex-2]# ./ex2-2
n=100 in thread A
n=100 in thread B
n has been clear 0!
n=0 in thread B after clear 0
n=100 in thread A
n=200 in thread A
n=300 in thread A
n=400 in thread A
n=500 in thread A
n=600 in thread A
n=700 in thread A
n=800 in thread A
n=900 in thread A
n=900 in thread B
n has been clear 0!
n=0 in thread B after clear 0
n=100 in thread A
n=200 in thread A
n=300 in thread A
n=400 in thread A
n=500 in thread A
n=600 in thread A
n=700 in thread A
n=800 in thread A
n=900 in thread A
n=1000 in thread A
n=1000 in thread B
n has been clear 0!

三、mutex的使用实例

1)不使用mutex出现的问题

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter; /* incremented by threads */
void *doit(void *);
int main(int argc, char **argv)
{
pthread_t tidA, tidB; 
pthread_create(&tidA, NULL, &doit, NULL); 
pthread_create(&tidB, NULL, &doit, NULL); /* wait for both threads to terminate */
pthread_join(tidA, NULL); 
pthread_join(tidB, NULL); 
return 0;
}
void *doit(void *vptr)
{ int i, val; /* * Each thread fetches, prints, and increments the counter NLOOP times. * The value of the counter should increase monotonically. */ 
for (i = 0; i < NLOOP; i++) 
{ 
val = counter; 
printf("%x: %d\n", (unsigned int)pthread_self(), val + 1); 
counter = val + 1; 
} 
return NULL;
}

(1) 编译并运行以上程序,运行结果:在这里插入图片描述

(2)观察程序中出现的问题,给出问题出现的原因:

	A和B都是从(void*)doit函数开始运行,但是该函数(临界资源)数据没有加锁,A和B不是互斥访问该函数,两个线程可以同时访问该函数以及数据但两个线程都是共同访问数据区,会出现A线程访问的过程中,B线程访问,但这两者同时访问函数,最后变量只加了一次而不是两次。这样的结果就是输出的counter不确定,counter>=5000。
	
	出现这样问题的原因主要是因为两个线程本该互斥访问该临界资源,结果该资源没有加锁,导致两个线程共同访问数据区(两个线程共享counter的累加结果)。

在这里插入图片描述

2)使用mutex的程序

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter; /* incremented by threads */
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

void *doit(void *);
int main(int argc, char **argv)
{ 
pthread_t tidA, tidB; 
pthread_create(&tidA, NULL, &doit, NULL); 
pthread_create(&tidB, NULL, &doit, NULL); /* wait for both threads to terminate */
pthread_join(tidA, NULL); 
pthread_join(tidB, NULL); 
return 0;
}

void *doit(void *vptr)
{ 
int i, val; /* * Each thread fetches, prints, and increments the counter NLOOP times. * The value of the counter should increase monotonically. */ 
for (i = 0; i < NLOOP; i++) 
{ 
pthread_mutex_lock(&counter_mutex); 
val = counter;
printf("%x: %d\n", (unsigned int)pthread_self(), val + 1); 
counter = val + 1;
pthread_mutex_unlock(&counter_mutex);
} 
return NULL;
}

(1)编译并运行以上程序,观察结果,1)题中问题解决的方法是什么?在这里插入图片描述

 	解决方法就是对函数体中的数据加锁,只允许一个线程访问,而另一个线程等待。两个线程各自将counter累加5000次,最后也就是10000次。等待两个线程都结束后,才会结束。

四、Semaphore的使用实例
下面的程序使用Semaphore,演示了一个生产者-消费者的例子,生产者生产一个随机数放入队列,消费者从队列取走数据。

#include <pthread.h> 
#include <stdio.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM];
sem_t blank_number, product_number;
void *producer(void *arg) 
{ 
static int p = 0; 
while(1)
{ 
sem_wait(&blank_number); 
queue[p] = rand()%1000; 
printf("Produce %d\n", queue[p]); 
p = (p+1)%NUM;
 sleep(rand()%5); 
sem_post(&product_number); 
}
}
void *consumer(void *arg)
 {
 static int c = 0;
 while(1)
{ 
sem_wait(&product_number); 
printf("Consume %d\n", queue[c]); 
c = (c+1)%NUM;
sleep(rand()%5); 
sem_post(&blank_number); 
}
}
int main(int argc, char *argv[]) 
{ 
pthread_t pid, cid; 
sem_init(&blank_number, 0, NUM); 
sem_init(&product_number, 0, 0); 
pthread_create(&pid, NULL, producer, NULL); 
pthread_create(&cid, NULL, consumer, NULL);
 pthread_join(pid, NULL);
 pthread_join(cid, NULL); 
sem_destroy(&blank_number); 
sem_destroy(&product_number);
}

(1)运行结果:在这里插入图片描述

(2) 怎样创建、初始化、撤销信号量?

首先定义了两个sem_t类型的semaphore信号量。
之后在main()函数中通过sem_init(sem_t *sem ,int pshared,unsigned int value)函数初始化两个semaphore信号量,
第一个参数是指semaphore信号量;
第二个参数为0时,说明时信号量是在同一个进程的不同线程之间的信号量;
第三个信号量是值资源的数量。

通过sem_destory(sem_t *sem)来销毁信号量
(3) sem_wait(),sem_post()在程序中起什么作用?

Sem_wait()是获得资源的,并使其中的信号量减一,变为0.如果在调用之前,信号量的之已经为0,则将该线程挂起等待。
Sem_post()是释放资源,是对应的信号量加一,同时唤醒等待该信号量的线程。
;