进程和线程
进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。
线程是进程内的一个相对独立的可执行的单元。若把进程称为任务的话,那么线程则是应用中的一个子任务的执行。
线程和进程十分相似,不同的只是线程比进程小。首先,线程采用了多个线程可共享资源的设计思想;例如,它们的操作大部分都是在同一地址空间进行的。其次,从一个线程切换到另一线程所花费的代价比进程低。再次,进程本身的信息在内存中占用的空间比线程大,因此线程更能允分地利用内存。
多线程例子
使用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