linux 多线程编程

来源:互联网 发布:angela js模块化技术 编辑:程序博客网 时间:2024/06/05 09:22
一、线程的基本概念
    进程(process)和文件(files)是unix/linux操作系统两个最基本的抽象。进程是处于执行期的程序和它所包含的资源的总和,也就是说一个进程就是处于执行期的程序。一个线程(thread)就是运行在一个进程上下文中的一个逻辑流,不难看出,线程是进程中最基本的活动对象。
    
    在传统的系统中,一个进程只包含有一个线程。但在现代操作系统中,允许一个进程里面可以同时运行多个线程,这类程序就被称为多线程程序。所有的程序都有一个主线程(main thread),主线程是进程的控制流或执行线程。在多线程程序中,主线程可以创建一个或多个对等线程(peer  thread),从这个时间点开始,这些线程就开始并发执行。主线程和对等线程的区别仅在于主线程总是进程中的第一个运行的线程。从某种程度上看,线程可以看作是轻量级的进程。在linux操作系统中,内核调度的基本对象时线程,而不是进程,所以进程中的多个线程将由内核自动调度。

    每个线程都拥有独立的线程上下文(thread context),线程ID(thread ID,TID),程序计数器(pc),线程栈(stack)。其中,内核正是通过线程ID(TID)来识别线程,进行线程调度的。




二、线程与进程的异同点

A.相同点

<1> 比如都具有ID,一组寄存器,状态,优先级以及所要遵循的调度策略。
<2>每个进程都有一个进程控制块,线程也拥有一个线程控制块(在Linux内核,线程控制块与进程控制块用同一个结构体描述,即struct  task_struct),这个控制块包含线程的一些属性信息,操作系统使用这些属性信息来描述线程。
<3>线程和子进程的创建者可以在线程和子进程上实行某些控制,比如,创建者可以取消、挂起、继续和修改线程和子进程的优先级。

B.不同点
<1>主要区别:每个进程都拥有自己的地址空间,但线程没有自己独立的地址空间,而是运行在一个进程里的所有线程共享该进程的整个虚拟地址空间。
<2>线程的上下文切换时间开销比进程上下文切换时间开销要小的多
<3>线程的创建开销远远小于进程的创建
<4>子进程拥有父进程的地址空间和数据段的拷贝,因此当子进程修改它的变量和数据时,它不会影响父进程中的数据,但线程可以直接访问它进程中的数据段。
<5>进程之间通讯必须使用进程间通讯机制,但线程可以与进程中的其他线程直接通讯
<6>线程可以对同一进程中的其他线程实施大量控制,但进程只能对子进程实施控制
<7>改变主线程的属性可能影响进程中其他的线程,但对父进程的修改不影响子进程


三、线程相关的API

A.线程的创建



参数说明:

thread:指向pthread_t类型的指针,该地址将存放线程创建成功之后的线程TID。
attr:用户设置线程的属性,一般都不需要特殊设置,所以可简单设置为NULL。
*(*start_routine)(void *):传递新线程所要执行的函数的地址。
arg:新线程所有执行的函数的参数。

调用成功,则返回值是0,如果失败则返回错误代码。

注意:我们可以看到,如果想启动一个线程,就必须让这个线程关联一个子函数。我们一般称此函数为线程函数,但是我们又发现,在我们给线程函数传参数时,标准线程创建接口只留了 一个参数传递。思考?如果想给线程函数传递多个参数,该怎么解决呢?

B.线程终止



参数说明 :
value_ptr指向线程放回的某个对象

线程通过调用pthread_exit函数终止执行,并带回指向某个对象的指针。

注意:绝不能用它返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不存在了,这将引起严重的程序漏洞。

C.等待线程终止



一般此函数用在主线程中,等待通过thread指定的线程终止,此函数调用成功,可以通过value_ptr获取终止线程的返回值。

注意:如果等待的线程没有终止,此函数将引起调用者阻塞。成功返回0,失败返回-1。 

D.线程取消




如果想取消一个正在运行的线程,可以调用此函数。

参数说明:
thread:要取消的线程

函数返回值:成功返回0,失败返回-1

案例探究:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. #include <string.h>

  5. char buf[] = "Hello word";

  6. void *pthread_function1(void*arg)
  7. {
  8.     while(1)
  9.     {
  10.         sleep(2);
  11.         printf("pthread_function : %s.\n",buf);
  12.     }
  13. }

  14. void *pthread_function2(void*arg)
  15. {
  16.     int i = 0;

  17.     for(i = 0;i < strlen(buf);i)
  18.     {
  19.         *(int*)arg = 1;
  20.         sleep(1);
  21.     }

  22.     pthread_exit("pthread_function2 over");
  23. }

  24. int main()
  25. {
  26.     pthread_t tid1,tid2;
  27.     int    count = 0;
  28.     void *value_ptr;
  29.     
  30.     if(pthread_create(&tid1,NULL,pthread_function1,NULL)!= 0)
  31.     {
  32.         perror("Fail to pthread create");
  33.         return -1;
  34.     }else{
  35.         printf("create pthread %lu.\n",tid1);
  36.     }

  37.     if(pthread_create(&tid2,NULL,pthread_function2,(void*)&count)!= 0)
  38.     {
  39.         perror("Fail to pthread create");
  40.         exit(EXIT_FAILURE);
  41.     }else{
  42.         printf("create pthread %lu.\n",tid2);
  43.     }
  44.     
  45.     if(pthread_join(tid2,&value_ptr)< 0)
  46.     {
  47.         perror("Fail to pthread_join");
  48.         exit(EXIT_FAILURE);
  49.     }
  50.     
  51.     printf("pthread %lu join success,return string : %s.\n",tid1,(char*)value_ptr);

  52.     if((pthread_cancel(tid1))< 0)
  53.     {
  54.         perror("Fail to pthread cancel");
  55.         exit(EXIT_FAILURE);
  56.     }
  57.     
  58.     printf("pthread %lu cancel success.\n",tid2);
  59.     printf("main pthread sleeping...\n");
  60.     sleep(5);
  61.     
  62.     return 0;
  63. }

在主线程中,创建了两个子线程tid1,tid2。
tid1线程每隔2秒钟打印一次全局数组buf的内容。
tid2线程没1秒统计一下buf数组中的字符,统计结束后调用pthread_exit退出。

运行结果如下:



可以看出,在调用pthread_cancel后,对应的线程就取消了。

案例二

有一buf[] = "hello word.\n";
创建两个线程A、B;

A线程将buf的内容向文件 test写5此;
B线程读取test文件五次,将读取的内容在终端上进行打印

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <errno.h>
  6. #include <sys/types.h>
  7. #include <sys/stat.h>
  8. #include <fcntl.h>

  9. #define MAX 50

  10. char buf[] = "hello word.\n";

  11. void *write_file(void*file_name)
  12. {
  13.     int fd;
  14.     int i = 0;

  15.     if((fd= open((char*)file_name,O_CREAT| O_TRUNC | O_APPEND| O_WRONLY,0666))< 0)
  16.     {
  17.         fprintf(stderr,"Fail to open %s : %s.\n",(char*)file_name,strerror(errno));
  18.         pthread_exit(NULL);
  19.     }
  20.     
  21.     for(i = 0;i < 5;i)
  22.     {
  23.         write(fd,buf,strlen(buf));
  24.     }

  25.     pthread_exit(NULL);
  26. }

  27. void *read_file(void*file_name)
  28. {
  29.     int fd;
  30.     int n = 0,i= 0;
  31.     char buf[MAX];

  32.     if((fd= open((char*)file_name,O_CREAT| O_RDONLY,0666))< 0)
  33.     {
  34.         fprintf(stderr,"Fail to open %s : %s.\n",(char*)file_name,strerror(errno));
  35.         pthread_exit(NULL);
  36.     }

  37.     for(i = 0;i < 5;i)
  38.     {
  39.         n = read(fd,buf,sizeof(buf));
  40.         buf[n]= '\0';
  41.         printf("read %d : %s\n",n,buf);
  42.     }

  43.     pthread_exit(NULL);
  44. }

  45. int main(int argc,char*argv[])
  46. {
  47.     int res;
  48.     pthread_t tid1,tid2;
  49.     void *arg = argv[1];
  50.     
  51.     if(argc< 2)
  52.     {
  53.         fprintf(stderr,"usage : %s argv[1].\n",argv[0]);
  54.         return -1;
  55.     }
  56.     
  57.     res = pthread_create(&tid1,NULL,write_file,arg);
  58.     if(res != 0)
  59.     {
  60.         perror("Fail to pthread create");
  61.         exit(EXIT_FAILURE);
  62.     }

  63.     res = pthread_create(&tid2,NULL,read_file,arg);
  64.     if(res != 0)
  65.     {
  66.         perror("Fail to pthread create");
  67.         exit(EXIT_FAILURE);
  68.     }

  69.     pthread_join(tid1,NULL);
  70.     pthread_join(tid2,NULL);

  71.     exit(EXIT_SUCCESS);
  72. }
运行结果如下:



什么玩意?和我猜想的结果不一样呀。思考?为什么是这种情况。

我们更希望,在A线程写完后,B线程读。B线程读完后,A线程在写。想要达到这种效果,我们可以通过线程的同步和互斥。

四、线程的同步

A.线程间机制

<1>多线程共享同一个进程的地址空间
<2>有点:线程间很容易进行通信,通过全局变量实现数据共享和交换
<3>缺点:多个线程同时访问共享对象时需要引入同步和互斥机制

B.线程间同步 - P/V操作

<1>信号量代表某一类资源,其值表示系统中该资源的数量

<2>信号量是 一个受保护的变量,只能通过三种操作来访问

a.初始化

b.P操作(申请资源)

c.v操作(释放资源)

<3>信号量的值为非负整数,其值为0时,表示当前系统中无此类资源

P(s)含义如下:

if(信号量的值大于0)
{
    申请资源的任务继续运行;
注:暂存的内容只能恢复到当前文章的编辑器中,如需恢复到其他文章中,请编辑该文章并从暂存箱中恢复;或者直接复制以上内容,手工恢复到相关文章。
原创粉丝点击