和进程相比,线程的最大优点之一是数据的共享性高。但是在应用的过程中,我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。
´1)线程数据
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很像,在线程内部,各个函数可以像使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。
我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
(1)创建键
函数原型为:extern int pthread_key_create P (pthread_key_t *key,void (*destr_function) (void *));
第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once (pthread_once_t*once_control,void (*initroutine) (void))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。
´在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数 createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。
/*declare a key*/
pthread_key_t myWinKey;
void createWindow()
{
F1_Window *win;
static pthread_once_t once = PTHREAD_ONCE_INIT;
/*call the createMyKey() function ,to create a key */
pthread_once(&once,createMyKey);
/* win point to a new created window*/
win = new F1_Window(0,0,100,100,"Mywindow");
/* set up the new win*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific(myWinKey,win);
}
/*The function which is createMvKey(),create a key,and specific the destructor*/
void createMvKey()
{
pthread_keycreate(&myWinKey,freeWinkey);
}
/* the function freeWinKey ,to release memory*/
void freeWinKey()
{
delete win;
}
´这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数 pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。
´这两个函数的原型如下:
extern int pthread_setspecific P (pthread_key_t key,const void *pointer);
extern void *pthread_getspecific P (pthread_key_t key);
2.互斥锁
´互斥锁用来保证一段时间内只有一个线程在执行一段代码。
´我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。
void writer_function ()
{
while(1)
{ /* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if(buffer_has_item==0)
{
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
void *reader_function(void *rd)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(buffer_has_item==1)
{
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ()
{
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, reader_function, NULL);
writer_function( );
}
3.条件变量
´互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
´条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。
´它的原型为:extern int pthread_cond_init P (pthread_cond_t *cond,const pthread_condattr_t *cond_attr);
´其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。
释放一个条件变量的函数为pthread_cond_destroy(pthread_cond_t cond)
´函数pthread_cond_wait()使线程阻塞在一个条件变量上。
´它的函数原型为:extern int pthread_cond_wait P (pthread_cond_t *cond,pthread_mutex_t *mutex);
´线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数 pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
´另一个用来阻塞线程的函数是pthread_cond_timedwait()
´它的原型为:extern int pthread_cond_timedwait P (pthread_cond_t *cond,pthread_mutex_t *mutex, const struct timespec *abstime);它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
´函数pthread_cond_signal()的原型为:extern int pthread_cond_signal P (pthread_cond_t *cond);它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。
´下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
´count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数 increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。
4.信号量
´信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。
´信号量的数据类型为结构sem_t,它本质上是一个长整型的数。
´函数sem_init()用来初始化一个信号量。
它的原型为:extern int sem_init P (sem_t *sem, int pshared, unsigned int value);
´sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
´函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
´函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。
´函数sem_destroy(sem_t *sem)用来释放信号量sem。
´下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
#include
#include
#include
#define Max 100
int stack[Max][2];
int size;
sem_t sem;
void *readdata1(void *ptr)
{
FILE *fp = fopen("1.dat","r");
size = 1;
while(!feof(fp))
{
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
size--;
fclose(fp);
}
void *readdata2(void *ptr)
{
FILE *fp = fopen("2.dat","r");
size = 1;
while(!feof(fp))
{
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
size--;
fclose(fp);
}
void *hand1(void *ptr)
{
size--;
while(size)
{
sem_wait(&sem);
printf("Plus: %d+%d = %d\n",stack[size][0],stack[size][1],stack[size][0]+stack[size][1]);
--size;
}
}
void *hand2(void *ptr)
{
size--;
while(size)
{
sem_wait(&sem);
printf("Multiply: %d*%d = %d\n",stack[size][0],stack[size][1],stack[size][0]*stack[size][1]);
--size;
}
}
int main()
{
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,hand1,NULL);
pthread_create(&t3,NULL,readdata1,NULL);
pthread_create(&t2,NULL,hand2,NULL);
pthread_create(&t4,NULL,readdata2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
}
程序结果截图: