zoukankan      html  css  js  c++  java
  • Linux 多线程编程

    前言

    Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用libpthread.a。Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。


    基础知识点和代码实现

    1.运行一个进程中的多个线程,彼此之间使用相同的地址空间,共享大部分数据。

    2.启动一个线程所花费的空间远远小于启动一个进程所话费的空间。

    3.线程间切换所需要的时间远远小于进程间切换所需要的时间。

    4.不同进程具有独立的数据空间,数据的传递只能通过通信的方式。--费时、不方便

     统一进程下的线程之间共享数据空间,一个线程数据可以直接为其他线程所用。--快捷、方便

    5.编写多线程需要注意的地方:

     有的变量不能同时被两个线程所修改
     子程序中声明为static的数据可能为多线程带来灾难性打击

    6.多线程优点:

      1)提高应用程序相响应,将耗时长的操作置于一个新的线程,避免等待。
      2)使CPU多核系统更加高效。
      3)改善程序结构。将长而复杂的进程可以分为多个线程。

    7. 进程是资源分配的基本单位,线程没什么资源。共享进程资源

    8. volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.

     简单地说就是防止编译器对代码进行优化.比如如下程序:
    XBYTE[2]=0x55;
    XBYTE[2]=0x56;
    XBYTE[2]=0x57;
    XBYTE[2]=0x58;
    对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一的进行编译并产生相应的机器代码(产生四条代码).

    9.线程相关操作

         9.1线程的标识符 :pthread_t
          pthread_t 在头文件/usr/include/bits/pthreadtypes.h定义如下:
        typedef unsigned long int pthread_t;

        9.2创建线程:pthread_creat

       原型为:

     extern int pthread_create _P((pthread_t *_thread,_const pthread_attr_t *_attr,void *(*_start_routine) (void *),void *_arg));

         第一个参数:指向线程标识符的指针  第二个参数:设置线程属性  第三个参数:线程运行函数的起始地址 最后一个参数:运行函数的参数
     线程创建成功,返回0,不为0,创建失败。
     常见错误返回代码:EAGAIN:系统限制创建新的线程 EINVAL:线程的属性值非法

     线程创建成功,新创建线程运行参数三和参数四确定的函数。

        9.3等待线程结束pthread_join和线程结束pthread_exit

         pythread_join函数原型:   

       extern int pthread_join_P((pthread_t _th,void**_thread_return));

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

        线程结束有两种途径:函数结束,调用它的线程结束;另一种方式通过函数pthread_exit来实现。

    pthread_exit函数原型:

    exter void pthread_exit_P((void*_retval)_attribute_((_noreturn_));

    一的参数是函数的返回代码:只要pthread_join中的第二个参数thread_return不是NULL,该值传递给thread_return。

    一个线程不能被多个线程等待,否则第一个接受到的信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH.

    10.线程的同步

        虽然线程本地存储可以避免线程访问共享数据,但是线程之间的大部分数据始终还是共享的。在涉及到对共享数据进行读写操作时,就必须使用同步机制,否则就会造成线程们哄抢共享数据的结果,这会把你的数据弄的七零八落理不清头绪。
        Linux提供的线程同步机制主要有互斥锁和条件变量。 
       10.1互斥锁

        所谓的互斥就是线程之间互相排斥,获得资源的线程排斥其它没有获得资源的线程。Linux使用互斥锁来实现这种机制。

         既然叫锁,就有加锁和解锁的概念。当线程获得了加锁的资格,那么它将独享这个锁,其它线程一旦试图去碰触这个锁就立即被系统“拍晕”。当加锁的线程解开并放弃了这个锁之后,那些被“拍晕”的线程会被系统唤醒,然后继续去争抢这个锁。至于谁能抢到,只有天知道。但是总有一个能抢到。于是其它来凑热闹的线程又被系统给“拍晕”了……如此反复。

        从互斥锁的这种行为看,线程加锁和解锁之间的代码相当于一个独木桥,同意时刻只有一个线程能执行。从全局上看,在这个地方,所有并行运行的线程都变成了排队运行了。比较专业的叫法是同步执行,这段代码区域叫临界区。同步执行就破坏了线程并行性的初衷了,临界区越大破坏得越厉害。所以在实际应用中,应该尽量避免有临界区出现。实在不行,临界区也要尽量的小

       Linux初始化和销毁互斥锁的接口是pthread_mutex_init()和pthead_mutex_destroy(),对于加锁和解锁则有pthread_mutex_lock()、pthread_mutex_trylock()和pthread_mutex_unlock()。这些接口的完整定义如下:

    从这些定义中可以看到,互斥锁也是有属性的。只不过这个属性在绝大多数情况下都不需要改动,所以使用默认的属性就行。方法就是给它传递NULL。
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    const pthread_mutexattr_t *restrict attr);
    int pthread_mutex_destory(pthread_mutex_t *mutex );
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
         phtread_mutex_trylock()比较特别,用它试图加锁的线程永远都不会被系统“拍晕”,只是通过返回EBUSY来告诉程序员这个锁已经有人用了。至于是否继续“强闯”临界区,则由程序员决定。系统提供这个接口的目的可不是让线程“强闯”临界区的。它的根本目的还是为了提高并行性,留着这个线程去干点其它有意义的事情。当然,如果很幸运恰巧这个时候还没有人拥有这把锁,那么自然也会取得临界区的使用权。

        互斥锁在同一个线程内,没有互斥的特性。也就是说,线程不能利用互斥锁让系统将自己“拍晕”。解释这个现象的一个很好的理由就是,拥有锁的线程把自己“拍晕”了,谁还能再拥有这把锁呢?但是另外情况需要避免,就是两个线程已经各自拥有一把锁了,但是还想得到对方的锁,这个时候两个线程都会被“拍晕”。一旦这种情况发生,就谁都不能获得这个锁了,这种情况还有一个著名的名字——死锁。死锁是永远都要避免的事情,因为这是严重损人不利己的行为。

        10.2 条件变量
        条件变量关键点在“变量”上。与锁的不同之处就是,当线程遇到这个“变量”,并不是类似锁那样的被系统给“拍晕”,而是根据“条件”来选择是否在那里等待。等待什么呢?等待允许通过的“信号”。这个“信号”是系统控制的吗?显然不是!它是由另外一个线程来控制的。

        条件变量是一种事件机制。由一类线程来控制“事件”的发生,另外一类线程等待“事件”的发生。为了实现这种机制,条件变量必须是共享于线程之间的全局变量。而且,条件变量也需要与互斥锁同时使用。

    初始化和销毁条件变量的接口是pthread_cond_init()和pthread_cond_destory();控制“事件”发生的接口是pthread_cond_signal()或pthread_cond_broadcast();等待“事件”发生的接口是pthead_cond_wait()或pthread_cond_timedwait()。它们的完整定义如下:

    int pthread_cond_init(pthread_cond_t *cond,
    const pthread_condattr_t *attr);
    int pthread_cond_destory(pthread_cond_t *cond);
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int pthread_cond_timedwait(pthread_cond_t *cond,
    pthread_mutex_t *mutex,
    const timespec *abstime);
    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond);
    
       对于等待“事件”的接口从其名称中可以看出,一种是无限期等待,一种是限时等待。后者与互斥锁的pthread_mutex_trylock()有些类似,即当等待的“事件”经过一段时间之后依然没有发生,那就去干点别的有意义的事情去。而对于控制“事件”发生的接口则有“单播”和“广播”之说。所谓单播就是只有一个线程会得到“事件”已经发生了的“通知”,而广播就是所有线程都会得到“通知”。对于广播情况,所有被“通知”到的线程也要经过由互斥锁控制的独木桥。


    这里插入一个简单多线程的例子:    

    #include 要包含头文件pthread.h 还需要链接libpthread.so库,链接阶段应该有类似指令:gcc program.o -o program -lpthread
          
    多线程例子:
     
    #include<stdio.h>
    #include<pthread.h>
    
    
    void *thread(void *arg)
    {
     printf("This is a  thread and arg=%d.
    ",*(int *)arg);
     *(int*)arg = 0;
     return arg;
    }
    
    
    int main(int argc,char *argv[])
    {
     pthread_t th;
     int ret;
     int arg = 10;
     int *thread_ret = NULL;
    
    
      ret = pthread_create(&th,NULL,thread,&arg);
      if(ret!=0){
      printf("create thread error!
    ");
      return -1;
    }
            
    cloud@zw:~$ gcc thread.c -o thread -lpthread
    cloud@zw:~$ ./thread
    This is the main process.
    This is a  thread and arg=10.
    thread_ret=0.
    


    11.线程的合并与分离
    线程的合并就是回收线程资源,以免出现资源泄露的问题。
    当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。
    当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。
    与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()。线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。

    线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。

    12.两个多线程编程的例子和实现

      12.1) Linux里编写成多线程序,输出各线程号。

    设计思路:今天看了老师的程序,明白了关于线程号输出函数pthread_self().很简单,我主要把多线程程序的主线程和子线程号进行输出。在过程中碰到问题,主线程号输出了,子线程号没有输出。仔细一想,主线程执行完了,子线程还没来得及执行就结束了。怎么办呢?sleep函数,让主线程等待10s,最终达到预期目的。

     程序如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    
    void  * thread(void *arg)
    {
            printf("thread id is %lu.
    ",pthread_self());
            return NULL;
    }
    
    int main()
    {
    pthread_t id;
    printf("Main thread id is %lu 
    ",pthread_self());
    if(!pthread_create(&id,NULL,(void *)thread,NULL))
    {
    printf("succeed!
    ");
    sleep(10);
    return 0;
    }
    else
    {printf("Fail to Create Thread");
    return -1;
    }
    }
    


    运行结果:

    注:编译的时候要链接库:gcc  -o thread1  thread1.c -lpthread

      12.2) 利用多线程计算pi值。

    设计思路:这里利用中值积分定理来计算π值。我的思路就是把线程的执行任务分成四段,,每个线程执行一段,期间遇到的问题就是,线程传递的参数,有些没计算完,来不及传递过去,最终线程结束,我的做法就是让每个线程创建的时候,等待1s.最终达到我的预期结果。

    程序如下:

    #include <stdio.h>  
    #include<pthread.h>  
    static long num_steps=100000;   
    const int numThreads = 4;  
    double step, pi;  
    pthread_mutex_t mut; 
    double sum = 0.0;  
      
      
    void * thread(void *pArg)   
    {    
       double x;  
       int  i ;  
       int temp = *((int *)pArg);  
       int start = temp*(num_steps/4);  
       int end = start + num_steps/4;  
       printf("%d %d %d
    ",temp,start,end);  //测试程序,可以去掉
       for (i=start; i<end; i++){  
    
          pthread_mutex_lock(&mut);
          x = (i+0.5)*step;  
          sum = sum + 4.0/(1.0 + x*x); 
          pthread_mutex_unlock(&mut); 
       }  
         
       return 0;   
    }  
      
    void main()  
    {    
       int i;  
       pthread_t  hThread[numThreads];  
       pthread_mutex_init(&mut,NULL);
       step = 1.0/(double) num_steps;  
           
       for(i=0;i<numThreads;i++)  
       {  
          hThread[i] = pthread_create(&hThread[i] ,NULL,thread, &i);
          sleep(1);    
       }  
       
    //  for(i=0;i<numThreads;i++){
    
      // pthread_join(hThread[i],NULL);
       
     //  }
      
       pi = step * sum;
       printf("Pi = %12.9f
    ",pi);
       
         
    }  
    

    运行结果:



  • 相关阅读:
    iOS9下App Store新应用提审攻略
    信息安全系统设计基础第十一周学习总结
    Linux第十一次学习笔记
    信息安全系统设计基础第十周学习总结
    Linux第八次学习笔记
    Linux第七次实验笔记
    家庭作业3.67
    Linux第六次学习笔记
    Linux第五次学习笔记
    Linux第四次学习笔记
  • 原文地址:https://www.cnblogs.com/bigdata1024/p/8387450.html
Copyright © 2011-2022 走看看