Pthread 学习(1)

来源:互联网 发布:linux message日志分析 编辑:程序博客网 时间:2024/05/18 03:32

使用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,                        //outconst pthread_attr_t* attr_p,         //invoid*           (*start_routine)(void* arg_p)    //invoid*                   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 ,               //invoid**     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;elsefactor=-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;elsefactor=-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;elsefactor=-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;}


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








0 0
原创粉丝点击