多线程学习

 linux多线程学习(二)多线程的创建和退出

http://blog.csdn.net/wtz1985/article/details/3792770

在上一篇文章中对线程进行了简单的概述,它在系统中和编程的应用中,扮演的角色是不言而喻的。学习它、掌握它、吃透它是作为一个程序员的必须作为。在接下来的讲述中,所有线程的操作都是用户级的操作。在LINUX中,一般pthread线程库是一套通用的线程库,是由POSIX提出的,因此他的移植性是非常好的。

     创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create。在线程创建之后,就开始运行相关的线程函数。在该函数运行结束,线程也会随着退出。这是其中退出线程的一种方法,另外一种退出线程的方法就是调用pthread_exit()函数接口,这是结束函数的主动行为。在这里要注意的是,在使用线程函数时,不要轻易调用exit()函数,因为这样会使整个进程退出,往往一个进程包含着多个线程,所以调用了exit()之后,所有该进程中的线程都会被结束掉。因此,在线程中,利用pthread_exit来替代进程中的exit

     由于一个进程中的数据段是共享的,因此通常在线程退出之后,退出线程所占的资源并不会随着线程的结束而得到释放。正如进程之间可以调用wait()函数来同步中指并释放资源一样,线程之间也有类似的机制,那就是pthread_join函数.pthread_join可以将当前线程挂起,等待线程的结束,这个函数是一个阻塞函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待函数的资源就会被释放。

1、函数语法简述。

pthread_create

头文件:       pthread.h

函数原型:    int pthread_create (pthread_t* thread,pthread_attr_t* attr,

                                                 void* (start_routine)(void*), void* arg);

函数传入值: thread: 线程标识符

                  attr: 线程属性设置

                  start_routine:线程函数入口

                  arg:传递给线程函数的参数

返回值:       0成功

                  -1失败 

pthread_exit

头文件:      pthread.h

函数原型:   void pthread_exit (void*  retval);

函数传入值:retvalpthread_exit()调用者线程的返回值,可又其他函数如pthread_join来检索获取。

phread_join

头文件:      pthread.h

函数原型:   int pthread_join (pthread_t* thread,void** thread_return);

函数传入值:thread:等待线程的标识符。

                  thread_return:用户定义的指针,用来存储被等待线程的返回值(不为NULL值);

函数返回值:成功: 0

                  失败:-1

 

函数定义: int pthread_join(pthread_tthread, void **retval);

描述:pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

参数:thread: 线程标识符,即线程ID,标识唯一线程。retval:用户定义的指针,用来存储被等待线程的返回值。

返回值 : 0代表成功。失败,返回的则是错误号。

 

2、函数举例实现。

 

[c-sharp] view plaincopy

1.  #include <stdlib.h>    

2.  #include <stdio.h>    

3.  #include <pthread.h>    

4.  #include <errno.h>    

5.       

6.  static void* pthread_func_1 (void*);    

7.  static void* pthread_func_2 (void*);    

8.      

9.  int main (int argc, char** argv)    

10. {    

11.   pthread_t pt_1 = 0;    

12.   pthread_t pt_2 = 0;    

13.   int ret = 0;    

14.      

15.   ret = pthread_create (&pt_1, NULL, pthread_func_1, NULL);    

16.   if (ret != 0)    

17.   {    

18.      perror ("pthread_1_create");    

19.   }    

20.     

21.   ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL);    

22.   if (ret != 0)    

23.   {    

24.       perror ("pthread_2_create");    

25.    }    

26.      

27.    pthread_join (pt_1, NULL);    

28.    pthread_join (pt_2, NULL);    

29.      

30.    return 0;    

31. }    

32.      

33. static void* pthread_func_1 (void* data)    

34. {    

35.    int i = 0;    

36.        

37.    for (; i < 6; i++)    

38.    {    

39.      printf ("This is pthread1!/n");    

40.      

41.      if (i == 2)    

42.      {    

43.        pthread_exit (0);    

44.      }    

45.      

46.      sleep (1);    

47.    }    

48.   

49.    return NULL;  

50. }    

51.      

52. static void* pthread_func_2 (void* data)    

53. {    

54.    int i = 0;    

55.      

56.    for (; i < 3; i++)    

57.    {    

58.      printf ("This is pthread2!/n");    

59.    }    

60.      

61.    pthread_exit (0);    

62.   

63.    return NULL;  

64. }    

 

在上面的例子中只是简单的创建了线程、主动退出线程和挂起线程。在下一篇文章中,将讲述线程线的属性及其设定。

 

~~END~~

 

 

linux多线程学习()——互斥锁线程控制

在前面的文章中提及到,一个进程中的多个线程是共享同一段资源的,由于线程对资源的竞争引出了锁。其中mutex是一种简单的加锁方法,这个互斥锁只有两种状态,那就是上锁和解锁,可以把互斥锁看作是某种意义上的全局变量。在某一时刻,只能有一个线程取得这个互斥上的锁,拥有上锁状态的线程可以对共享资源进行操作,而其他线程在该线程未解锁之前,够会被挂起,直到上锁的线程解开锁。可以这么说,互斥锁使得共享资源按序的在各个线程上操作。

互斥锁的操作主要包括互斥锁初始化、上锁、判断上锁、解锁、摧毁互斥锁。其中互斥锁可以分为快速互斥锁、递归互斥锁这检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要等待挂起。快速锁是指调用线程会阻塞直到线程锁得到解锁为止。递归锁能够成功地返回并且增加调用线程在互斥上的加锁次数,比如一个链表在进行插入的操作时,可以进行查找的操作。检错锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误的信息。

1、函数简义。

1pthread_mutex_init

头文件:                 <pthread.h>

函数原型:              int pthread_mutex_init (pthread_mutex_t* mutex,

                                                                         constpthread_mutexattr_t* mutexattr);

函数传入值:           mutex:互斥锁。

                              mutexattr:PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁。 

                                              PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁。

                                               PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁。

函数返回值:            成功:0

                             出错:-1 

2)上锁函数:

intpthread_mutex_lock(pthread_mutex_t* mutex);

intpthread_mutex_trylock (pthread_mutex_t* mutex);

intpthread_mutex_unlock (pthread_mutex_t* mutex);

intpthread_mutex_destroy (pthread_mutex_t* mutex);

函数传入值:           mutex:互斥锁。

函数返回值:           同上。

2、互斥锁实现。

[cpp] view plaincopy

include <stdio.h>    

#include <stdlib.h>    

#include <pthread.h>    

#include <errno.h>    

#include <unistd.h>    

    

#define  return_if_fail(p)  /  

    if((p) == 0){printf("[%s]:func error!/n",__func__);return;}    

     

typedef struct _PrivInfo    

{    

   pthread_mutex_t mutex;    

   int lock_var;    

   time_t end_time;    

}PrivInfo;    

    

static void info_init (PrivInfo* thiz);    

static void* pthread_func_1 (PrivInfo* thiz);    

static void* pthread_func_2 (PrivInfo* thiz);    

 

    

int main (int argc, char** argv)    

{    

   pthread_t pt_1 = 0;    

   pthread_t pt_2 = 0;    

   int ret = 0;    

   PrivInfo* thiz = NULL;    

    

   thiz = (PrivInfo*)malloc (sizeof (PrivInfo));    

   if (thiz == NULL)    

   {    

      return -1;    

   }   

    

   info_init(thiz);    

    

   ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);    

   if (ret != 0)    

   {    

     perror ("pthread_1_create:");    

   }    

 

   ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);    

   {    

     perror ("pthread_2_create:");    

   }    

    

   pthread_join (pt_1, NULL);    

   pthread_join (pt_2, NULL);    

    

   pthread_mutex_destroy (&thiz->mutex);    

       

   free (thiz);    

   thiz = NULL;    

    

   return 0;    

}    

    

static void info_init (PrivInfo* thiz)    

{    

   return_if_fail (thiz != NULL);    

    

   thiz->lock_var = 0;    

   thiz->end_time = time (NULL) + 10;    

    

   pthread_mutex_init (&thiz->mutex, NULL);    

    

   return;    

}    

    

static void* pthread_func_1 (PrivInfo* thiz)    

{    

   return_if_fail (thiz != NULL);    

    

   int i = 0;    

   int ret = 0;    

    

   while (time (NULL) < thiz->end_time)    

   {    

     ret  = pthread_mutex_lock (&thiz->mutex);    

     if (ret != 0)    

     {    

         perror ("[%s]pthread_mutex_lock");    

     }    

     else    

     {    

       printf ("pthread1:pthread1 lock the variable!/n");    

     }    

    

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

     {    

        sleep (1);    

        thiz->lock_var ++;    

     }    

    

     ret = pthread_mutex_unlock (&thiz->mutex);    

     if (ret != 0)    

     {    

        perror ("[%s]pthread_mutex_unlock");    

     }    

     else    

     {    

        printf ("pthread1: pthread1 unlock the variable./n");    

     }         

   }    

    

   return;    

}    

    

static void* pthread_func_2 (PrivInfo* thiz)    

{    

   return_if_fail (thiz != NULL);    

    

   int ret = 0;    

       

   while (time (NULL) < thiz->end_time)    

   {    

      ret = pthread_mutex_trylock (&thiz->mutex);    

      if (ret == EBUSY)    

      {    

         printf ("pthread2:the variable is locked by thread1./n");    

      }    

      else    

      {    

         if (ret != 0)    

         {    

             perror ("[%s]pthread2_mutex_trylock");     

         }    

         else    

         {    

            printf ("pthread2: pthread2 lock the variable./n");    

         }             

          

    

         ret = pthread_mutex_unlock (&thiz->mutex);    

         if (ret != 0)    

         {    

           perror ("[%s]pthread_mutex_lock");    

         }    

    

         sleep (3);    

      }    

    }    

}    

在上例的中,是对变量lock_var的读写进行加锁,线程一是对其进行写,在写的时候,线程二不能对其进行读。直到线程一解锁,线程二得到互斥锁,才能进行读。同样,在线程二进行读的时候,线程一不能进行写。

今天讲述到这里,下一篇文章将讲述怎样通过信号量来达到互斥锁的效果。

 

linux多线程学习(五)——信号量线程控制

在上一篇文章中,讲述了线程中互斥锁的使用,达到对共享资源互斥使用。除了使用互斥锁,信号量,也就是操作系统中所提到的PV原语,能达到互斥和同步的效果,这就是今天我们所要讲述的信号量线程控制。

PV原语是对整数计数器信号量sem的操作,一次P操作可使sem减一,而一次V操作可是sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量的值大于零或等于零的时候,该进程(或线程)具有对公共资源访问的权限,否则,当信号量的值小于时,该进程(或线程)就会被阻塞,直到信号量的值大于或等于一。

1、在LINUX中,实现了POSIX的无名信号量,主要用于线程间的互斥同步,下面将简单介绍一些函数接口:

1)、sem_init

功能:         用于创建一个信号量,并初始化信号量的值。

头文件:       <semaphore.h>

函数原型:     int sem_init (sem_t* sem, intpshared, unsigned int value);

函数传入值:   sem:信号量。

                   pshared:决定信号量能否在几个进程间共享。由于目前LINUX还没有实现进程间共享信息量,所以这个值只能取0

函数返回值:   0:成功。

                  -1:失败。

2)其他函数。

intsem_wait       (sem_t* sem);

intsem_trywait   (sem_t* sem);

intsem_post       (sem_t* sem);

int sem_getvalue(sem_t* sem);

intsem_destroy   (sem_t* sem);

功能:sem_waitsem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量的值小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。

       sem_post相当于V操作,它将信号量的值加一,同时发出唤醒的信号给等待的进程

      (或线程)。

       sem_getvalue 得到信号量的值。

       sem_destroy 摧毁信号量。

函数传入值: sem:信号量。

函数返回值:同上。

2、函数实现。

[c-sharp] view plaincopy

#include <stdio.h>    

#include <stdlib.h>    

#include <unistd.h>    

#include <pthread.h>    

#include <semaphore.h>    

#include <errno.h>    

  

#define return_if_fail(p)  /  

    if((p) == 0){printf ("[%s]: func error!", __func__);return;}    

    

typedef struct _PrivInfo    

{    

  sem_t    sem;    

  int      lock_var;    

  time_t   end_time;    

}PrivInfo;    

    

static void info_init (PrivInfo* thiz);    

static void* pthread_func_1 (PrivInfo* thiz);    

static void* pthread_func_2 (PrivInfo* thiz);    

    

int main (int argc, char** argv)    

{    

  pthread_t pt_1 = 0;    

  pthread_t pt_2 = 0;    

  int ret = 0;    

  PrivInfo* thiz = NULL;    

    

  thiz = (PrivInfo* )malloc (sizeof (PrivInfo));    

  if (thiz == NULL)    

  {    

    printf ("[%s]:Failed to malloc PrivInfo./n");    

    return -1;    

  }    

    

  info_init (thiz);    

      

  ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);    

  if (ret != 0)    

  {    

    perror ("pthread_1_create:");    

  }    

    

  ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);    

  if (ret != 0)    

  {    

    perror ("pthread_2_create:");    

  }    

    

  pthread_join (pt_1, NULL);    

  pthread_join (pt_2, NULL);    

    

  sem_destroy (&thiz->sem);    

  free (thiz);    

  thiz = NULL;    

    

  return;    

}    

    

static void info_init (PrivInfo* thiz)    

{    

   return_if_fail (thiz != NULL);    

    

   thiz->lock_var = 0;    

   thiz->end_time = time(NULL) + 10;    

   sem_init (&thiz->sem, 0,  1);      

   return;    

}    

    

static void* pthread_func_1 (PrivInfo* thiz)    

{    

  return_if_fail(thiz != NULL);    

    

  int i = 0;    

    

  while (time(NULL) < thiz->end_time)    

  {    

     sem_wait (&thiz->sem);    

     printf ("pthread: pthread1 get lock./n");    

    

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

     {    

         thiz->lock_var ++;    

         sleep (1);    

     }    

    

     sem_post (&thiz->sem);    

     printf ("pthread1: pthread1 unlock/n");    

    

     sleep (1);    

  }    

    

   return;    

}    

    

static void* pthread_func_2 (PrivInfo* thiz)    

{    

   return_if_fail (thiz != NULL);    

    

   while (time (NULL) < thiz->end_time)    

   {    

      sem_wait (&thiz->sem);    

      printf ("pthread2: pthread2 get lock!/n");    

      printf ("the lock_var = %d/n", thiz->lock_var);    

    

      sem_post (&thiz->sem);    

      printf ("phtread2: pthread2 unlock./n");    

    

      sleep (3);    

   }    

    

   return;    

}    

从上面的实例中可以看出,通过信号量实现共享资源中的互斥使用,跟上一篇文章中的互斥锁的效果是一样的。但是通过互斥锁还有一个更加方便的功能,就是同步。下一篇文章将讲述线程间通过信号量的同步实现。

 

线程同步和线程异步有什么区别?

 

线程同步和线程异步有什么区别?(重要基础知识)

打个比方,如果你在等一个人,

同步的时候,你会一直等到她来了之后才做其他事情,这个过程除了等待你啥都不会做,

异步的时候,你一边在等,可能一边玩游戏或者是看报纸什么的,一直到她到来,你的等待状态才会结束

在实现上,同步的过程会阻塞进程的所有其他操作,将同步转换为异步的最常见方法则是

将会阻塞进程的等待操作放入到一个新的进程中,同时为该等待操作添加一个监视器,在检测到等待操作完成的时候结束等待的进程。

 

 

linux多线程学习(六)——信号量实现同步。

在上一篇文章中已经用信号量来实现线程间的互斥,达到了互斥锁的效果,今天这篇文章将讲述怎样用信号量去实现同步。

信号量的互斥同步都是通过PV原语来操作的,我们可以通过注册两个信号量,让它们在互斥的问题上互动,从而达到同步。通过下面实例就可以很容易理解:

 

[cpp] view plaincopy

#include <stdlib.h>    

#include <stdio.h>    

#include <unistd.h>    

#include <pthread.h>    

#include <semaphore.h>    

#include <errno.h>    

    

#define  return_if_fail(p)  if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}    

    

typedef struct _PrivInfo    

{    

  sem_t s1;    

  sem_t s2;    

  time_t end_time;    

}PrivInfo;    

    

static void info_init (PrivInfo* thiz);    

static void info_destroy (PrivInfo* thiz);    

static void* pthread_func_1 (PrivInfo* thiz);    

static void* pthread_func_2 (PrivInfo* thiz);    

    

int main (int argc, char** argv)    

{    

  pthread_t pt_1 = 0;    

  pthread_t pt_2 = 0;    

  int ret = 0;    

  PrivInfo* thiz = NULL;    

      

  thiz = (PrivInfo* )malloc (sizeof (PrivInfo));    

  if (thiz == NULL)    

  {    

    printf ("[%s]: Failed to malloc priv./n");    

    return -1;    

  }    

    

  info_init (thiz);    

    

  ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);    

  if (ret != 0)    

  {    

    perror ("pthread_1_create:");    

  }    

    

  ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);    

  if (ret != 0)    

  {    

     perror ("pthread_2_create:");    

  }    

    

  pthread_join (pt_1, NULL);    

  pthread_join (pt_2, NULL);    

    

  info_destroy (thiz);    

      

  return 0;    

}    

    

static void info_init (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

  thiz->end_time = time(NULL) + 10;    

      

  sem_init (&thiz->s1, 0, 1);    

  sem_init (&thiz->s2, 0, 0);    

    

  return;    

}    

    

static void info_destroy (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

  sem_destroy (&thiz->s1);    

  sem_destroy (&thiz->s2);    

    

  free (thiz);    

  thiz = NULL;    

    

  return;    

}    

    

static void* pthread_func_1 (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

  while (time(NULL) < thiz->end_time)    

  {    

    sem_wait (&thiz->s2);    

    printf ("pthread1: pthread1 get the lock./n");    

    

    sem_post (&thiz->s1);    

    printf ("pthread1: pthread1 unlock/n");    

    

    sleep (1);    

  }    

    

  return;    

}    

    

static void* pthread_func_2 (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

  while (time (NULL) < thiz->end_time)    

  {    

    sem_wait (&thiz->s1);    

    printf ("pthread2: pthread2 get the unlock./n");    

    

    sem_post (&thiz->s2);    

    printf ("pthread2: pthread2 unlock./n");    

    

    sleep (1);    

  }    

    

  return;    

}    

通过执行结果后,可以看出,会先执行线程二的函数,然后再执行线程一的函数。它们两就实现了同步。在上大学的时候,虽然对这些概念知道,可都没有实践过,所以有时候时间一久就会模糊甚至忘记,到了工作如果还保持这么一种状态,那就太可怕了。虽然现在外面的技术在不断的变化更新,可是不管怎么变,其核心技术还是依旧的,所以我们必须要打好自己的基础,再学习其他新的知识,那时候再学新的知识也会觉得比较简单的。闲话多说了两句,在下一篇文章中,我们将会实现一个经典的实例回顾这段时间对多线程的学习,那就是消费者和生产者

linux多线程学习(七)——实现“生产者和消费者”

在上一篇文章中,利用信号量实现了线程间的互斥,这一篇将要利用信号量的互斥同步机制来实现一个经典实例,就是生产者和消费者

1、简单描述生产者和消费者的问题。

有一个缓冲区和两个线程:生产者和消费者。生产者把产品放入缓冲区,而消费者从缓冲区中拿走。当缓冲区满时,生产者必须等待;另外,当缓冲区空时,消费者必须等待,并且缓冲区不能同时进行生产者和消费者的操作。

[cpp] view plaincopy

#include <stdio.h>    

#include <stdlib.h>    

#include <pthread.h>    

#include <semaphore.h>    

#include <errno.h>    

#include <fcntl.h>    

#include <string.h>  

    

#define FIFO "my_fifo"    

#define N    10    

#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}    

    

typedef struct _PrivInfo    

{    

  sem_t avail;    

  sem_t full;    

  sem_t mutex;  

  char  buf_r[255];    

  time_t end_time;    

  int fd;    

}PrivInfo;    

    

static void info_init (PrivInfo* thiz);    

static void info_destroy (PrivInfo* thiz);    

static void* productor (PrivInfo* thiz);    

static void* consumer (PrivInfo* thiz);    

    

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

{    

  pthread_t pt_1 = 0;    

  pthread_t pt_2 = 0;    

  int ret = 0;    

  PrivInfo* thiz = NULL;    

      

  thiz = (PrivInfo*) malloc (sizeof (PrivInfo));    

  if (thiz == NULL)    

  {    

    printf ("[%s]: Failed to malloc PrivInfo./n", __func__);    

    return -1;    

  }    

    

  info_init (thiz);    

    

  ret = pthread_create (&pt_1, NULL, (void*)productor, thiz);    

  if (ret != 0)    

  {    

    perror ("pthread_1_create:");    

  }    

    

  ret = pthread_create (&pt_2, NULL, (void*)consumer, thiz);    

  if (ret != 0)    

  {    

    perror ("pthread_2_func:");    

  }    

    

  pthread_join (pt_1, NULL);    

  pthread_join (pt_2, NULL);    

    

  info_destroy (thiz);    

  thiz = NULL;    

    

  return 0;    

}    

    

static void info_init (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

  thiz->end_time = time(NULL) + 10;    

      

  sem_init (&thiz->avail, 0, N);    

  sem_init (&thiz->full, 0, 0);    

  sem_init (&thiz->mutex, 0, 1);    

    

  if (mkfifo (FIFO, O_CREAT|O_EXCL) && (errno != EEXIST))    

  {    

    printf ("cannot create fifo server./n");    

  }    

  

  printf ("Prepareing for reading bytes.../n");    

    

  memset (thiz->buf_r, 0, sizeof (thiz->buf_r));    

    

  thiz->fd = open (FIFO, O_RDWR|O_NONBLOCK, 0777);    

  if (thiz->fd == -1)    

  {    

    perror ("open FIFO:");    

    info_destroy (thiz);    

    exit (1);    

  }    

    

  return ;    

}    

    

static void info_destroy (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

  sem_destroy (&thiz->avail);    

  sem_destroy (&thiz->full);    

  sem_destroy (&thiz->mutex);    

    

  free (thiz);    

  thiz = NULL;    

    

  return;    

}    

    

static void* productor (PrivInfo* thiz)    

{    

  return_if_fail (thiz != NULL);    

    

 int ret = 0;    

    

  while (time (NULL) < thiz->end_time)    

  {    

    sem_wait (&thiz->avail);    

    sem_wait (&thiz->mutex);    

    

    if ((ret = write (thiz->fd, "hello", 5)) == -1)    

    {    

       if (errno == EAGAIN)    

       {    

           printf ("The FIFO has not been read, Please try later again./n");    

       }    

       else    

       {    

           printf ("write hello to the FIFO./n");    

       }    

    }    

    

    sem_post (&thiz->full);    

    sem_post (&thiz->mutex);    

    

    sleep (1);    

  }    

    

  return;    

}    

    

static void* consumer (PrivInfo* thiz)    

{    

   return_if_fail (thiz != NULL);    

    

   int ret = 0;    

    

   while (time (NULL) < thiz->end_time)    

  {    

     sem_wait (&thiz->full);    

     sem_wait (&thiz->mutex);    

    

     if ((ret = read(thiz->fd, thiz->buf_r, 255)) > 0)    

     {    

                  

        printf ("read %s frm FIFO./n", thiz->buf_r);    

      }    

      else   

      {  

        if (errno == EAGAIN)    

        {    

           printf ("no data yet./n");     

        }  

      }  

    

      sem_post (&thiz->avail);    

      sem_post (&thiz->mutex);    

  

      sleep(1);  

  }    

    

  return;    

}    

从上面的经典例子中,结合了多线程的同时性编程问题。学习线程算是告了一段落,在以后的编程应用中,希望能落实到实践中去,大家一起加油~~

 

 

 

 

 

 

 

linux线程基本函数笔记 2010-07-2814:28:31

分类: C/C++

 

1、linux中的中主要用的是进程,对线程的操作优势不如windows

2、主要以pthread_开头,在用gcc来build时要加上-lpthread选项。

3、头文件在<pthread.h>

4、一个进程中的大部分东西都是多个线程共享的,但是一下东西在每个线程是独立的:线程id号,寄存器集合,用于保存局部变量和返回地址的堆栈,errno,信号掩码,运行的优先级。

5、5个函数:

a、int pthread_create(pthread_t *tid, constpthread_attr_t *attr, void*(*func)(void*), void *arg); 相当于fork函数。

注意成功返回0,线程id通过tid返回。

一般情况下,attr只需要传递空指针即可,(属性用系统默认的就足够了)

注意:func是线程调用的函数,参数是通用指针void*,返回也是通用指针void*,arg是该函数的参数。

b、int pthread_join(pthread_t tid, void **status); 相当于waitpid函数

等待tid线程终止,如果status非空,则tid线程终止的返回值将传到*status地方。

c、pthread_t pthread_self(void); 相当于getpid函数。

d、int ptrhead_detach(pthread_t tid)

线程状态或者是可汇合的(joinable),或者是可脱离的(detached)(默认为可汇合的)

脱离的线程像守护进程(daemon):所有的资源将会被释放,因此不能用pthread_join等待其终止。

一般用法,使自己脱离:pthread_detach(pthread_self());

e、void pthread_exit(void *status)

如果该线程未脱离,则其线程id和推出状态一直保留到某个线程调用pthread_join为止。
终止线程的其他情况:

启动线程的func函数调用return 或者是 本进程的main函数返回 或者 某个线程调用了exit(这种情况本进程及其所有线程都终止)


===============================================================================

今天写了个程序,但是很郁闷的是没有得到预想的结果:

 

pthread_t tid;

int main(void)

{

    pthread_attr_t attr;

    pthread_attr_init(&attr);

    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

    pthread_create(&attr,&tid, func, NULL);

    pthread_attr_destroy(&attr);

    printf("Main routeexit.\n");

    return 0;

}

void * func(void *args)

{

    int i=0;

    for(; 1;)

        printf("Thread:i is %d\n", i);

    return ((void *)0);

}

 

运行结果为,一直打印Thread:i,过了1s左右,打印Mainroute exit.然后所有打印结束了。

但是用detach方法新建thread,不是应该和主线程分离的么?想不同了。

 

------------------------------------------

ok, 还是贴一下pthread常用的几个方法,以免遗忘了。

#include <pthread.h>

 

int pthread_create(struct pthread_attr_t *, pthread_t *,void *f*(void *), void *);

    创建一个线程,f为线程函数。默认情况下线程属性为joinable,即当主线程退出时,线程也会退出。

int pthread_join(pthread_t, void **);

    主线程阻塞直到这个thread结束后才继续往下走。这里第二个参数接收线程退出的返回值。

int pthread_exit(void);

    线程自己主动退出。

int pthread_cancel(pthread_t);

    被动退出。一个线程结束另外一个线程的运行。这里包括同步退出(默认),即线程立刻结束。

    以及异步退出,即线程直到下一个cancellation point才退出。线程可以设置这样的cancellation point,

    当然也可以设置为不被其他线程命令退出。

int pthread_self(void);

    得到自己的thread id

----------------------------------------

 

编译时需要加上-lpthread选项。

另外,发现c99不允许在for中直接声明变量,厄。。。

 

 

 pthread编程基础 

http://blog.chinaunix.net/uid-20528014-id-333508.html

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。与vxworks上任务的概念类似,都是调度的最小单元,都有共享的堆、栈、代码区、全局变量等。

 

2.   创建线程

int  pthread_create(pthread_t  *  thread,

pthread_attr_t *attr,

void *(*start_routine)(void *),

void * arg)

thread:返回创建的线程的ID

attr:线程属性,调度策略、优先级等都在这里设置,如果为NULL则表示用默认属性

start_routine:线程入口函数,可以返回一个void*类型的返回值,该返回值可由pthread_join()捕获

arg:传给start_routine的参数, 可以为NULL

返回值:成功返回0

 

3.   设置线程属性

线程属性通过attr进行设置。

设置与查询attr结构的为pthread_attr_get***()pthread_attr_set***()两个函数系列。

设置与查询线程参数的为pthread_get***()pthread_set***()两个函数系列。也可以在创建时通过pthrea_create传入参数。

注:有些必须在线程创建时设置,如调度策略。

 

3.1.  调度策略

调度策略有三种:

SCHED_OTHER:非实时、正常

SCHED_RR:实时、轮询法

SCHED_FIFO:实时、先入先出,与vxworks的调度机制一致

例程:

pthread_attr_tattr;

pthread_attr_init(&attr);

pthread_attr_setschedpolicy(&attr,SCHED_FIFO);//sched_policy

 

3.2.  优先级

线程优先级支持两种设置方式,一种是创建时设置,另一种是创建后动态设置

例程:

pthread_attr_setschedparam(&attr,new_priority);

如果是静态设置,则之后会用该属性创建任务,如果是动态设置,则使用下列函数设置:

pthread_attr_setschedparam(&attr,&task->prv_priority);

pthread_attr_getschedparam(&attr,&schedparam);

schedparam.sched_priority= new_priority;

pthread_attr_setschedparam(&attr,&schedparam);

pthread_setschedparam(pthrid,sched_policy, &schedparam);

 

3.3.  脱离同步

Pthread_join()函数可以使主线程与子线程保持同步。如果设置了detachstate状态,则pthread_join()会失效,线程会自动释放所占用的资源。线程的缺省状态为PHREAD_CREATE_JOINABLE状态,线程运行起来后,一旦被设置为PTHREAD_CREATE_DETACH状态,则无法再恢复到joinable状态。

例程:

pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);

 

3.4.  调度继承

线程A创建了线程B,则线程B的调度策略是与线程A的调度策略与线程B的继承策略有关的。如果线程B继承策略为PTHREAD_INHERIT_SCHED,则线程B的调度策略与线程A相同;如果线程B继承策略为PTHREAD_EXPLICIT_SCHE,则线程B的调度策略由attr决定。

pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

4.   线程取消

4.1.  线程取消定义

Pthread线程可以通过发送取消请求的方式终止一个线程的运行。

取消线程的操作主要应用于下列场景中:有一个线程在使用select监控网口,主控线程此时接到了用户的通知,要放弃监听,则主控线程会向监听线程发送取消请求。

Linuxpthread线程接收到取消请求时,并不会立刻终止线程,而是要等到取消点时才会结束任务。这样我们可以为取消点建立某些特殊的处理。Select是取消点,所以可以退出。

 

4.2.  取消点

Vxworks可以在任意位置杀死任务,这样做导致任务被杀死后的位置不可控,所以vxworks中不会很少使用taskDeleteHook

Pthread规定了取消点的概念。不论线程何时收到取消请求,都只能在取消点上才能取消线程。这就保证了风险的可控。

Pthread标准指定了以下几个取消点:

Ø  pthread_testcancel

Ø  所有调度点,如pthread_cond_waitsigwaitselectsleep

根据POSIX标准,read()write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();

retcode =read(fd, buffer, length);

pthread_testcancel();

这段代码可以保证read附近有取消点,但是否有可能会卡在read中无法返回呢?

 

如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

 

4.3.  线程取消函数

int pthread_cancel(pthread_t thread)

发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

int pthread_setcancelstate(int state, int*oldstate)

设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。

int pthread_setcanceltype(int type, int*oldtype)

设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。

void pthread_testcancel(void)

检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。

 

4.4.  例程

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

 

pthread_key_t key;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond =   PTHREAD_COND_INITIALIZER;

unsigned long abc=0;

 

void* Test03(void *p)

{

    printf("Cancel point");

    return NULL;

}

 

void* Test01(void* ptr)

{

    pthread_cleanup_push(Test03, NULL); /* push */

    while(1)

    {

        abc++;

        pthread_testcancel();

    }

    pthread_cleanup_pop(0); /* pop */

    return NULL;

}

 

void* Test02(void* ptr)

{

    while(1)

    {

        sleep(2);

        printf("2222cond_wait:abc=0x%08x\n", abc);

    }

   

    return NULL;

}

 

int main(void)

{

    int tid1, tid2;

    int ret;

 

    printf("Start:\n");

    ret = pthread_create(&tid1, NULL, Test01, NULL);

    ret = pthread_create(&tid2, NULL, Test02, NULL);

 

    sleep(6);

    pthread_cancel(tid1);

    pthread_join(tid1, NULL);

    pthread_join(tid2, NULL);

   

    return 0;

}

 

结果:

Start:

2222cond_wait:abc=0x22c29a05

2222cond_wait:abc=0x47b49007

Cancelpoint2222cond_wait:abc=0x6c9de3ad

2222cond_wait:abc=0x6c9de3ad

2222cond_wait:abc=0x6c9de3ad

 

如果不加取消点pthread_testcancel(),线程1无法退出。

5.   线程终止方式

线程终止有两种情况(不考虑进程),一种是线程主体函数return时,线程会自动终止,此时的退出是可预知的;另一种是其它线程向通以进程下的线程发送取消请求,线程会根据情况判断是否终止,此时的终止是不可预知的。

Vxworks中的任务与此类似。任务的主体函数return时,任务会自动终止;其它任务调用taskDelete()可以杀死任意一个任务。TaskDelete()并不安全,因为任务可能被杀死在任意一个时刻。

5.1.  线程终止时的清理

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源,相当于是增加了一个析构函数。从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。

API定义如下:

void pthread_cleanup_push(void (*routine) (void  *),  void *arg)

void pthread_cleanup_pop(int execute)

 

pthread_cleanup_push()/pthread_cleanup_pop()

采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。

execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

 

这两个其实是宏,必须成对出现,否则会编译不过。

编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。

pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);

pthread_mutex_lock(&mut);

/* do some work */

pthread_mutex_unlock(&mut);

pthread_cleanup_pop(0);

 

必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,pthread中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:

{

int oldtype;

 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

 pthread_cleanup_push(routine, arg);

 ...

 pthread_cleanup_pop(execute);

 pthread_setcanceltype(oldtype, NULL);

 }

 

5.2.  线程终止的同步及其返回值

一般情况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。

void pthread_exit(void *retval)

int pthread_join(pthread_t th, void **thread_return)

int pthread_detach(pthread_t th)

 

pthread_join()的调用者将挂起并等待th线程终止,retvalpthread_exit()调用者线程(线程IDth)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。

如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。

一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收。

 

5.3.  关于ptread_exite()return

理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。

在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。

其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault

 

5.4.  判断是否为同一个线程

intpthread_equal(pthread_t thread1, pthread_t thread2)

判断两个线程描述符是否指向同一线程。在LinuxThreads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1thread2是否相等。

 

5.5.  仅执行一次的操作

int pthread_once(pthread_once_t*once_control, void (*init_routine) (void))

本函数使用初值为PTHREAD_ONCE_INITonce_control变量保证init_routine()函数在本进程执行序列中仅执行一次。这个类似与线程的构造函数。

#include <stdio.h>

#include <pthread.h>

pthread_once_t  once=PTHREAD_ONCE_INIT;

void    once_run(void)

{

        printf("once_run in thread %d\n",pthread_self());

}

void * child1(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_once(&once,once_run);

        printf("thread %d returns\n",tid);

}

void * child2(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_once(&once,once_run);

        printf("thread %d returns\n",tid);

}

int main(void)

{

        int tid1,tid2;

        printf("hello\n");

        pthread_create(&tid1,NULL,child1,NULL);

        pthread_create(&tid2,NULL,child2,NULL);

        sleep(10);

        printf("main thread exit\n");

        return 0;

}

 

once_run()函数仅执行一次,且究竟在哪个线程中执行是不定的,尽管pthread_once(&once,once_run)出现在两个线程中。

LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是PTHREAD_ONCE_INITLinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER0)、IN_PROGRESS1)、DONE2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0

pthread_kill_other_threads_np()

voidpthread_kill_other_threads_np(void)

这个函数是LinuxThreads针对本身无法实现的POSIX约定而做的扩展。POSIX要求当进程的某一个线程执行exec*系统调用在进程空间中加载另一个程序时,当前进程的所有线程都应终止。由于LinuxThreads的局限性,该机制无法在exec中实现,因此要求线程执行exec前手工终止其他所有线程。pthread_kill_other_threads_np()的作用就是这个。

需要注意的是,pthread_kill_other_threads_np()并没有通过pthread_cancel()来终止线程,而是直接向管理线程发"进程退出"信号,使所有其他线程都结束运行,而不经过Cancel动作,当然也不会执行退出回调函数。尽管LinuxThreads的实验结果与文档说明相同,但代码实现中却是用的__pthread_sig_cancel信号来kill线程,应该效果与执行pthread_cancel()是一样的,其中原因目前还不清楚。

6.   TSD

6.1.  TSD概念

在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由Posix线程库维护,称为线程私有数据(Thread-specific Data,或TSD)。

 

6.2.  创建与注销

Posix定义了两个API分别用来创建和注销TSD:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))

 该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:

static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };

创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。注销一个TSD采用如下API:

int pthread_key_delete(pthread_key_t key)

这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。 6.3.  访问TSD的读写都通过专门的PosixThread函数进行,其API定义如下:

int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)void * pthread_getspecific(pthread_key_t key)

写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void *,因此可以指向任何类型的数据。在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由以下几个宏来说明:

#define PTHREAD_KEY_2NDLEVEL_SIZE       32#define PTHREAD_KEY_1STLEVEL_SIZE   \((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)/ PTHREAD_KEY_2NDLEVEL_SIZE)    其中在/usr/include/bits/local_lim.h中定义了PTHREAD_KEYS_MAX为1024,    因此一维数组大小为32。而具体存放的位置由key值经过以下计算得到:idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZEidx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE

也就是说,数据存放与一个32×32的稀疏矩阵中。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。

 

6.4.  使用范例

/* fireaxe的例程 */

#include <stdio.h>

#include <pthread.h>

pthread_key_t   key;

void echomsg(int t)

{

        printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);

}

void * child1(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_setspecific(key,(void *)tid);

        sleep(2);

        printf("thread %d returns %d\n",tid,pthread_getspecific(key));

        sleep(5);

}

void * child2(void *arg)

{

        int tid=pthread_self();

        printf("thread %d enter\n",tid);

        pthread_setspecific(key,(void *)tid);

        sleep(1);

        printf("thread %d returns %d\n",tid,pthread_getspecific(key));

        sleep(5);

}

int main(void)

{

        int tid1,tid2;

        printf("hello\n");

        pthread_key_create(&key,echomsg);

        pthread_create(&tid1,NULL,child1,NULL);

        pthread_create(&tid2,NULL,child2,NULL);

        sleep(10);

        pthread_key_delete(key);

        printf("main thread exit\n");

        return 0;

}

 

给例程创建两个线程分别设置同一个线程私有数据为自己的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果可以看出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid

7.   线程同步

7.1.  互斥锁(互斥信号量)

Pthread的互斥锁与vxworks的互斥信号量类似,都是用于互斥保护。

需要注意的是,linux有取消点的概念,即任务在运行时可以被取消。如果使用了互斥锁,可能会造成为解锁就被取消。为了解决这一问题,linux提供了可以在取消上加回调函数的功能:

pthread_cleanup_push()

pthread_cleanup_pop()

两个必须成对出现如果线程运行到两个函数之间被取消时,push注册的函数会被调用。

例程:

如果没有pushpop两行,则tid1被杀死后会导致tid2无法获取到互斥锁。

/* fireaxe的例程 */

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

 

pthread_key_t key;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond =   PTHREAD_COND_INITIALIZER;

 

void* Test01(void* ptr)

{

    pthread_cleanup_push(pthread_mutex_unlock, &mutex); /* push */

    while(1)

    {

        sleep(3);

        pthread_mutex_lock(&mutex);

        printf("1111mutex_lock\n");

        pthread_cond_wait(&cond, &mutex);

        printf("1111cond_wait\n");

        pthread_mutex_unlock(&mutex);

    }

    pthread_cleanup_pop(0); /* pop */

    return NULL;

}

 

void* Test02(void* ptr)

{

    while(1)

    {

        sleep(2);

        pthread_mutex_lock(&mutex);

        printf("2222mutex_lock\n");

        pthread_cond_wait(&cond, &mutex);

        printf("2222cond_wait\n");

        pthread_mutex_unlock(&mutex);

        sleep(2);

    }

   

    return NULL;

}

 

int main(void)

{

    int tid1, tid2;

    int ret;

 

    pthread_mutex_init(&mutex, NULL);

    pthread_mutex_init(&mutex, NULL);

    printf("Start:\n");

    ret = pthread_create(&tid1, NULL, Test01, NULL);

    ret = pthread_create(&tid2, NULL, Test02, NULL);

    printf("ret = 0x%x\n", ret);

 

    sleep(4);

    pthread_cancel(tid1);

   

    do

    {

        sleep(4);

        pthread_cond_signal(&cond);

    } while(1);

    printf("end.\n");

   

    return 0;

   

}

 

7.2.  条件变量(同步信号量)

Pthread的条件变量与vxworks中同步信号量类似,都是用于任务同步。区别是条件变量的操作不是原子操作,需要借助互斥锁保证其原子性。7.3.  信号灯(计数信号量)

Pthread的信号灯与vxworks中计数信号量信号量类似,都是用于表示资源是否可用。

 

 

线程条件变量pthread_cond_t

1.初始化条件变量pthread_cond_init

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
返回值:函数成功返回0;任何其他返回值都表示错误

初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。调用pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。这个函数返回时,条件变量被存放在参数cv指向的内存中。

可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

 

 

2.阻塞在条件变量上pthread_cond_wait

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
返回值:函数成功返回0;任何其他返回值都表示错误

函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。

被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。

pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。

阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:

pthread_mutex_lock();
while (condition_is_false)
 pthread_cond_wait();
pthread_mutex_unlock();

阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

 

 

3.解除在条件变量上的阻塞pthread_cond_signal

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误

函数被用来释放被阻塞在指定条件变量上的一个线程。

必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。

唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。

如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

 

 

4.阻塞直到指定时间pthread_cond_timedwait

#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函数成功返回0;任何其他返回值都表示错误

函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

注意:pthread_cond_timedwait函数也是退出点。

超时时间参数是指一天中的某个时刻。使用举例:

pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;

超时返回的错误码是ETIMEDOUT

 

 

5.释放阻塞的所有线程pthread_cond_broadcast

#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。

由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

 

 

6.释放条件变量pthread_cond_destroy

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误

释放条件变量。

注意:条件变量占用的空间并未被释放。

 

 

7.唤醒丢失问题

在线程未获得相应的互斥锁时调用pthread_cond_signalpthread_cond_broadcast函数可能会引起唤醒丢失问题。

唤醒丢失往往会在下面的情况下发生:

一个线程调用pthread_cond_signalpthread_cond_broadcast函数;

另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;

没有线程正在处在阻塞等待的状态下。

 

 

 

消息队列的常见函数

# include <mqueue.h>

mqd_t   mq_open(const char *name, int oflag);

mqd_t   mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

创建一个新的消息队列或打开一个已存在的消息队列

oflag参数是:

      O_RDONLY

             Open the queue to receive messages only.

 

      O_WRONLY

             Open the queue to send messages only.

 

      O_RDWR

Open the queue to both send and receive messages.

 

       Zero or more of the following flags can additionally be ORed inoflag:

 

      O_NONBLOCK

             Open the queue in non-blocking mode. Incircumstances where mq_receive(3) and mq_send(3)

             would normally block, these functions instead fail with the error EAGAIN.

 

      O_CREAT

             Create the message queue if it does not exist. The owner (user ID) ofthe message queue is set to the effective user ID of the callingprocess. The group ownership (group ID) is set to the effective group IDof the calling process.

 

 

 

       O_EXCL

 If O_CREAT was specified in oflag, and a queue with the given namealready exists, then fail with the error EEXIST.

 

int mq_close(mqd_t mqdes)

            返回:成功时为0,出错时为-1

 

int mq_unlink(const char * name);

            返回:成功时为0,出错时为-1

一个消息队列的名字在系统中的存在本身也占用其引用计数器的一个引用数,mq_unlink从系统中删除该名字意味着同时将其引用计数减1,若变为0则真正拆除该队列。

 

属性:

int mq_getattr(mqd_t mqdes, struct mq_attr * attr);

int mq_setattr(mqd_t mqdes, const struct mq_attr * attr, struct mq_attr * oattr);

                          都返回:成功0,失败-1

 

mq_attr的结构如下:

struct mq_attr{

      long       mq_flags;

      long       mq_maxmsg; //队列中的最大消息数

      long        mq_msgsize;   //任意给定消息的最大字节数

      long        mq_curmsgs;

};

 

发送与接收:

int mq_send(mqd_t mqdes, const char * ptr, size_t len, unsigned int prio);

                 返回:成功时为0,出错时为-1

ssize_t mq_receive(mqd_t mqdes, char * ptr, size_t len , unsigned int *priop);

                 返回:成功时为消息中的字节数,出错为-1

 

通知:

当有一个消息放到消息队列时,我们怎样得到他呢?调用mq_receive阻塞的话会阻止我们其间做其他事,如果设置非阻塞标志,我们必须轮训,这样消耗CPU时间。可以使用posix消息队列的异步通知方式。

       异步通知,已告知何时有一个消息放到了某个空的消息队列。这种统治有两种方式可供选择:产生一个新号;创建一个线程执行一个指定的函数。

int mq_notify(mqd_t mqdes, const struct sigevent * notification);

                 返回:成功时为0,出错时为-1

union sigval{          /* Data passed withnotification */

              int    sival_int;         /* Integer value */

              void   *sival_ptr;         /*Pointer value */

          };

 

 structsigevent {

              int          sigev_notify; /*Notification method */

              int         sigev_signo; /* Notification signal */

              union sigval   sigev_value; /* Data passed with

                                            notification */

              void         (*sigev_notify_function)(union sigval);

                                         /*Function for thread

                                            notification */

              void        *sigev_notify_attributes;

                                         /* Thread function attributes */

          };

通知的编程方法可能产生多种问题(详见unix网络编程第二卷)

#include <signal.h>

 int sigwait(const sigset_t * set, int * sig);

                                 返回:成功时为0,出错时为正的Exx值

 

 

 

IPC通信:Posix消息队列

 

消息队列可以认为是一个链表。进程(线程)可以往里写消息,也可以从里面取出消息。一个进程可以往某个消息队列里写消息,然后终止,另一个进程随时可以从消息队列里取走这些消息。这里也说明了,消息队列具有随内核的持续性,也就是系统不重启,消息队列永久存在。

创建(并打开)、关闭、删除一个消息队列

 1 #include <stdio.h> 

 2 #include <stdlib.h>

 3 #include <mqueue.h>   //头文件

 4 #include <sys/types.h> 

 5 #include <sys/stat.h> 

 6 #include <unistd.h> 

 7 #include <fcntl.h> 

 8 #include <errno.h>  

 9

10#define MQ_NAME ("/tmp") 

11#define MQ_FLAG (O_RDWR | O_CREAT | O_EXCL) // 创建MQflag 

12#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH) // 设定创建MQ的权限 

13

14int main() 

15

16

17    mqd_t posixmq; 

18    int rc = 0

19

20    /* 

21    函数说明:函数创建或打开一个消息队列 

22    返回值:成功返回消息队列描述符,失败返回-1,错误原因存于errno 

23    */

24    posixmq =mq_open(MQ_NAME, MQ_FLAG, FILE_MODE, NULL); 

25

26    if(-1 == posixmq) 

27    { 

28        perror("创建MQ失败"); 

29        exit(1); 

30    } 

31

32    /* 

33    函数说明:关闭一个打开的消息队列,表示本进程不再对该消息队列读写 

34    返回值:成功返回0,失败返回-1,错误原因存于errno 

35    */

36    rc =mq_close(posixmq); 

37    if(0 != rc) 

38    { 

39        perror("关闭失败"); 

40        exit(1); 

41    } 

42

43    /* 

44    函数说明:删除一个消息队列,好比删除一个文件,其他进程再也无法访问 

45    返回值:成功返回0,失败返回-1,错误原因存于errno 

46    */

47    rc =mq_unlink(MQ_NAME); 

48    if(0 != rc) 

49    { 

50        perror("删除失败"); 

51        exit(1); 

52    } 

53

54    return0

55 }

编译并执行:

 1 root@linux:/mnt/hgfs/C_libary# gcc -o crtmq crtmq.c

 2 /tmp/ccZ9cTxo.o: In function `main':

 3 crtmq.c:(.text+0x31): undefined reference to `mq_open'

 4 crtmq.c:(.text+0x60): undefined reference to `mq_close'

 5 crtmq.c:(.text+0x8f): undefined reference to `mq_unlink'

 6 collect2: ld returned 1 exit status

 7因为mq_XXX()函数不是标准库函数,链接时需要指定;库-lrt;

 8 root@linux:/mnt/hgfs/C_libary# gcc -o crtmq crtmq.c -lrt

 9

10 root@linux:/mnt/hgfs/C_libary# ./crtmq

11最后程序并没有删除消息队列(消息队列有随内核持续性),如再次执行该程序则会给出错误信息:

12 root@linux:/mnt/hgfs/C_libary# ./crtmq

13创建MQ失败: File exit(0)

编译这个程序需要注意几点:

1、消息队列的名字最好使用“/”打头,并且只有一个“/”的名字。否则可能出现移植性问题;(还需保证在根目录有写权限,为了方便我在root权限下测试)
2
、创建成功的消息队列不一定能看到,使用一些方法也可以看到,本文不做介绍;

消息队列的名字有如此规定,引用《UNIX网络编程2》的相关描述:mq_open,sem_open,shm_open这三个函数的第一个参数是
一个IPC名字,它可能是某个文件系统中的一个真正存在的路径名,也可能不是。Posix.1是这样描述Posix IPC名字的。 
1)
它必须符合已有的路径名规则(最多由PATH_MAX个字节构成,包括结尾的空字节) 
2)
如果它以斜杠开头,那么对这些函数的不同调用将访问同一个队列,否则效果取决于实现(也就是效果没有标准化) 
3)
名字中的额外的斜杠符的解释由实现定义(同样是没有标准化)因此,为便于移植起见,Posix IPC名字必须以一个斜杠打头,并且不能再包含任何其他斜杠符。

 

 

 

 

 

 

 

IPC通信:Posix消息队列读,

 

创建消息队列的程序:

 1 #include <stdio.h> 

 2 #include <stdlib.h>

 3 #include <mqueue.h>   //头文件

 4 #include <sys/types.h> 

 5 #include <sys/stat.h> 

 6 #include <unistd.h> 

 7 #include <fcntl.h> 

 8 #include <errno.h>  

 9

10#define MQ_NAME ("/tmp") 

11#define MQ_FLAG (O_RDWR | O_CREAT | O_EXCL) // 创建MQflag 

12#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH) // 设定创建MQ的权限 

13

14int main() 

15

16

17    mqd_t posixmq; 

18    int rc = 0

19

20    /* 

21    函数说明:函数创建或打开一个消息队列 

22    返回值:成功返回消息队列描述符,失败返回-1,错误原因存于errno 

23    */

24    posixmq =mq_open(MQ_NAME, MQ_FLAG, FILE_MODE, NULL); 

25

26    if(-1 == posixmq) 

27    { 

28        perror("创建MQ失败"); 

29        exit(1); 

30    } 

31

32    /* 

33    函数说明:关闭一个打开的消息队列,表示本进程不再对该消息队列读写 

34    返回值:成功返回0,失败返回-1,错误原因存于errno 

35    */

36    rc =mq_close(posixmq); 

37    if(0 != rc) 

38    { 

39        perror("关闭失败"); 

40        exit(1); 

41    } 

42

43#if 0

44    /* 

45    函数说明:删除一个消息队列,好比删除一个文件,其他进程再也无法访问 

46    返回值:成功返回0,失败返回-1,错误原因存于errno 

47    */

48    rc =mq_unlink(MQ_NAME); 

49    if(0 != rc) 

50    { 

51        perror("删除失败"); 

52        exit(1); 

53    } 

54

55    return0;

56#endif 

57 }

编译并执行:

1 root@linux:/mnt/hgfs/C_libary# gcc -ocrtmq crtmq.c -lrt

2 root@linux:/mnt/hgfs/C_libary# ./crtmq

3程序并没有删除消息队列(消息队列有随内核持续性),如再次执行该程序则会给出错误信息:

4 root@linux:/mnt/hgfs/C_libary# ./crtmq

5创建MQ失败: File exit(0)

向消息队列写消息的程序:

消息队列的读写主要使用下面两个函数:

/*头文件*/

#include<mqueue.h> 

 

/*返回:若成功则为消息中字节数,若出错则为-1 */

int mq_send(mqd_t mqdes, constchar *msg_ptr, size_tmsg_len, unsigned msg_prio);

 

/*返回:若成功则为0若出错则为-1*/

ssize_tmq_receive(mqd_t mqdes, char *msg_ptr,size_t msg_len, unsigned *msg_prio); 

 

/*消息队列属性结构体*/

struct mq_attr {

   long mq_flags;       /* Flags: 0 or O_NONBLOCK */

   long mq_maxmsg;      /* Max. # of messages on queue */

   long mq_msgsize;     /* Max. message size (bytes) */

   long mq_curmsgs;     /* # of messages currently in queue */

};

 1 #include <stdio.h> 

 2 #include <stdlib.h> 

 3 #include <mqueue.h> 

 4 #include <sys/types.h> 

 5 #include <sys/stat.h> 

 6 #include <unistd.h> 

 7 #include <fcntl.h> 

 8 #include <errno.h> 

 9   

10/*向消息队列发送消息,消息队列名及发送的信息通过参数传递*/

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

12

13    mqd_t mqd; 

14    char *ptr; 

15    size_t len; 

16    unsigned int prio; 

17    int rc; 

18

19    if(argc != 4

20    { 

21        printf("Usage:sendmq <name> <bytes> <priority>\n"); 

22        exit(1); 

23    } 

24

25    len = atoi(argv[2]); 

26    prio = atoi(argv[3]);   

27

28    //只写模式找开消息队列 

29    mqd = mq_open(argv[1], O_WRONLY); 

30    if(-1 == mqd) 

31    { 

32        perror("打开消息队列失败"); 

33        exit(1); 

34    } 

35

36    // 动态申请一块内存 

37    ptr = (char *) calloc(len, sizeof(char)); 

38    if(NULL == ptr) 

39    { 

40        perror("申请内存失败"); 

41        mq_close(mqd); 

42        exit(1); 

43    } 

44   

45    /*向消息队列写入消息,如消息队列满则阻塞,直到消息队列有空闲时再写入*/

46    rc = mq_send(mqd, ptr,len, prio); 

47    if(rc < 0

48    { 

49        perror("写入消息队列失败"); 

50        mq_close(mqd); 

51        exit(1); 

52    }    

53

54    // 释放内存 

55    free(ptr); 

56    return0

57 }

编译并执行:

1 root@linux:/mnt/hgfs/C_libary# gcc -osendmq sendmq.c -lrt

2 root@linux:/mnt/hgfs/C_libary# ./sendmq/tmp 3015

3 root@linux:/mnt/hgfs/C_libary# ./sendmq/tmp 3016

4 root@linux:/mnt/hgfs/C_libary# ./sendmq/tmp 3017

5 root@linux:/mnt/hgfs/C_libary# ./sendmq/tmp 3018

  上面先后向消息队列“/tmp”写入了四条消息,因为先前创建的消息队列只允许存放3条消息,本次第四次写入时程序会阻塞。直到有另外进程从消息队列取走消息后本次写入才成功返回。

读消息队列:

#include<stdio.h> 

#include<stdlib.h> 

#include<mqueue.h> 

#include<sys/types.h> 

#include<sys/stat.h> 

#include<unistd.h> 

#include<fcntl.h> 

#include<errno.h> 

 

/*读取某消息队列,消息队列名通过参数传递*/

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

    mqd_t mqd; 

    struct mq_attr attr; 

    char *ptr; 

    unsigned int prio; 

    size_t n; 

    int rc; 

 

    if(argc != 2

    { 

        printf("Usage: readmq <name>\n"); 

        exit(1); 

    } 

 

    /*只读模式打开消息队列*/

    mqd = mq_open(argv[1], O_RDONLY); 

    if(mqd < 0

    { 

        perror("打开消息队列失败"); 

        exit(1); 

    }    

 

    // 取得消息队列属性,根据mq_msgsize动态申请内存 

    rc = mq_getattr(mqd, &attr); 

    if(rc < 0

    { 

        perror("取得消息队列属性失败"); 

        exit(1); 

    } 

 

    /*动态申请保证能存放单条消息的内存*/

    ptr = calloc(attr.mq_msgsize, sizeof(char)); 

    if(NULL == ptr) 

    { 

        printf("动态申请内存失败\n"); 

        mq_close(mqd); 

        exit(1); 

    }    

 

    /*接收一条消息*/

    n = mq_receive(mqd, ptr, attr.mq_msgsize, &prio); 

    if(n < 0

    { 

        perror("读取失败"); 

        mq_close(mqd); 

        free(ptr); 

        exit(1); 

    } 

   

    printf("读取 %ld 字节\n 优先级为 %u\n", (long)n, prio);    

    return0

}

编译并执行:

 1 root@linux:/mnt/hgfs/C_libary# vi readmq.c

 2 root@linux:/mnt/hgfs/C_libary# vi readmq.c

 3 root@linux:/mnt/hgfs/C_libary# gcc -o readmq readmq.c -lrt

 4 root@linux:/mnt/hgfs/C_libary# ./readmq /tmp

 5读取30 字节

 6   优先级为18

 7 root@linux:/mnt/hgfs/C_libary# ./readmq /tmp

 8读取30 字节

 9   优先级为17

10 root@linux:/mnt/hgfs/C_libary# ./readmq/tmp

11读取30 字节

12  优先级为16

13 root@linux:/mnt/hgfs/C_libary# ./readmq/tmp

14读取30 字节

15    优先级为15

16 root@linux:/mnt/hgfs/C_libary# ./readmq/tmp

  程序执行五次,第一次执行完,先前阻塞在写处的程序成功返回。第五次执行,因为消息队列已经为空,程序阻塞。直到另外的进程向消息队列写入一条消息。另外,还可以看出Posix消息队列每次读出的都是消息队列中优先级最高的消息。

 

 

IPC通信:Posix消息队列的属性设置

 

Posix消息队列的属性使用如下结构存放:

struct mq_attr 

    long mq_flags; /*阻塞标志位,0为非阻塞(O_NONBLOCK)*/

    long mq_maxmsg; /*队列所允许的最大消息条数*/

    long mq_msgsize; /*每条消息的最大字节数*/

    long mq_curmsgs; /*队列当前的消息条数*/

};

队列可以在创建时由mq_open()函数的第四个参数指定mq_maxmsg,mq_msgsize。 如创建时没有指定则使用默认值,一旦创建,则不可再改变。

队列可以在创建后由mq_setattr()函数设置mq_flags

 

#include<mqueue.h> 

 

/*取得消息队列属性,放到mqstatfh*/

int mq_getattr(mqd_t mqdes, struct mq_attr *mqstat); 

 

/*设置消息队列属性,设置值由mqstat提供,原先值写入omqstat*/

int mq_setattr(mqd_t mqdes, conststruct mq_attr *mqstat, struct mq_attr *omqstat);  

 

均返回:若成功则为0,若出错为-1

程序获取和设置消息队列的默认属性:

 1 #include <stdio.h> 

 2 #include <stdlib.h> 

 3 #include <mqueue.h> 

 4 #include <sys/types.h> 

 5 #include <sys/stat.h> 

 6 #include <unistd.h> 

 7 #include <fcntl.h> 

 8 #include <errno.h> 

 9   

10#define MQ_NAME ("/tmp") 

11#define MQ_FLAG (O_RDWR | O_CREAT | O_EXCL) // 创建MQflag 

12#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH) // 设定创建MQ的权限 

13   

14int main() 

15

16    mqd_t posixmq; 

17    int rc = 0

18   

19    struct mq_attr mqattr; 

20   

21    // 创建默认属性的消息队列 

22    posixmq =mq_open(MQ_NAME, MQ_FLAG, FILE_MODE, NULL); 

23    if(-1 == posixmq) 

24    { 

25        perror("创建MQ失败"); 

26        exit(1); 

27    } 

28       

29    // 获取消息队列的默认属性 

30    rc = mq_getattr(posixmq, &mqattr); 

31    if(-1 == rc) 

32    { 

33        perror("获取消息队列属性失败"); 

34        exit(1); 

35    } 

36

37    printf("队列阻塞标志位:%ld\n", mqattr.mq_flags); 

38    printf("队列允许最大消息数:%ld\n", mqattr.mq_maxmsg); 

39    printf("队列消息最大字节数:%ld\n", mqattr.mq_msgsize); 

40    printf("队列当前消息条数:%ld\n", mqattr.mq_curmsgs); 

41   

42    rc =mq_close(posixmq); 

43    if(0 != rc) 

44    { 

45        perror("关闭失败"); 

46        exit(1); 

47    } 

48   

49    rc =mq_unlink(MQ_NAME); 

50    if(0 != rc) 

51    { 

52        perror("删除失败"); 

53        exit(1); 

54    }    

55    return0

56 }

编译并执行:

1 root@linux:/mnt/hgfs/C_libary# gcc -oattrmq attrmq.c -lrt

2 root@linux:/mnt/hgfs/C_libary# ./attrmq

3队列阻塞标志位:0

4队列允许最大消息数:10

5队列消息最大字节数:8192

6队列当前消息条数:0

7 root@linux:/mnt/hgfs/C_libary#

设置消息队列的属性:

 1 #include <stdio.h> 

 2 #include <stdlib.h> 

 3 #include <mqueue.h> 

 4 #include <sys/types.h> 

 5 #include <sys/stat.h> 

 6 #include <unistd.h> 

 7 #include <fcntl.h> 

 8 #include <errno.h> 

 9   

10#define MQ_NAME ("/tmp") 

11#define MQ_FLAG (O_RDWR | O_CREAT | O_EXCL) // 创建MQflag 

12#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH) // 设定创建MQ的权限 

13   

14int main() 

15

16    mqd_t posixmq; 

17    int rc = 0

18   

19    struct mq_attr mqattr; 

20  

21    // 创建默认属性的消息队列 

22    mqattr.mq_maxmsg = 5; // 注意不能超过系统最大限制 

23    mqattr.mq_msgsize = 8192

24    //posixmq =mq_open(MQ_NAME, MQ_FLAG, FILE_MODE, NULL); 

25    posixmq = mq_open(MQ_NAME, MQ_FLAG, FILE_MODE, &mqattr); 

26

27    if(-1 == posixmq) 

28    { 

29        perror("创建MQ失败"); 

30        exit(1); 

31    } 

32    

33    mqattr.mq_flags = 0

34    mq_setattr(posixmq, &mqattr, NULL);// mq_setattr()只关注mq_flagsadw 

35       

36    // 获取消息队列的属性 

37    rc = mq_getattr(posixmq, &mqattr); 

38    if(-1 == rc) 

39    { 

40        perror("获取消息队列属性失败"); 

41        exit(1); 

42    } 

43

44    printf("队列阻塞标志位:%ld\n", mqattr.mq_flags); 

45    printf("队列允许最大消息数:%ld\n", mqattr.mq_maxmsg); 

46    printf("队列消息最大字节数:%ld\n", mqattr.mq_msgsize); 

47    printf("队列当前消息条数:%ld\n", mqattr.mq_curmsgs); 

48   

49    rc =mq_close(posixmq); 

50    if(0 != rc) 

51    { 

52        perror("关闭失败"); 

53        exit(1); 

54    }   

55

56    rc =mq_unlink(MQ_NAME); 

57    if(0 != rc) 

58    { 

59        perror("删除失败"); 

60        exit(1); 

61    }

62         

63    return0

64 }

编译运行:

1 root@linux:/mnt/hgfs/C_libary# gcc -osetattrmq setattrmq.c -lrt

2 root@linux:/mnt/hgfs/C_libary# ./setattrmq

3队列阻塞标志位:0

4队列允许最大消息数:5

5队列消息最大字节数:8192

6队列当前消息条数:0

 

 

 

信号量和互斥锁的实现

 

“ 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在 那里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是 流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算 或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

两者之间的区别:

作用域 
信号量 : 进程间或线程间(linux仅线程间)
互斥锁 : 线程间

上锁时 
信号量 : 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一。一句话,信号量的value>=0 。
互斥锁 : 只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的vlaue可以为负数 。

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

接下来,我们需要分析一下信号量和线程互斥锁的实现机制。

在Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。

futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2.5.7版;其语义在2.5.40固定下来,然后在2.6.x系列稳定版内核中出现。

Futex 是fast userspace mutex的缩写,意思是快速用户空间互斥体。Linux内核把它们作为快速的用户空间的锁和信号量的预制构件提供给开发者。Futex非常基础,借助其 自身的优异性能,构建更高级别的锁的抽象,如POSIX互斥体。大多数程序员并不需要直接使用Futex,它一般用来实现像NPTL这样的系统库。

Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。 

----------------------------------------------------------------
插播一段关于x86原子操作指令的说明:

cmpxchg 比较交换指令,其语义为:

int CompareAndExchange(int *ptr, int old, int new)

{

    int actual = *ptr;

    if (actual == old)

      *ptr = new;

    return actual;

}


Intel白皮书上的说明如下:
(* Accumulator = AL, AX, EAX, or RAX depending on whether a byte, word,doubleword, or
quadword comparison is being performed *)
IF accumulator = DEST
THEN
ZF ← 1;
DEST ← SRC;
ELSE
ZF ← 0;
accumulator ← DEST;
FI;

使用此原子操作可以实现自旋锁,之前有一篇文章中描述了实现:

void lock(lock_t *lock) {

    while (CompareAndExchange(&lock->flag, 0, 1) == 1)

      ; // spin

}

void unlock(lock_t *lock) {

    lock->flag = 0;

}

 

关于smp下的原子操作的一些说明:
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为 是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源 互斥的原因。在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
在x86 平台上,CPU提供了在指令执行期间对总线加锁 的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK" ,经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
当然,并不是所有的指令前面都可以加lock前缀的,只有ADD, ADC, AND,BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, 和 XCHG指令前面可以加lock指令,实现原子操作。
----------------------------------------------------------------


futex保存在用户空间的共享内存中,并且通过原子操作进行操作。在大部分情况下,资源不存在争用的情况下,进程或者线程可以立刻获得资源成功,实际上就没有必要调用系统调用,陷入内核了。实际上,futex的作用就在于减少系统调用的次数,来提高系统的性能。

线程互斥锁pthread_mutex_t的实现原理:

pthread_mutex_lock:
atomic_dec(pthread_mutex_t.value);
if(pthread_mutex_t.value!=0)
futex(WAIT)
else
success

pthread_mutex_unlock:
atomic_inc(pthread_mutex_t.value);
if(pthread_mutex_t.value!=1) 
   futex(WAKEUP)
 
else
success

信号量sem_t的实现原理(直接从glibc/nptl/DESIGN-sem.txt中摘的):

sem_wait(sem_t *sem)
{
for (;;) {

if (atomic_decrement_if_positive(sem->count))
break;

futex_wait(&sem->count, 0)
}
}

sem_post(sem_t *sem)
{
n = atomic_increment(sem->count);
// Pass the new value of sem->count
  futex_wake(&sem->count, n + 1); 
}


对 比,pthread_mutex_unlock()和sem_post()的实现,我们发现一个不同点,sem_post()无论如何都会调用 futex_wake(),进行系统调用。但是pthread_mutex_unlock()却符合futex的初衷,只有在需要仲裁的时候才调用 futex_wake()。那么什么是仲裁条件呢?

前面说过信号量和线程互斥锁语义上的区别在于信号量的value>=0,而线程互斥锁的value可以为负数。
对于lock操作,这两个倒是没有多少差别。信号量只要value>0就可以获得资源,线程互斥锁需要value=1。
但 是对于unlock操作,这两个就有一些差别了。信号量和线程互斥锁,都会增加对应的value。如果加1后,value为1,对于线程互斥锁来讲,实际 上表明资源可用,并且之前没有其他的线程在等待这个资源;否则说明还有其他线程在等待这个资源,需要调用futex系统调用唤醒它们。但是对于信号量,由于value必须>=0。那么加1后,即使value1,也无法判定现在没有其他的进程或线程正在等待资源,所以必须调用futex系统调用。 例如:

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>

sem_t sem_a;
void *task1();

int main(void)
{
int ret=0;
pthread_t thrd1;
pthread_t thrd2;
sem_init(&sem_a,0,1);
ret=pthread_create(&thrd1,NULL,task1,NULL); //创建子线程
ret=pthread_create(&thrd2,NULL,task1,NULL); //创建子线程
pthread_join(thrd1,NULL); //等待子线程结束
pthread_join(thrd2,NULL); //等待子线程结束
}

void *task1()
{
int sval = 0;
sem_wait(&sem_a); //持有信号量
sleep(5); //do_nothing
sem_getvalue(&sem_a,&sval);
printf("sem value = %d/n",sval);
sem_post(&sem_a); //释放信号量
}

上面sem的value初始化为1,但是有两个线程争用资源。那么第一个线程获得资源成功,当它unlock的时候,sem的value变为1。但是,这个时候,实际上还有一个线程在等待资源。因此,必须要进行futex_wake()系统调用,唤醒等待资源的线程。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值