POSIX线程管理



线程管理

创建线程:  

  • 最初,main函数包含了一个缺省的线程。其它线程则需要程序员显式地创建。 
  • pthread_create创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用。 
  • pthread_create参数: 
    • thread:返回一个不透明的,唯一的新线程标识符。 
    • attr:不透明的线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值。 
    • start_routine:线程将会执行一次的C函数。 
    • arg:传递给start_routine单个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL 
  • 一个进程可以创建的线程最大数量取决于系统实现。 
  • 一旦创建,线程就称为peers,可以创建其它线程。线程之间没有指定的结构和依赖关系。 

 

 

 

Q:一个线程被创建后,怎么知道操作系统何时调度该线程使之运行? 

A:除非使用了Pthreads的调度机制,否则线程何时何地被执行取决于操作系统的实现。强壮的程序应该不依赖于线程执行的顺序。

 

线程属性:  

  • 线程被创建时会带有默认的属性。其中的一些属性可以被程序员用线程属性对象来修改。 
  • pthread_attr_initpthread_attr_destroy用于初始化/销毁线程属性对象。 
  • 其它的一些函数用于查询和设置线程属性对象的指定属性。 
  • 一些属性下面将会讨论。 

结束终止:  

  • 结束线程的方法有一下几种: 
    • 线程从主线程(main函数的初始线程)返回。 
    • 线程调用了pthread_exit函数。 
    • 其它线程使用pthread_cancel函数结束线程。 
    • 调用exec或者exit函数,整个进程结束。 
  • pthread_exit用于显式退出线程。典型地,pthread_exit()函数在线程完成工作时,不在需要时候被调用,退出线程。 
  • 如果main()在其他线程创建前用pthread_exit()退出了,其他线程将会继续执行。否则,他们会随着main的结束而终止 
  • 程序员可以可选择的指定终止状态,当任何线程连接(join)该线程时,该状态就返回给连接(join)该线程的线程。 
  • 清理:pthread_exit()函数并不会关闭文件,任何在线程中打开的文件将会一直处于打开状态,直到线程结束。 
  • 讨论:对于正常退出,可以免于调用pthread_exit()。当然,除非你想返回一个返回值。然而,在main中,有一个问题,就是当main结束时,其它线程还没有被创建。如果此时没有显式的调用pthread_exit()main结束时,进程(和所有线程)都会终止。可以在main中调用pthread_exit(),此时尽管在main中已经没有可执行的代码了,进程和所有线程将保持存活状态,。 


Example Code - Pthread Creation and Termination  

#include <pthread.h> 

#include <stdio.h> 

#define NUM_THREADS    

 

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); 

        exit(-1); 

     

  

  pthread_exit(NULL); 

向线程传递参数 

  • pthread_create()函数允许程序员向线程的start routine传递一个参数。当多个参数需要被传递时,可以通过定义一个结构体包含所有要传的参数,然后用pthread_create()传递一个指向该结构体的指针,来打破传递参数的个数的限制。 
  • 所有参数都应该传引用传递并转化成(void*)。 

 

 

Q:怎样安全地向一个新创建的线程传递数据? 

A:确保所传递的数据是线程安全的(不能被其他线程修改)。下面三个例子演示了那个应该和那个不应该。 

Example 1 - Thread Argument Passing  

下面的代码片段演示了如何向一个线程传递一个简单的整数。主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。 


int *taskids[NUM_THREADS]; 

 

for(t=0; t<NUM_THREADS; t++) 

  taskids[t] = (int *) malloc(sizeof(int)); 

  *taskids[t] = t; 

  printf("Creating thread %d\n", t); 

  rc = pthread_create(&threads[t], NULL, PrintHello,  

       (void *) taskids[t]); 

  ... 

 

 

Example 2 - Thread Argument Passing  

例子展示了用结构体向线程设置/传递参数。每个线程获得一个唯一的结构体实例。 


struct thread_data{ 

  int  thread_id; 

  int  sum; 

  char *message; 

}; 

 

struct thread_data thread_data_array[NUM_THREADS]; 

 

void *PrintHello(void *threadarg) 

  struct thread_data *my_data; 

  ... 

  my_data = (struct thread_data *) threadarg; 

  taskid = my_data->thread_id; 

  sum = my_data->sum; 

  hello_msg = my_data->message; 

  ... 

 

int main (int argc, char *argv[]) 

  ... 

  thread_data_array[t].thread_id = t; 

  thread_data_array[t].sum = sum; 

  thread_data_array[t].message = messages[t]; 

  rc = pthread_create(&threads[t], NULL, PrintHello,  

       (void *) &thread_data_array[t]); 

  ... 

 

 

Example 3 - Thread Argument Passing (Incorrect)  

例子演示了错误地传递参数。循环会在线程访问传递的参数前改变传递给线程的地址的内容。 


int rc, t; 

 

for(t=0; t<NUM_THREADS; t++)  

  printf("Creating thread %d\n", t); 

  rc = pthread_create(&threads[t], NULL, PrintHello,  

       (void *) &t); //传递共享变量 错误。

  ... 

连接(Joining)和分离(Detaching)线程 

函数:  

intpthread_join (pthread_t threadid,void **thread_return);

intpthread_detach (pthread_tthreadid);

int pthread_attr_setdetachstate (pthread_attr_t attr,int detachstate);  

int pthread_attr_getdetachstate (const pthread_attr_t attr,int *detachstate) ;

连接:  

  •  “连接”是一种在线程间完成同步的方法。例如: 
  • pthread_join()函数阻塞调用线程直到threadid所指定的线程终止。 
  • 如果在目标线程中调用pthread_exit(),程序员可以在主线程中获得目标线程的终止状态。 
  • 连接线程只能用pthread_join()连接一次。若多次调用就会发生逻辑错误。 
  • 两种同步方法,互斥量(mutexes)和条件变量(condition variables),稍后讨论。 

可连接(Joinable or Not?  

  • 当一个线程被创建,它有一个属性定义了它是可连接的(joinable)还是分离的(detached)。只有是可连接的线程才能被连接(joined),如果创建的线程是分离的,则不能连接。 
  • POSIX标准的最终草案指定了线程必须创建成可连接的。然而,并非所有实现都遵循此约定。 
  • 使用pthread_create()attr参数可以显式的创建可连接或分离的线程,典型四步如下: 
    1. 声明一个pthread_attr_t数据类型的线程属性变量 
    2. pthread_attr_init()初始化该属性变量 
    3. pthread_attr_setdetachstate()设置可分离状态属性 
    4. 完了后,用pthread_attr_destroy()释放属性变量所占用的库资源 

分离(Detaching):  

  • pthread_detach()可以显式用于分离线程,尽管创建时是可连接的。 
  • 没有与pthread_detach()功能相反的函数 

建议:  

  • 若线程需要连接,考虑创建时显式设置为可连接的。因为并非所有创建线程的实现都是将线程创建为可连接的。 
  • 若事先知道线程从不需要连接,考虑创建线程时将其设置为可分离状态。一些系统资源可能需要释放。 

例子: Pthread Joining 

Example Code - Pthread Joining  

这个例子演示了用Pthread join函数去等待线程终止。因为有些实现并不是默认创建线程是可连接状态,例子中显式地将其创建为可连接的。  


#include <pthread.h> 

#include <stdio.h> 

#define NUM_THREADS   

 

void *BusyWork(void *null) 

  int i; 

  double result=0.0; 

  for (i=0; i<1000000; i++) 

  

    result = result + (double)random(); 

  

  printf("result = %e\n",result); 

  pthread_exit((void *) 0); 

 

int main (int argc, char *argv[]) 

  pthread_t thread[NUM_THREADS]; 

  pthread_attr_t attr; 

  int rc, t; 

   void *status; 

 

  /* Initialize and set thread detached attribute */ 

  pthread_attr_init(&attr); 

  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 

 

  for(t=0; t<NUM_THREADS; t++) 

  

     printf("Creating thread %d\n", t); 

     rc = pthread_create(&thread[t], &attr, BusyWork, NULL);  

     if (rc) 

     

        printf("ERROR; return code from pthread_create()  

               is %d\n", rc); 

        exit(-1); 

     

  

 

  /* Free attribute and wait for the other threads */ 

  pthread_attr_destroy(&attr); 

  for(t=0; t<NUM_THREADS; t++) 

  

     rc = pthread_join(thread[t], &status); 

     if (rc) 

     

        printf("ERROR; return code from pthread_join()  

               is %d\n", rc); 

        exit(-1); 

     

     printf("Completed join with thread %d status= %ld\n",t, (long)status); 

  

 

  pthread_exit(NULL); 




     在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

   默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。

[cpp]
int pthread_join(pthread_t tid, void**thread_return); 
                                 若成功则返回0,若出错则为非零。 

线程通过调用pthread_join函数等待其他线程终止。调用pthread_join函数线程会阻塞,直到线程tid终止,将线程例程返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源。

[cpp] view plaincopyprint?int pthread_detach(pthread_t tid); 
                                 若成功则返回0,若出错则为非零。

pthread_detach用于分离可结合线程tid。线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。
如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。 由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码 pthread_detach(pthread_self())或者父线程调用 pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值