Pthread 学习(1)

使用Pthread进行共享内存编程

1.简介

        使用POSIX线程库来实线共享内存的访问,线程大体上是轻量级的进程,进程是正在运行(或挂起)的程序的一个实例,除了
可执行代码外,它还包括 栈段,堆段,系统为进程分配的资源描述符(如文件描述符),安全信息(如进程允许访问的硬件和软件资源),描述进程状态的信息(程序计数器的数值等)
        典型的共享内存“进程”允许了进程间互相访问各自内存区域,事实上,除了他们各自拥有独立的栈和程序计数器外,为了方便,它们基本上可以共享所有其他区域,为了方便管理,一般的方法是启动一个进程,然后由这个进程生成这些“轻量级”进程。
        “轻量级”进程更通用的术语是线程。POSIX线程库是一个类UNIX操作系统上的标准库,定义了一套多线程编程应用程序的编程接口。Pthreads的API 只有在支持POSIX的系统(Linux, Max OS X, Solaris等)上才有效。

2一个简单的Pthread程序

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int thread_count;

void* Hello(void* rank);

int main(int argc,char* argv[])
{
	long thread;

	thread_count=strtol(argv[1],NULL,10);

	pthread_t thread_handles[thread_count];

	for(thread=0;thread<thread_count;thread++)
		pthread_create(&thread_handles[thread],NULL,Hello,(void*)thread);

	printf("Hello from the main thread\n");

	for(thread=0;thread<thread_count;thread++)
		pthread_join(thread_handles[thread],NULL);

	return 0;
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              

void* Hello(void* rank)
{
	long my_rank=(long) rank;

	printf("Hello from thread %ld of %d\n",my_rank,thread_count);

	return NULL;
}

编译时链接到pthread 库即可,  gcc -o hello hello.c -lpthread

运行:

./hello 5 

输出类似于:输出是并不确定的,因为每个线程先后都不一定

Hello from thread 3 of 5
Hello from the main thread
Hello from thread 1 of 5
Hello from thread 2 of 5
Hello from thread 0 of 5
Hello from thread 4 of 5


程序分析:

thread_count是一个全局变量,在Pthread程序中,全局变量被所有线程所共享,全局变量可能会在程序中引发令人困惑的错误,应该限制使用全局变量的使用,
除了确实需要用到的情况外,比如线程之间共享变量。


首先要定义个pthread_t 对象,来左右线程的调用对象,然后在创建线程之前要为这个pthread_t对象分配内存空间,pthread_t 数据结构用来存储线程
专有信息,由pthread.h声明。


调用pthread_create函数来生成线程,语法为,其中in和out只是为了说明此变量为输入变量还是输出变量

int pthread_create(
		pthread_t*         	thread_p,                        //out
		const pthread_attr_t* 	attr_p,			         //in
		void*           	(*start_routine)(void* arg_p)    //in
		void*                   arg_p			         //in
)

第一个参数是一个指针,指向对应的pthread_t 对象,必须在调用pthrad_create前就为pthread_t对象
分配内存空间;
第二个参数可以用NULL(缺省值)
第三个参数表示该线程将要运行的函数,此函数的形式通常为  void* thread_function(void* args_p)
最后一个参数是一个指针,只想传给start_routine的参数

给线程标注编号是由好处的,因为pthread_t对象是不透明的,所以不能用来输出,通过赋予线程编号,可以帮助我们了解在程序出错时是哪个线程发生了错误

运行main函数的线程一般称为主线程,没有参数用于指定线程在哪个核上运行,线程的调度是由操作系统来控制的。


调用pthread_join将等待pthread_t对象所关联的那个线程结束。
int pthread_join(
		
		pthread_t  thread ,               //in
		void**     rat_val_p		  //out
)

调用一次pthread_join 将等待pthread_t 对象所关联的那个线程结束
第二个参数可以接收任意由pthread_t 对象所关联的那个线程结束


关于线程启动的一些认识:
在上面的例程中是通过键入参数来决定生成多少个线程,然后由主线程来生成这些“辅助”线程。

还有一种做法是,请求到来后,主线程启动辅助线程来进行请求处理,例如WEB服务器

需要知道的是,线程的启动开销是比较大的,所以“按需启动线程”也许并不是使应用程序性能最优化的理想方法



二.临界区

当多个线程需要更新同一内存单元时,如果至少其中一个访问是更新操作,那么这些访问就可能会导致某种错误,我们称为竞争条件


三,忙等待

使用一个共享的标识量flag,在下面的程序中,当线程数为2时,运行时间为18秒左右,当线程数目为1时,运行时间仅需要2秒,这主要是忙等待中:while(flag!=my_rank)语句,flag初始化的值为0,所以在线程0完成临界区运算并将flag加1之前,线程1必须等待,同理,当线程1进入临界区后,线程0必须等待线程1完成运算。所以,线程不停的在等待和运行之间切换,所以是非常耗时的!

int flag=0;
int n=100000000;
int thread_count=2;
void* Thread_sum(void* rank);
double sum;


int main()
{
	long thread;

	pthread_t thread_handles[thread_count];
	clock_t start_time=clock();  

	for(thread=0;thread<thread_count;thread++)
		pthread_create(&thread_handles[thread],NULL,Thread_sum,(void*)thread);

	for(thread=0;thread<thread_count;thread++)
		pthread_join(thread_handles[thread],NULL);

	printf("%lf\n",sum*4);
	clock_t end_time=clock(); 
	double runtime=(double)(end_time-start_time)/CLOCKS_PER_SEC;
	printf("Running time is %f S:\n",runtime);


	return 0;
}



void* Thread_sum(void* rank)
{
	long my_rank=(long)rank;
	double factor;
	long long i;
	long long my_n=n/thread_count;
	long long my_fisrt_i=my_n*my_rank;
	long long my_last_i=my_fisrt_i+my_n;

	if(my_fisrt_i%2==0)
		factor=1.0;
	else
		factor=-1.0;

	for(i=my_fisrt_i;i<my_last_i;i++,factor=-factor)
	{
		while(flag!=my_rank);
		sum+=factor/(2*i+1);
		flag=(flag+1)%thread_count;
	}

	return NULL;
}

忙等待不是保护临界区的唯一方法,事实上,还有很多更好的方法,然而,因为临界区中的代码一次只能由一个线程运行,所以无论如何限制访问临界区,都必须串行地执行其中的代码。如果可能的话,我们应该 执行临界区的次数。能够大幅度提高性能的一个方法是:给每个线程配置私有变量来存储各个部分的和,然后for循环一次性将所有部分和加在一起算出总和。

void* Thread_sum(void* rank)
{
	long my_rank=(long)rank;
	double factor,my_sum=0.0;
	long long i;
	long long my_n=n/thread_count;
	long long my_fisrt_i=my_n*my_rank;
	long long my_last_i=my_fisrt_i+my_n;

	if(my_fisrt_i%2==0)
		factor=1.0;
	else
		factor=-1.0;

	for(i=my_fisrt_i;i<my_last_i;i++,factor=-factor)
	{
		my_sum+=factor/(2*i+1);
	}


	while(flag!=my_rank);
	printf("this is thread %ld\n",my_rank);
	sum+=my_sum;
	flag=(flag+1)%thread_count;


	return NULL;
}


四.互斥量

互斥量是互斥锁的简称,它是一个特殊类型变量的简称,通过某些特殊类型的函数,互斥量可以用来限制每次只有一个线程能够进入临界区。

Pthreads标准为互斥量提供了一个特殊类型:pthread_mutex_t,在使用pthread_mutex_t类型之前,必须由系统对其进行初始化,初始化函数为:

int pthread_mutex_init(
       
       pthread_mutex_t*              mutex_p,
       const  pthread_mutexattr_t*   attr_p       
)
我们不使用第二个参数,给这个参数赋值NULL即可,当一个pthread程序使用完互斥量,它应该调用

int pthread_mutex_destroy(
      pthread_mutex_t*       mutex_p
)

要获得临界区的访问权,线程需要调用
int pthread_mutex_lock(
     pthread_mutex_t *   mutex_p
)

当线程退出临界区后,线程应该调用:

int pthread_mutex_unlock(
     pthread_mutex_t*      mutex_p

)

通过声明一个全局的互斥量,可以在全局求和的程序中用互斥量来代替忙等待,主线程对互斥量进行初始化,当线程进入临界区前调用pthread_mutex_lock,在执行完临界区中的所有操作后调用pthread_mutex_unlock.。第一个调用pthrad_mutex_lock的线程

会为临界区上锁,其他线程如果想要进入临界区,也需要调用pthread_mutex_lock,这些调用了pthread_mutex_lock的线程都会阻塞并等待,直到第一个线程调用了pthread_mutex_unlock离开临界区

long n=500000000;
int thread_count=10;
void* Thread_sum(void* rank);
double sum;

pthread_mutex_t mutex;

int main()
{
	long thread;

    pthread_mutex_init(&mutex,NULL);


	pthread_t thread_handles[thread_count];
	clock_t start_time=clock();  

	for(thread=0;thread<thread_count;thread++)
		pthread_create(&thread_handles[thread],NULL,Thread_sum,(void*)thread);

	for(thread=0;thread<thread_count;thread++)
		pthread_join(thread_handles[thread],NULL);

	printf("%lf\n",sum*4);
	clock_t end_time=clock(); 
	double runtime=(double)(end_time-start_time)/CLOCKS_PER_SEC;
	printf("Running time is %f S:\n",runtime);


	return 0;
}



void* Thread_sum(void* rank)
{
	long my_rank=(long)rank;
	double factor,my_sum=0.0;
	long long i;
	long long my_n=n/thread_count;
	long long my_fisrt_i=my_n*my_rank;
	long long my_last_i=my_fisrt_i+my_n;

	if(my_fisrt_i%2==0)
		factor=1.0;
	else
		factor=-1.0;

	for(i=my_fisrt_i;i<my_last_i;i++,factor=-factor)
		my_sum+=factor/(2*i+1);

	pthread_mutex_lock(&mutex);
	sum+=my_sum;
	pthread_mutex_unlock(&mutex);


	return NULL;
}


在使用互斥量的多线程程序中,多个线程进入临界区的顺序是随机的,线程顺序由系统负责分配








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值