关于多线程编程

多线程的优点:

1.运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右。

2.使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。 

3. 提高应用程序响应。

4. 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
5. 
改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

 

 

其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4种操作,分别是创建,销毁,加锁和解锁。条件操作有 5种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。

 

 

编程经常用到的函数:pthread_join,pthread_exit,getpid,pthread_self,pthread_create,pthread_mutex_lock,pthread_mutex_unlock,pthread_mutex_init,pthread_mutexattr_init,pthread_mutexattr_setpshared,pthread_mutexattr_settype,pthread_cond_signal,pthread_cond_timewait,pthread_detach,

 

pid_t getpid(void):获得进程id.

 pthread_t pthread_self(void):获得线程id.

 

关于线程:

1.常用的三个函数(pthread_t: typedefunsigned long int pthread_t;)

int pthread_create(pthread_t*restrict tidp,const pthread_attr_t * restrict attr,void*(*start_rtn)(void),void *restrictarg);//成功返回0.常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了。后者表示第二个参数代表的线程属性值非法。

 

pthread_join和pthread_exit

int pthread_join(pthread_t thread,void**value_ptr);//value_ptr线程退出返回的指针。成功结束进程为0,否则为错误编码

第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线的资源被收回。

注意:thread所表示的线程必须是处于非detached(游离)状态。如果处于游离状态,那么该函数调用将返回错误。如果不关心一个线程的结束状态,那也可以将一个线程设置为游离状态。

 

votid pthread_exit(void *value_ptr);

其唯一的参数是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给pthread_join中的value_ptr。

 

2.线程的属性

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。


绑定:pthread_attr_setscope(a,b);//b有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)

例:#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;


pthread_attr_init(&attr);
pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid,&attr, (void *) my_function,NULL);

 

分离:线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态.pthread_attr_setdetachstate(pthread_attr_t*attr,int detachstate),第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD_CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

 

#iinclude<pthread.h>

 

void*child_thread(void *arg)

{

printf(“child thread run!\n”);

}

 

int main(intargc,char *argv[ ])

{

     pthread_t tid;

     pthread_attr_t attr;

 

     pthread_attr_init(&attr);

     pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

     pthread_create(&tid,&attr,fn,arg);

     pthread_attr_destroy(&attr);

     sleep(1);

}



 

线程优先级:它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。

 

#include<pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr,&param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr,&param);
pthread_create(&tid, &attr, (void*)myfunction, myarg);

 


 

继承性:函数pthread_attr_setinheritschedpthread_attr_getinheritsched分别用来设置和得到线程的继承性

 

intpthread_attr_getinheritsched(const pthread_attr_t *attr,int*inheritsched);

intpthread_attr_setinheritsched(pthread_attr_t *attr,intinheritsched);

 

 

 

 

 这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是继承性或指向继承性的指针。继承性决定调度的参数是从创建的进程中继承还是使用在schedpolicyschedparam属性中显式设置的调度信息。Pthreads不为inheritsched指定默认值,因此如果你关心线程的调度策略和参数,必须先设置该属性。

      继承性的可能值是PTHREAD_INHERIT_SCHED(表示新现成将继承创建线程的调度策略和参数)和PTHREAD_EXPLICIT_SCHED(表示使用在schedpolicyschedparam属性中显式设置的调度策略和参数)。

      如果你需要显式的设置一个线程的调度策略或参数,那么你必须在设置之前将inheritsched属性设置为PTHREAD_EXPLICIT_SCHED.


举例:

      int policy=0;

     pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED); 

pthread_attr_getschedpolicy(&attr,&policy);

max_priority =sched_get_priority_max(policy);

       min_priority = sched_get_priority_min(policy);

param.sched_priority=min_priority;

pthread_attr_setschedparam(&attr,&param);


 

简单的例子:

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

struct menber
{
 
   inta;
 
   char*s;
};

void *create(void *arg)
{
 
   structmenber *temp;
 
   temp=(structmenber *)arg;//不需要再分配空间
 
  printf("menber->a = %d \n",temp->a);
 
  printf("menber->s = %s \n",temp->s);
 
   return (void*)0;
}

int main(int argc,char *argv[])
{
 
   pthread_ttidp;
 
   interror;
 
   structmenber *b;
 
   b=(structmenber *)malloc( sizeof(struct menber) );//已分配空间
 
  b->a = 4;
 
  b->s = "zieckey";

 
   error =pthread_create(&tidp, NULL,create, (void*)b);

 
   if(error )
 
  {
 
     printf("phread is not created...\n");
 
      return-1;
 
   }
 
  sleep(1);
 
  printf("pthread is created...\n");
 
   return0;
}

 
编译方法:

gcc -Wall pthread_struct.c -lpthread


 
  执行结果:
menber->a = 4 

menber->s = zieckey 

pthread is created...

 

 例程总结:
 
  可以看出来main函数中的一个结构体传入了新建的线程中。
 
  线程包含了标识进程内执行环境必须的信息。他集成了进程中的所有信息都是对线程进行共享的,包括文本程序、程序的全局内存和堆内存、栈以及文件描述符。

 

 

3. 关于线程数据:

 

在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD:Thread-SpecificData)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。

 


创建键的函数原型为:

extern int pthread_key_create ((pthread_key_t *__key,void(*__destr_function) (void *))); 

//第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once((pthread_once_t*once_control, void (*initroutine)(void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。


  在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createMyWin,这个函数定义一个图形窗口(数据类型为Fl_Window*,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。


 

pthread_key_t myWinKey;

 

void createMyWin( void ) {

 Fl_Window * win;

 static pthread_once_t once= PTHREAD_ONCE_INIT;

 pthread_once ( & once, createMyKey) ;

 win=new Fl_Window( 0, 0, 100, 100, "MyWindow");

 setWindow(win);

 pthread_setpecific ( myWinKey, win);

}


 

void createMyKey ( void ) {

 pthread_key_create(&myWinKey, freeWinKey);

}


 

void freeWinKey ( Fl_Window * win){

 delete win;

这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:

extern int pthread_setspecific((pthread_key_t __key,__const void *__pointer));

extern void *pthread_getspecific((pthread_key_t __key)); 

这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。




 

 

关于互斥锁:

1.常用的几个函数

pthread_mutex_init,pthread_mutex_destroy,pthread_mutex_lock(pthread_mutex_trylock是它的一个非阻塞版本),pthread_mutex_unlock,


互斥锁的属性: 如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
注意:pthread_互斥锁是多线程编程中基本的概念,在开发中被广泛使用。其调用次序层次清晰简单:建锁,加锁,解锁,销毁锁。但是需要注意的是,与诸如 Windows 平台的互斥变量不同,在默认情况下,Linux 下的同一线程无法对同一互斥锁进行递归加锁,否则将发生死锁。


pthread_mutex_t *theMutex = new pthread_mutex_t;
pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr); 
 pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP); // 设置 recursive 属性 pthread_mutex_init(theMutex,&attr);
 
关于条件变量:建议在 Linux 平台上要触发条件变量之前要检查是否有等待的线程,只有当有线程在等待时才对条件变量进行触发。
常见的函数:pthread_cond_init,pthread_cond_destroy,pthread_cond_wait,pthread_cond_timedwait,pthread_cond_signal,pthread_cond_broadcast。
条件变量的结构为pthread_cond_t.
函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
extern int pthread_cond_init ((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 ((pthread_cond_t *__cond,pthread_mutex_t *__mutex));//线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
extern int pthread_cond_timedwait ((pthread_cond_t *__cond,pthread_mutex_t *__mutex, __const struct timespec *__abstime));
它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
函数pthread_cond_signal()的原型为:
extern int pthread_cond_signal ((pthread_cond_t *__cond));
  它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。
函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。
例子为:
…… // 提示出租车到达的条件变量 
 pthread_cond_t taxiCond; 
 // 同步锁 
 pthread_mutex_t taxiMutex; 
 // 旅客人数,初始为 0 
 int travelerCount=0; 
 // 旅客到达等待出租车
void * traveler_arrive(void * name){ 
 cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;     pthread_mutex_lock(&taxiMutex); 
 // 提示旅客人数增加 
 travelerCount++;
 pthread_cond_wait (&taxiCond, &taxiMutex);
 pthread_mutex_unlock (&taxiMutex);
 cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;
 pthread_exit( (void *)0 ); 
 }
 // 出租车到达
 void * taxi_arrive(void *name)  { 
 cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl;
 while(true) 
 
 pthread_mutex_lock(&taxiMutex);
 // 当发现已经有旅客在等待时,才触发条件变量 
 if(travelerCount>0) 
 
 pthread_cond_signal(&taxtCond); 
 pthread_mutex_unlock (&taxiMutex);             break; 
 
 pthread_mutex_unlock (&taxiMutex); 
 }
 pthread_exit( (void *)0 );
 }
void main() { 
 // 初始化 
 taxtCond= PTHREAD_COND_INITIALIZER; 
 taxtMutex= PTHREAD_MUTEX_INITIALIZER;
 pthread_t thread;
 pthread_attr_t threadAttr; 
 pthread_attr_init(&threadAttr);
 pthread_create(&thread, & threadAttr, taxt_arrive, (void *)( ” Jack ” )); 
 sleep(1); 
 pthread_create(&thread, &threadAttr, traveler_arrive, (void *)( ” Susan ” ));
 sleep(1); 
 pthread_create(&thread, &threadAttr, taxi_arrive, (void *)( ” Mike ” )); 
 sleep(1); 
 return 0;
 }

注意:int pthread_cond_timedwait(pthread_cond_t *restrict cond,               pthread_mutex_t *restrict mutex,               const struct timespec *restrict abstime);
abstime它所表示的是一个绝对时间,而不是一个时间间隔数值,只有当系统的当前时间达到或者超过 abstime 所表示的时间时,才会触发超时事件。假设我们指定相对的超时时间参数如 dwMilliseconds (单位毫秒)来调用和超时相关的函数,这样就需要将 dwMilliseconds 转化为 Linux 下的绝对时间参数 abstime 使用。常用的转换方法如下:
struct timeval now;  gettimeofday(&now, NULL); 
  abstime ->tv_nsec = now.tv_usec * 1000 + (dwMilliseconds % 1000) * 1000000;  abstime ->tv_sec = now.tv_sec + dwMilliseconds / 1000;

注解:pthread_t:typedef unsigned long int pthread_t;

pthread_cond_t:

pthread_mutex_t:

pthread_attr_t:

 

 

 关于信号量:

 

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsignedint __value));
  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
  函数sem_post( sem_t *sem)用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  函数sem_wait( sem_t *sem)被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait (sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
  函数sem_destroy(sem_t*sem)用来释放信号量sem。
  下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;

void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d%d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}

void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d%d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}

void HandleData1(void){
while(1){
sem_wait(&sem);
printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
--size;
}
}

void HandleData2(void){
while(1){
sem_wait(&sem);
printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void*)HandleData1,NULL);
pthread_create(&t2,NULL,(void*)HandleData2,NULL);
pthread_create(&t3,NULL,(void*)ReadData1,NULL);
pthread_create(&t4,NULL,(void*)ReadData2,NULL);

pthread_join(t1,NULL);
}

 


线程的终止

 
  如果进程中任何一个线程中调用exit,_Exit,或者是_exit,那么整个进程就会终止,
 
  与此类似,如果信号的默认的动作是终止进程,那么,把该信号发送到线程会终止进程。
 
  线程的正常退出的方式:
 
     (1) 线程只是从启动例程中返回,返回值是线程中的退出码
 
     (2) 线程可以被另一个进程进行终止
 
     (3) 线程自己调用pthread_exit函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值