`
shinepaopao
  • 浏览: 142856 次
社区版块
存档分类
最新评论

Linux操作系统中多线程的同步

阅读更多

1 互斥锁

  互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。

  先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

  void reader_function ( void );

  void writer_function ( void );

  char buffer;

  int buffer_has_item=0;

  pthread_mutex_t mutex;

  struct timespec delay;

  void main ( void ){

  pthread_t reader;

  /* 定义延迟时间*/

  delay.tv_sec = 2;

  delay.tv_nec = 0;

  /* 用默认属性初始化一个互斥锁对象*/

  pthread_mutex_init (&mutex,NULL);

  pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);

  writer_function( );

  }

  void writer_function (void){

  while(1){

  /* 锁定互斥锁*/

  pthread_mutex_lock (&mutex);

  if (buffer_has_item==0){

  buffer=make_new_item( );

  buffer_has_item=1;

  }

  /* 打开互斥锁*/

  pthread_mutex_unlock(&mutex);

  pthread_delay_np(&delay);

  }

  }

  void reader_function(void){

  while(1){

  pthread_mutex_lock(&mutex);

  if(buffer_has_item==1){

  consume_item(buffer);

  buffer_has_item=0;

  }

  pthread_mutex_unlock(&mutex);

  pthread_delay_np(&delay);

  }

  }

  这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上锁、解锁机制,一般情况下,选用最后一个默认属性。

  pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。

  在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

  2 条件变量

  前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

  条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:

  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));

  其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。

  函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:

  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,

  pthread_mutex_t *__mutex));

  线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。

  另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:

  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,

  pthread_mutex_t *__mutex, __const struct timespec *__abstime));

  它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。

  函数pthread_cond_signal()的原型为:

  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));

  它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号有可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

  pthread_mutex_t count_lock;

  pthread_cond_t count_nonzero;

  unsigned count;

  decrement_count () {

  pthread_mutex_lock (&count_lock);

  while(count==0)

  pthread_cond_wait( &count_nonzero, &count_lock);

  count=count -1;

  pthread_mutex_unlock (&count_lock);

  }

  increment_count(){

  pthread_mutex_lock(&count_lock);

  if(count==0)

  pthread_cond_signal(&count_nonzero);

  count=count+1;

  pthread_mutex_unlock(&count_lock);

  }

  count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。

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

  3 信号量

  信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。

  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:

  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));

  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。

  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。

  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。

  函数sem_destroy(sem_t *sem)用来释放信号量sem。

  下面来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。

  /* File sem.c */

  #include

  #include

  #include

  #define MAXSTACK 100

  int stack[MAXSTACK][2];

  int size=0;

  sem_t sem;

  /* 从文件1.dat读取数据,每读一次,信号量加一*/

  void ReadData1(void){

  FILE *fp=fopen("1.dat","r");

  while(!feof(fp)){

  fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);

  sem_post(&sem);

  ++size;

  }

  fclose(fp);

  }

  /*从文件2.dat读取数据*/

  void ReadData2(void){

  FILE *fp=fopen("2.dat","r");

  while(!feof(fp)){

  fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);

  sem_post(&sem);

  ++size;

  }

  fclose(fp);

  }

  /*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/

  void HandleData1(void){

  while(1){

  sem_wait(&sem);

  printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],

  stack[size][0]+stack[size][1]);

2
4
分享到:
评论

相关推荐

    Linux系统编程之线程同步

    所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。 因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成...

    linux 操作系统 多线程编程 经典同步算法--读者写者问题

    因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。

    Linux操作系统编程(linux多线程的互斥与同步控制及实践)

    Linux操作系统编程,主要提供高级编程技术及实践方面内容。主要侧重多线程互斥与同步控制程序设计、调试等。部分代码练习。

    Linux多线程同步方式

    而操作系统对于多线程不会自动帮我们串行化,所以需要我们通过操作系统提供的同步方式api,结合自己的业务逻辑,利用多线程提高性能的同时,保证业务逻辑的正确性。一般而言,linux下同步方式主要有4种,原子锁,...

    Linux多线程的互斥与同步控制及实践.pdf

    Linux多线程的互斥与同步控制及实践.pdf

    操作系统实验三 -线程同步

    在linux环境下,利用多线程及同步的方法,编写一个程序模拟火车售票系统,共3个窗口,卖10张票,程序输出结果类似(程序输出不唯一,可以是其他类似的结果)

    5操作系统实验报告.doc

    报 告 课程名称: 线程控制实验 专业班级: 学 号: 姓 名: 报告日期: 2013年12月03日 计算机科学与技术学院 线程控制实验 一、目的和要求 通过本实验掌握在 Linux操作系统中遵循 Posix线程标准接口进行多线程程序...

    清华大学Linux操作系统原理与应用

    1.3 开放源代码的Unix/Linux操作系统 8 1.3.1 Unix的诞生和发展 8 1.3.2 Linux的诞生 9 1.3.3 操作系统标准POSIX 9 1.3.4 GNU和Linux 9 1.3.5 Linux的开发模式 10 1.4 Linux内核 10 1.4.1 Linux内核的位置 10 1.4.2 ...

    linux操作系统内核技术-uestc课件

     8介绍系统时钟和硬件定时器,单处理器和多处理器上的linux计时体系结构,定时的时间插补原理,单处理器和多处理器上的时钟中断处理,动态定时器的数据结构和算法原理,定时器竞争情形,延迟函数。Time,...

    操作系统课设用多进程同步方法演示“生产者-消费者”问题

    1、设计目的:通过研究Linux的进程同步机制和信号量,实现生产者消费者问题的并发控制。 2、说明:有界缓冲区内设有20个存储单元,放入取出的产品设定为20个100以内的随机整数。 3、设计要求: 1) 生产者与消费者均...

    LInux 平台下物联网网关(多线程实现),嵌入式项目

    Linux物联网网关是基于Linux操作系统的嵌入式网关Server,通过多线程方式实现各种功能。它广泛应用于物联网嵌入式项目,包括参数数据解析、协议转换、Socket收发、Sqlite、Uart、Camera等操作,并提供友好的UI界面。...

    操作系统进程调度与同步实验

    了解操作系统中常见的进程调度算法 了解在linux中利用多线程模拟实现FCFS,SJF,RR的调度过程。 了解进程同步的特点,掌握利用信号量实现进程间同步的的方法。 了解哲学家问题中进程之间的相互制约关系,能够合理的...

    详解Linux多线程使用信号量同步

    如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。 而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行...

    7 实验七 实现多线程聊天程序.docx

    1. 学习第18章“多线程服务器端的实现”,掌握线程创建、线程同步的原理和实现方法。 2. 在Linux操作系统上编写基于多线程的聊天室程序。

    《操作系统原理与设计》全本

    1.3.7 Linux操作系统 12 1.3.8 MACH操作系统 12 1.4 操作系统的分类 13 1.4.1 批处理操作系统 13 1.4.2 分时操作系统 13 1.4.3 实时操作系统 14 1.4.4 网络操作系统 15 1.4.5 分布式操作系统 16 1.4.6 嵌入式操作...

    操作系统之Linux下的生产者-消费者模型

    结合System V信号量机制,利用Linux下的多线程库实现了Linux下的操作系统生产者-消费者模型,具体原理可参考博文:: http://blog.csdn.net/Mikeoperfect/article/details/79431642

    Linux多线程服务端编程:使用muduo C++网络库

    《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...

    20万字必备java面试八股文宝典-多线程.数据库.Spring.SpringBoot.Linux.分布式.设计模式.面试指导

    这份宝典囊括了Java编程中的基础知识、多线程编程、数据库操作、Spring框架、Spring Boot、Spring Cloud、Linux操作系统、分布式系统、设计模式、面试指导以及自我介绍等内容。 基础部分,包括Java编程语言的核心...

    【嵌入式软件工程师面经】Linux系统编程(线程进程).pdf

    在多任务操作系统中,多个进程可以同时运行,操作系统的调度器负责管理这些进程的执行,给它们分配CPU时间。进程还可以进行创建(通常称为“fork”)新的进程。 线程(Thread) 线程有时被称为轻量级进程,它是...

    Linux操作系统的编程环境详细介绍

    在Linux系统的程序设计中,有一些重要特性要特别注意。Linux是多任务的,可以同时创建多个进程或者线程。绝不能认为自己的程序是系统唯一运实例;同一个用户或者不同的用户...本文为大家介绍Linux操作系统的编程环境。

Global site tag (gtag.js) - Google Analytics