分享

同步

 心不留意外尘 2016-05-16

http://blog.csdn.net/zg_hover/article/details/3929833

2009


同 步

 

   在进行多线程和多进程编程的时候,总会遇到多个进程或多个线程对同一块数据的访问。这是我们就需要使用某种同步的手段来保证数据的正确访问。

 

1 互斥锁和条件变量

 使用范围:同一进程中的不同线程间;

1.1 互斥锁    

*基本概念
    互斥锁是指相互排斥,是最基本的同步形式。它可以用来保护临界区,以保证任何时候都只有多个线程中的一个线程在期间运行。

    注意:如果在多个进程间,而且每个进程都有独立的互斥锁变量的内存空间,这样每个进程都可以调用pthread_mutex_lock那么这样的互斥锁将失去意义。例如:

下面的代码是usp上的14-1的一段   

...

pthread_mutex_t mutex;

...

for (i = 1; i < n; i++)
      if (childpid = fork())            //创建进程链
         break;
   snprintf(buffer, BUFSIZE,
       "i:%d  process ID:%ld  parent ID:%ld  child ID:%ld/n",
       i, (long)getpid(), (long)getppid(), (long)childpid);
 
    c = buffer;
   /********** start of critical section *****************/

   //到这里每个子进程都拥有mutex的内存空间和变量值,所以这里无法完成互斥

   pthread_mutex_lock(&mutex);
   while (*c != '/0') {
      fputc(*c, stderr);
      c++;
      for (i = 0; i < delay; i++)
         dummy++;
   }

   pthread_mutex_unlock(&mutex);

 /************** end of critical section **************/
   if (r_wait(NULL) == -1)
      return 1;
   ...


*锁互斥的初始化
    .静态初始化
        static    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    .动态初始化           
        static pthread_mutex_t lock;
        pthread_mutex_init(&lock, NULL);

*使用形式
    static    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&lock);
    ... /* 需要保护的操作 */
    pthread_mutex_unlock(&lock);

*加锁函数
    //如果出错则阻塞
    pthread_mutex_lock(pthread_mutex_t *lock)
    //如果出错立即返回,errno=EBUSY
    pthread_mutex_trylock(pthread_mutex_t *lock)

1.2 条件变量
    如果没有条件变量线程可能一直阻塞或轮训的方式来进行相互的操作,也就是说线程根本就不知道是否可以进行下面的操作而,只有进行轮训去查询看是否满足条件。  
    条件变量和互斥锁结合才不至于线程进行忙等。       

*相关函数1   
    #include <pthread.h>
    int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mpthr);
注意:
    .在使用该函数前必须要用互斥锁锁住线程,然后再调用该函数。
    .在没有返回前该函数会自动释放mpthr锁。
    .该函数会阻塞在条件变量cptr上,
    .返回时,该函数又自动加上锁。
    .一般的使用结构
    pthread_mutex_lock(&l);
    while (condition is true)
        pthread_cond_wait(&cond, &l);
    pthread_mutex_unlock(&l);


*相关函数2   
    #include <pthread.h>
    int pthread_cond_broadcast(pthread_cond_t *cond);
    in pthread_cond_signal(pthread_cond_t *cptr);

 

注意:
       .第一个函数解除了所有阻塞在cond上的条件变量上的线程的阻塞。
    .第二个函数解除了至少一个阻塞在cond指向的条件变量上的线程。
    .调用这两个函数线程可以没有被加锁,但如果没有阻塞在条件变量的线程,那么它们不起任何作用。
    .如果有多个线程阻塞在条件变量cond上,那么那个先运行决定系统的调度。

 

 

实例:

  1. /* 
  2.  *  生产者和消费者在环形缓冲区中的实现和运用 
  3.  */  
  4. #include <stdio.h>  
  5. #include <stdlib.h>  
  6. #include <string.h>  
  7. #include <unistd.h>  
  8. #include <fcntl.h>  
  9. #include <signal.h>  
  10. #include <errno.h>  
  11. #include <pthread.h>  
  12. #include <sys/types.h>   
  13. #include <sys/stat.h>  
  14. #include <time.h>  
  15. #define MAXLEN 10  
  16. struct manqueue  
  17. {  
  18.     int arrlen;   /* array total length */     
  19.     int count;    /* current queue quantity */  
  20.     int putpos;  
  21.     int getpos;  
  22. };  
  23. struct _node  
  24. {  
  25.     char name[16];  
  26.     int  ID;     
  27. };  
  28. typedef struct _node Node;    /* A Node structure. */  
  29. static  Node st[MAXLEN];   /* Buffer length */  
  30. static struct manqueue mq;  
  31. static pthread_mutex_t mutex;   
  32. static pthread_cond_t items;  
  33. static pthread_cond_t slots;  
  34. static pthread_attr_t attr;  
  35. int reader(Node *st);  
  36. int  writer(Node *pnode);  
  37. static void *workproc(void *arg);  
  38. int   
  39. main(void)  
  40. {  
  41.     /*pthread_t rtid, wrtid;*/  
  42.     int ti,tj;  
  43.     Node tst[10];  
  44.     Node rst;  
  45.     pthread_t rtid,wtid;  
  46.     mq.count = 0;  
  47.     mq.putpos = 0;  
  48.     mq.getpos = 0;  
  49.     mq.arrlen = MAXLEN;    
  50.       
  51.     pthread_attr_init(&attr);  
  52.     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);  
  53.     pthread_mutex_init(&mutex, NULL);  
  54.     pthread_cond_init(&items, NULL);      
  55.     pthread_cond_init(&slots, NULL);  
  56.       
  57.     for (tj=0; tj<3; tj++) {  
  58.         pthread_create(&rtid, NULL, (void *)reader, &rst);  
  59.     }  
  60.       
  61.     for (ti=0; ti<10; ti++) {  
  62.         strcpy(tst[ti].name, "hov");  
  63.         tst[ti].ID = ti;  
  64.         pthread_create(&wtid, NULL, (void *)writer, tst+ti);  
  65.     }  
  66.       
  67.     pause();  
  68.     exit(0);  
  69. }  
  70. /* 
  71.  * Read queue buffer data. 
  72.  */  
  73. int   
  74. reader(Node *rst)  
  75. {  
  76.     while (1) {  
  77.         pthread_mutex_lock(&mutex);  
  78.         while(mq.count <= 0) /* queue is empty */  
  79.             pthread_cond_wait(&items, &mutex);   
  80.           
  81.         memmove(rst, &st[mq.getpos], sizeof(Node));  
  82.         printf("read->[%d]=[%s-%d]/n", mq.getpos, rst->name, rst->ID);  
  83.           
  84.         mq.getpos++;  
  85.         if(mq.getpos == mq.arrlen)   /* roll to header */  
  86.             mq.getpos = 0;  
  87.         mq.count--;  
  88.         //pthread_cond_signal(&slots);  //说明有空闲的位置了。  
  89.         pthread_mutex_unlock(&mutex);     
  90.    }  
  91.     return 0;  
  92. }  
  93. /*  
  94.  * Write pnode value to the buffer queue 
  95.  */  
  96. int    
  97. writer(Node *pnode)  
  98. {  
  99.     while (1) {  
  100.         sleep(1);   //这时为了让我们看清楚结果而加的  
  101.         pthread_mutex_lock(&mutex);  
  102.         /*      `                       //如果这里注释掉的话,就可能使得现在写的数据把 
  103.         while(mq.count >= mq.arrlen)     //已经写的数据覆盖掉。所以最好是加上它。 
  104.         {                                   //如果不加的话,说明本来就有可能丢失数据 
  105.             pthread_cond_wait(&slots, &mutex); 
  106.         } 
  107.         */  
  108.         //向空闲缓冲区中放数据  
  109.         strcpy(st[mq.putpos].name, pnode->name);  
  110.         st[mq.putpos].ID = pnode->ID;  
  111.         fprintf(stderr, "write->[%d]=[%s-%d]/n",   
  112.                         mq.putpos,st[mq.putpos].name, st[mq.putpos].ID);  
  113.           
  114.         /* roll to header */  
  115.         mq.putpos++;  
  116.         if(mq.putpos == mq.arrlen)  
  117.             mq.putpos = 0;  
  118.           
  119.         mq.count++;  
  120.           
  121.         pthread_cond_signal(&items);  
  122.         pthread_mutex_unlock(&mutex);  
  123.     }     
  124.     return 0;     
  125. }  

 

 

小结: 互斥锁和条件变量一般用于同一进程的不同线程间的同步, 用法比较简单,应用比较广,比较重要的就是环形缓冲区模型,以及生产者消费者模型。

 

2  posix信号灯


   信号灯是一种提供不同进程间或一个给定进程的不同线程间同步的原语。其中有三种信号灯比较常用:

       .posix有名信号灯

             posix有名信号灯使用posix IPC名字,可用于多个进程和线程间的同步。

       .posix内存信号灯

             posix内存信号灯存放在内存区中,可用于多个线程间的同步;如果多个进程间有共享内存,而信号灯变量在共享

       内存区内,那么posix内存信号灯也可以用于多个进程间的同步。

       .system v 信号灯:在内核中维护,可用于进程或线程的同步。

 

*二值信号灯和计数信号灯

    信号灯可以分为二值信号灯和计数信号灯。二值信号灯只有两个值0或1,而计数信号灯的值可以大于1。

 

*对信号的操作

    .创建信号一个信号灯。通常还需要调用者指定初始值,对于二值信号灯来说,它通常是1。对于计数信号灯要具体情况具体设定。

    .等待一个信号灯(P操作)。该操作测试信号灯的值,如果其值小于或等于0,那就阻塞,一旦其值大于0就将它减1。而且该操作必须是一个原子操作。

    .挂出一个信号灯(V操作)。该操作将信号灯的值加1,如果有一些进程等待该信号灯的值变为大于0,其中一个进程现在就可能被唤醒。该操作也必须是一个原子操作,所做的事情有:信号灯+1;解锁;发信号。

 

*信号灯的使用模型

    .互斥锁模式

          sem = 1;

          sem_wait(&sem);

          临界区

          sem_post(&sem);

 

    .生产者,消费者模型

 

          生产者                                                                  消费者

          get = 0;

          put = 1;

 

          for ( ; ; ) {                                                      for ( ; ; ) {

             sem_wait(&put);                                             sem_wait(&get);

             把数据放入缓冲区中                                             处理缓冲区中的数据

             sem_post(&get);                                             sem_post(&put);

          }                                                                    }

 

    .注意 :

        下面这种模型是错误的:

        sem_t mysem;

        sem_init(&mysem, 1, 0);   //the 2nd arg of 1 : shared between processes

        if (fork() == 0) {              //child

           ...

           sem_post(&mysem);

        }                                  

        sem_wait(&mysem);       //parent

 

        想想为什么?

          

 

*信号灯和互斥锁的差异:

    .互斥锁必须是由给他上锁的线程解锁,信号灯的挂出不必由执行过它的等待操作的同一线程执行。

    .在使用互斥锁时,如果线程发送了一个信号,但是没有线程在等待这个信号的到来,那么这个信号就会丢失。但信号量将会记下这次的信号量。不会丢失。

    .

 

*有名信号量和基于内存的信号量的函数调用

 

                有名信号灯                               基于内存的信号灯

              sem_open()                                sem_init()

                      /                                              /

                                                  

                                    sem_wait()

                                    sem_trywait()

                                    sem_post()

                                    sem_getvalue()

                          /                                      /

           sem_close()                               sem_destory()

           sem_unlink()

 

 

2.1 有名信号灯

 

 

     

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多