进程和线程
进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。
线程是进程内的一个相对独立的可执行的单元。若把进程称为任务的话,那么线程则是应用中的一个子任务的执行。
线程和进程十分相似,不同的只是线程比进程小。首先,线程采用了多个线程可共享资源的设计思想;例如,它们的操作大部分都是在同一地址空间进行的。其次,从一个线程切换到另一线程所花费的代价比进程低。再次,进程本身的信息在内存中占用的空间比线程大,因此线程更能允分地利用内存。
多线程例子
使用Pthreads的主要动机是提高潜在程序的性能。 当与创建和管理进程的花费相比,线程可以使用操作系统较少的开销,管理线程需要较少的系统资源。
Pthreads定义了一套C语言的类型、函数与常量,它以pthread.h头文件和一个线程库实现。
#include <pthread.h> #include <stdio.h> #define NUM_THREADS 5 void *PrintHello(void *threadid) { int tid; tid = (int)threadid; printf("Hello World! It's me, thread #%d!\n", tid); pthread_exit(NULL); } int main (int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc, t; for(t=0; t<NUM_THREADS; t++){ printf("In main: creating thread %d\n", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); if (rc){ printf("ERROR; return code from pthread_create() is %d\n", rc); return -1; } } pthread_exit(NULL); }
代码一行行解释。
#1 包含pthread头文件,这个在linux中默认就有,无需配置安装。
#5 -11 定义线程函数,作为后面pthread_create的参数,pthread_exit()为终止当前线程。
#15 定义一个线程句柄数组。
#17-24 创建5个线程。
pthread_create参数:
thread:返回一个不透明的,唯一的新线程标识符。
attr:不透明的线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值。
start_routine:线程将会执行一次的C函数。
arg: 传递给start_routine单个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。
pthread_create()函数允许程序员想线程的start routine传递一个参数。当多个参数需要被传递时,可以通过定义一个结构体包含所有要传的参数,然后用pthread_create()传递一个指向改结构体的指针,来打破传递参数的个数的限制。
所有参数都应该传引用传递并转化成(void*)。
线程安全
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
看一个向量点乘的例子:
#include <pthread.h> #include <stdio.h> #include <malloc.h> #define NUM_THREADS 5 #define VECLEN 100 typedef struct { double *a; double *b; double sum; int veclen; }DotData; pthread_t threads[NUM_THREADS]; pthread_mutex_t mutexsum; DotData dotstr; void *dotproduct(void *arg) { int i,start,end,offset,len; double mysum, *x, *y; offset = (int)arg; len = dotstr.veclen; start = offset*len; end = start + len; x = dotstr.a; y = dotstr.b; mysum = 0; for(i=start; i<end; i++) { mysum += (x[i] * y[i]); } pthread_mutex_lock(&mutexsum); dotstr.sum += mysum; printf("mysum%d:%f\n",(int)arg,mysum); pthread_mutex_unlock(&mutexsum); pthread_exit(NULL); } int main (int argc, char *argv[]) { int i; double *a,*b; void *status; pthread_attr_t attr; a = (double*)malloc(NUM_THREADS*VECLEN*sizeof(double)); b = (double*)malloc(NUM_THREADS*VECLEN*sizeof(double)); for(i=0; i<NUM_THREADS*VECLEN; i++) { a[i] = 2.0; b[i] = a[i]; } dotstr.veclen = VECLEN; dotstr.a = a; dotstr.b = b; dotstr.sum = 0; pthread_mutex_init(&mutexsum,NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); for(i=0; i<NUM_THREADS; i++) { pthread_create(&threads[i],&attr,dotproduct,(void*)i); } pthread_attr_destroy(&attr); for(i=0; i<NUM_THREADS; i++) { pthread_join(threads[i],&status); } printf("Sum = %f\n",dotstr.sum); free(a); free(b); pthread_mutex_destroy(&mutexsum); pthread_exit(NULL); }
在上面的代码中,将两个向量的点乘分别放到5个线程里分块完成,理想的情况下,计算速度提升了5倍。
在dotproduct函数中,操作dotstr的时候,使用了互斥锁。首先开启互斥 pthread_mutex_lock(&mutexsum),然后操作数据,最后打开互斥pthread_mutex_unlock(&mutexsum)。虽然在这里添不添加互斥没有关系,但如果对全局变量的操作更加复杂的时候,比如有乘除法的时候,互斥锁就变得很重要了。
在main函数中,pthread_attr_t表示线程属性结构体,使用的时候需要对此结构体进行初始化,初始化后使用,使用后还要进行去除初始化。pthread_attr_init:初始化,pthread_attr_destory:去除初始化。
设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参考
pthreads 的基本用法 - http://www.ibm.com/developerworks/cn/linux/l-pthred/
POSIX 多线程程序设计 - http://www.cnblogs.com/mywolrd/archive/2009/02/05/1930707.html