Chinaunix首页 | 论坛 | 博客
  • 博客访问: 112511
  • 博文数量: 11
  • 博客积分: 282
  • 博客等级: 二等列兵
  • 技术积分: 263
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-09 20:34
文章分类

全部博文(11)

文章存档

2012年(11)

分类: LINUX

2012-05-24 08:46:59

         线程最大的特点就是资源的共享性,然而资源共享中的同步问题是多线程编程的难点.Linux 系统提供了多种方式处理线程间同步问题,下面介绍几个.

一.互斥锁

  1.怎样实现线程间的同步?

    通过锁机制.在同一时刻,它通常只允许一个线程执行一个关键部分的代码.这样可以保证数据的正确性和每个操作的完整性.

  2.互斥锁函数

  在头文件中有一下常用的互斥锁函数.

  A.  pthread_mutex_init函数                     //初始化一个互斥锁

  两种方式来进行初始化,一种方式是静态赋值法,方法如下:
  pthread_mutex_t mutex=PTHREAD_MUTEX_INITLALIZER; 其中  PTHREAD_MUTEX_INITLALIZER     是一个结构常量.  
  另一种方式通过此函数来进行初始化,函数原型如下:
  int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t    *mutexattr);      
  函数中的参数mutexattr表示互斥锁的属性,如果为NULL表示默认属性.下面在介绍互斥锁的属性.

  B.加锁函数.  pthread_mutex_lock函数和pthread_mutex_trylock函数

  初始化以后,就可以给互斥锁加锁了.加锁函数原型如下:
   int pthread_mutex_lock(pthread_mutex_t *mutex);
   int pthread_mutex_trylock(pthread_mutex_t *mutex);
   它们俩个加锁函数区别如下:
       用pthread_mutex_lock()加锁时,如果mutex已经被锁住,当前尝试加锁的进程就会阻塞,直到互斥锁被其他线程释放.当pthread_mutex_lock()函数返回时,说明当前线程已经成功将该互斥锁加锁.
       用pthread_mutex_trylock()函数进行加锁时,如果mutex已经被锁住,它不回阻塞等待,而是立即返回,返回的错误代码为EBUSY(资源正在使用,不能共享).
    注意: 加锁时,无论哪种类型的锁,都不可能被两个不同的线程同时得到,其中一个必须等待解锁.同一进程中的线程如果加锁后没有解锁,其他线程将无法获得改锁.即会造成死锁(当一个线程终止时,如果不释放其占有的临界资源, 则该资源会被认为还被已经退出的线程所使用,因而永远不回得到释放.如果一个线程在等待使用这个资源,它就可能无限的等待下去,这就形成了死锁).为了防止死锁,linux系统提供了一对函数:pthread_cleanup_push()和 pthread_cleanup_pop()用于自动释放资源.从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序  段中的终止动作都将执行pthread_cleanup_push()指定的清理函数.这俩个函数是以宏形式提供的:

    #include
    #define pthread_cleanup_push(routine,arg)
     {   struct _pthread_cleanup_buffer buffer;
         _pthread_cleanup_push(&buffer,(routine),(srg));
     #define pthread_cleanup_pop _pthread_cleanup_pop(&buffer,(exeute));
     }
    这两个函数必须成对出现,且必须位于程序的同一代码段中才能通过编译.
      
    C.解锁函数  pthread_mutex_unlock 函数.

    函数原型如下: int pthread_mutex_unlock(pthread_mutex_t *mutex);
    用pthread_mutex_unlock()解锁需满足一下两个条件:
    >>互斥锁必须处于加锁状态
    >>调用本函数的线程必须是给此互斥锁加锁的线程.

    D.清除互斥锁函数    pthread_mutex_destroy函数 
     
    当一个互斥锁使用完毕后,必须进行清除操作. 该函数原型如下:
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    清除一个互斥锁意味着释放它所占用的资源.清除锁时要求当前处于开放状态.若锁处于锁定状态,则返回   EBUSY.成功时返回0.由于在linux中,互斥锁并不占用内存,因此此函数除了解除到斥锁的状态以外没有其他操作.

    3.互斥锁的属性
                                   
    PTHREAD_MUTEX_TIMED_NP  
    普通锁,当一个线程加锁后,其余请求锁的线程形成等待队列,解锁后按优先级获得锁.
   PTHREAD_MUTEX_RECURSIVE_NP       
   嵌套锁,允许同一个线程对同一个锁多次加锁,并多次通过unlock解锁,如果是不同线程请求,则在 解锁时重新争.
   PTHREAD_MUTEX_ERRORCHECK_NP    
   检错锁,在同一个线程请求同一个锁的情况下,返回EDEADLK,否则执行的动作与普通锁相同.
   PTHREAD_MUTEX_ADAPTIVE_NP         
   适应锁.解锁后,重新竞争.

  下面举个类子说明下:
   实现功能,对一个数据的读写操作.
   

点击(此处)折叠或打开

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

  4. pthread_mutex_t number_mutex;
  5. int globalnumber=0;

  6. void write_globalnumber()
  7. {
  8.     pthread_cleanup_push(pthread_mutex_unlock,&number_mutex); //note 1
  9.    while(1)
  10.    {
  11.      sleep(2);
  12.     pthread_mutex_lock(&number_mutex);
  13.     globalnumber++;
  14.     printf("in function write_globalnumber the globalnumber is :%d\n",globalnumber);
  15.     pthread_mutex_unlock(&number_mutex);
  16.    }
  17.     pthread_cleanup_pop(0);                               //note 2
  18. }
  19. void read_globalnumber()
  20. {
  21.     
  22.     while(1)
  23.     {

  24.     sleep(5);
  25.     int temp;

  26.     pthread_mutex_lock(&number_mutex);
  27.     temp=globalnumber;
  28.     pthread_mutex_unlock(&number_mutex);

  29.     printf("In function read_globalnumber the globalnumber is : %d\n",temp);
  30.     }
  31. }

  32. int main(void)
  33. {
  34.     int temp;
  35.     pthread_t thid1,thid2;
  36.     printf("mutex variable study!\n");
  37.     pthread_mutex_init(&number_mutex,NULL);
  38.     pthread_create(&thid1,NULL,(void *)write_globalnumber,NULL);
  39.     pthread_create(&thid2,NULL,(void *)read_globalnumber,NULL);

  40.     sleep(50);                                                  //note 3
  41.     do
  42.     {
  43.         sleep(10);
  44.         pthread_cancel(thid1);                                   //note 4

  45.     }while(1);

  46.     return 0;
  47. }
         上面代码中实现了如何通过互斥锁来保护全局变量.两个函数对共享全局变量globalnumber进行读写操作,write_globalnumber函数使用互斥锁保证在修改变量的时候操作一次执行完毕.而read_globalnumber函数使用互斥锁保证在读数据的时候,全局变量globalnumber不会被修改,确保读到数据的正确性.
         note1和note2是回调函数保护.防止死锁.虽然你把它注释掉没有太大影响,但是我认为,这是一种好的习惯吧. note3让主线程睡上50妙,以使thid1和thid2 线程有时间执行它们的操作.

   二.条件变量

   1.条件变量是通过什么操作来保证线程间的同步?
    
    条件变量是利用线程间共享的全局变量进行同步的一种机制.它其实类似于if语句,符合条件就执行某段程序,否则等待条件成立.
    它主要包涵两个动作
    >>一个等待使用资源的线程等待"条件变量被设置为真"
    >>一个线程使用资源后"设置条件变量为真"
    存在问题: 要保证条件变量能被正确的修改,所以他要受到特殊保护,所以条件变量一般都是和互斥锁共同使用的.
  
   2.条件变量操作的函数
    
   A.初始化函数
    pthread_cond_init 函数           初始化条件变量.

     函数原型: int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
     其中cond_attr参数是条件变量的属性,其并没有得到实现,所以它的值通常为NULL.
     还有一种静态赋值法,将宏结构常量PTHREAD_COND_INITIALIZER 赋予互斥锁.

   B.等待条件成立函数.
     >>pthread_cond_wait 函数.函数原型如下:
       int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
        pthread_cond_wait 函数释放由mutex指向的互斥锁,同时使当前线程关于条件变量阻塞,直到条件被信号唤醒.
    
     >>pthread_cond_timedwait函数,原型如下:
  int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime)
   pthread_cond_timedwait函数和pthread_cond_wait 函数用法类似.区别在于:pthread_cond_timedwait函数将阻塞     直到条件变量获得信号换成经过有abstime指定的时间.
  
   C.解除阻塞函数
   
   线程被条件变量阻塞后,可通过以下两个解除阻塞函数来激活.
   >>pthread_cond_signal函数.原型如下
    int pthread_cond_signal(pthread_cond_t *cond);
    pthread_cond_signal()激活一个等待条件成立的线程,如果有多个等待线程,则按入队顺序激活其中一个.
    >>pthread_cond_broadcast 函数 ,原型如下
    int pthread_cond_broadcast(pthread_cond_t *cond);
     pthread_cond_broadcast()激活所有等待线程.

   D.清除条件变量函数
    
    pthread_cond_destroy函数,原型如下:
    int pthread_cond_destroy(pthread_cond_t *cond);
    pthread_cond_destroy函数清除由cond指向的条件变量.
    注意:只有再没有线程等待该条件变量的时候才能清除这个条件变量,否则返回EBUSY.

   下面举个类子说明下:
   

点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<pthread.h>

  4. pthread_mutex_t mutex;
  5. pthread_cond_t cond;

  6. void * thread1(void *arg)
  7. {
  8.     pthread_cleanup_push(pthread_mutex_unlock,&mutex); //*******note 1****

  9.     while(1)
  10.     {
  11.         printf("thread1 is running!\n");
  12.         pthread_mutex_lock(&mutex);
  13.         pthread_cond_wait(&cond,&mutex);
  14.         printf("thread1 applied the condition!\n");
  15.         pthread_mutex_unlock(&mutex);
  16.         sleep(4);
  17.     }

  18.     pthread_cleanup_pop(0); //***********note 2******
  19. }

  20. void *thread2(void *arg)
  21. {
  22.     while(1)
  23.     {
  24.         sleep(5);
  25.         printf("thread2 is running!\n");
  26.         pthread_mutex_lock(&mutex);
  27.         pthread_cond_wait(&cond,&mutex);
  28.         printf("thread2 applied the condition!\n");
  29.         pthread_mutex_unlock(&mutex);
  30.         sleep(2);
  31.     }
  32. }

  33. int main(void)
  34. {
  35.     pthread_t tid1,tid2;
  36.     printf("**************condition variable study !*********************\n");
  37.     pthread_mutex_init(&mutex,NULL);
  38.     pthread_create(&tid1,NULL,(void *)thread1,NULL);
  39.     pthread_create(&tid2,NULL,(void *)thread2,NULL);
  40.     
  41.     do
  42.     {
  43.         sleep(5);
  44.         pthread_cancel(tid1); //************note 3*******
  45.         pthread_cond_signal(&cond); //*************note 4*******
  46.     }while(1);

  47.     sleep(20);

  48.     pthread_exit(0);
  49.     return 0;
  50. }
         上面这个类子中,有两个线程被启动,并等待同一个条件变量.这个如果注释掉回调函数,就会产生死锁.(即注释掉note1 和note2) ,原因在于,程序执行到note3的时候 ,会忽略掉线程tid1,而此时线程tid1以对此条件变量加锁,并且调用pthread_cond_wait()函数处于阻塞状态,所以如果不调用回调函数,就不会释放共享资源(即条件变量值不会被改变),所以会产生死锁.  note5是激活等待条件成立的线程.

除了上述讨论的同步方式以外,还有异步信号.其他很多进程间通信手段对于LinuxThreads也是可用的,比如基于文件系统的IPC(管道、Unix域Socket等)、消息队列(Sys.V或者Posix的)、System V的信号灯等。只有一点需要注意,LinuxThreads在核内是作为共享存储区、共享文件系统属性、共享信号处理、共享文件描述符的独立进程看待的。这些大家下去自己研究,我就介绍这么多.


阅读(2058) | 评论(0) | 转发(1) |
0

上一篇:变量存储区

下一篇:函数返回值为数组

给主人留下些什么吧!~~