linux线程同步之互斥

在windows中,为了让多个线程达到同步的目的,在对于全局变量等大家都要用的资源的使用上,通常得保证同时只能由一个线程在用,一个线程没有宣布对它的释放之前,不能够给其他线程使用这个变量。在windows里,我们可以用时EnterCriticalSection()和 LeaveCriticalSection()函数.那么在linux里,有什么类似的机制呢?

 

这里介绍互斥锁。

1.申请一个互斥锁

pthread_mutex_t mutex; //申请一个互斥锁

你可以声明多个互斥量。

在声明该变量后,你需要调用pthread_mutex_init()来创建该变量。pthread_mutex_init的格式如下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

第一个参数,mutext,也就是你之前声明的那个互斥量,第二个参数为该互斥量的属性。属性定义如下:

互斥量分为下面三种:

l         快速型(PTHREAD_MUTEX_FAST_NP)。这种类型也是默认的类型。该线程的行为正如上面所说的。

l         递归型(PTHREAD_MUTEX_RECURSIVE_NP)。如果遇到我们上面所提到的死锁情况,同一线程循环给互斥量上锁,那么系统将会知道该上锁行为来自同一线程,那么就会同意线程给该互斥量上锁。

l         错误检测型(PTHREAD_MUTEX_ERRORCHECK_NP)。如果该互斥量已经被上锁,那么后续的上锁将会失败而不会阻塞,pthread_mutex_lock()操作将会返回EDEADLK。

 

可以通过函数

注意以下语句可以做到将一个互斥锁快速初始化为快速型。

pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;

 

2.销毁一个互斥锁

pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:

 int pthread_mutex_destroy(pthread_mutex_t *mutex)

销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

 

3.上锁(相当于windows下的EnterCriticalSection)

在创建该互斥量之后,你便可以使用它了。要得到互斥量,你需要调用下面的函数:

int pthread_mutex_lock(pthread_mutex_t *mutex);

该函数用来给互斥量上锁。互斥量一旦被上锁后,其他线程如果想给该互斥量上锁,那么就会阻塞在这个操作上。如果在此之前该互斥量已经被其他线程上锁,那么该操作将会一直阻塞在这个地方,直到获得该锁为止。

在得到互斥量后,你就可以进入关键代码区了。

 

4.解锁(相当于windows下的LeaveCriticalSection)

在操作完成后,你必须调用下面的函数来给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

5..pthread_mutex_trylock

如果我们不想一直阻塞在这个地方,那么可以调用下面函数:
int pthread_mutex_trylock(pthread_mutex_t *mutex)
如果此时互斥量没有被上锁,那么pthread_mutex_trylock()将会返回0,并会对该互斥量上锁。如果互斥量已经被上锁,那么会立刻返回EBUSY。

注:

下面介绍一个实例说明上述函数的用法

这是一个简单的读写程序,在这个程序中,一个线程从共享的缓冲区中读数据,另一个线程向共享的缓冲区中写数据。对共享的缓冲区的访问控制是通过使用一个互斥锁来是实现的。

#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>#define FALSE 0
#define TRUE 1void readfun();
void writefun();char buffer[256];
int buffer_has_item=0;
int retflag=FALSE,i=0;
pthread_mutex_t mutex;int main()
{void *retval;pthread_t reader;pthread_mutex_init(&mutex,NULL);pthread_create(&reader,NULL,(void *)&readfun,NULL);writefun();pthread_join(reader,&retval);}void readfun()
{while(1){if(retflag)return;pthread_mutex_lock(&mutex);if(buffer_has_item==1){printf("%s",buffer);buffer_has_item=0;}pthread_mutex_unlock(&mutex);}
}
void writefun()
{int i=0;while(1){if(i==10){retflag=TRUE;return;}pthread_mutex_lock(&mutex);if(buffer_has_item==0){sprintf(buffer,"This is %d\n",i++);buffer_has_item=1;}pthread_mutex_unlock(&mutex);}
}

线程同步之条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

使用条件变量之前要先进行初始化。可以在单个语句中生成和初始化一个条件变量如:pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;(用于进程间线程的通信)。可以利用函数pthread_cond_init动态初始化。

条件变量分为两部分: 条件和变量. 条件本身是由互斥量保护的. 线程在改变条件状态前先要锁住互斥量. 它利用线程间共享的全局变量进行同步的一种机制。

相关的函数如下:

1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);  
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

简要说明:

     (1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL
      (2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真
      timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
      (3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
      (4)清除条件变量:destroy;无线程等待,否则返回EBUSY
 

详细说明

1. 初始化:

    条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:

  • 静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.
  • 动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.

    当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论.

2. 等待条件:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

成功则返回0, 出错则返回错误编号.

    这两个函数分别是阻塞等待和超时等待.

    等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.

    当pthread_cond_wait返回时, 互斥量再次被锁住.

3. 通知条件:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.

    这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.

下面给出一个典例,这个例子是一个典型的生产者/消费者的例子。

#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 4
#define OVER (-1)
struct producers   //定义生产者条件变量的结构
{int buffer[BUFFER_SIZE];        //定义缓冲区pthread_mutex_t lock;           //定义访问缓冲区的互斥锁int        readpos, writepos;  //读/写的位置pthread_cond_t  notempty;       //缓冲区中有数据时的标记pthread_cond_t notfull;        //缓冲区未满的标记
};
//初始化缓冲区
void init(struct producers *b)
{pthread_mutex_init(&b->lock,NULL);pthread_cond_init(&b->notempty,NULL);pthread_cond_init(&b->notfull,NULL);b->readpos=0;b->writepos=0;
}
//在缓冲区中存放一个整数
void put(struct producers *b, int data)
{pthread_mutex_lock(&b->lock);
//当缓冲区为满时等待while((b->writepos+1)%BUFFER_SIZE==b->readpos){pthread_cond_wait(&b->notfull,&b->lock);
//在返回之前,pthread_cond_wait需要参数b->lock}
//向缓冲区中写数据,并将写指针向前移动b->buffer[b->writepos]=data;b->writepos++;if(b->writepos>=BUFFER_SIZE) b->writepos=0;
//发送当前缓冲区中有数据的信号pthread_cond_signal(&b->notempty);pthread_mutex_unlock(&b->lock);
}
//从缓冲区中读数据并将数据从缓冲区中移走
int get(struct producers *b)
{int data;pthread_mutex_lock(&b->lock);
//当缓冲区中无数据时等待while(b->writepos==b->readpos){pthread_cond_wait(&b->notempty,&b->lock);}
//从缓冲区中读数据,并将指针前移data=b->buffer[b->readpos];b->readpos++;if(b->readpos>=BUFFER_SIZE) b->readpos=0;
//发送当前缓冲区未满的信号pthread_cond_signal(&b->notfull);pthread_mutex_unlock(&b->lock);return data;
}struct producers  buffer;
void *producer(void *data)
{int n;for(n=0;n<10;n++){printf("Producer : %d-->\n",n);put(&buffer,n);}put(&buffer,OVER);return NULL;
}void *consumer(void *data)
{int d;while(1){d=get(&buffer);if(d==OVER) break;printf("Consumer: --> %d\n",d);}return NULL;
}int main()
{pthread_t tha,thb;void *retval;init(&buffer);pthread_create(&tha,NULL,producer,0);pthread_create(&thb,NULL,consumer,0);pthread_join(tha,&retval);pthread_join(thb,&retval);return 0;}

程序说明:

主进程创建两个线程,一个称为producer,另一个称为consumer。producer向缓冲区中写整数,当缓冲区中已经写入数据后,就发送缓冲区中有数据的信号。consumer从缓冲区中读数据,当consumer从缓冲区中读数据后,就发送当前缓冲区未满的信号。


线程同步之信号量

sem_wait函数也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,这信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数就 会地等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。

sem_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同 时对同一个文件进行读、加和写操作的两个程序就有可能会引起冲突。信号量的值永远会正确地加一个“2”--因为有两个线程试图改变它。

下面给出实现生产者/消费者的信号量的例子。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 4
#define OVER (-1)
struct producers
{int buffer[BUFFER_SIZE];int        readpos, writepos;sem_t sem_read;sem_t  sem_write;
};void init(struct producers *b)
{sem_init(&b->sem_write,0,BUFFER_SIZE-1);sem_init(&b->sem_read,0,0);b->readpos=0;b->writepos=0;
}void put(struct producers *b, int data)
{sem_wait(&b->sem_write);b->buffer[b->writepos]=data;b->writepos++;if(b->writepos>=BUFFER_SIZE) b->writepos=0;sem_post(&b->sem_read);
}int get(struct producers *b)
{int data;sem_wait(&b->sem_read);data=b->buffer[b->readpos];b->readpos++;if(b->readpos>=BUFFER_SIZE) b->readpos=0;sem_post(&b->sem_write);return data;
}struct producers  buffer;
void *producer(void *data)
{int n;for(n=0;n<10;n++){printf("Producer : %d-->\n",n);put(&buffer,n);}put(&buffer,OVER);return NULL;
}void *consumer(void *data)
{int d;while(1){d=get(&buffer);if(d==OVER) break;printf("Consumer: --> %d\n",d);}return NULL;
}int main()
{pthread_t tha,thb;void *retval;init(&buffer);pthread_create(&tha,NULL,producer,0);pthread_create(&thb,NULL,consumer,0);pthread_join(tha,&retval);pthread_join(thb,&retval);return 0;}

Linux C编程--线程操作2--线程同步详解相关推荐

  1. python进程线程处理模块_python程序中的线程操作 concurrent模块使用详解

    一.concurrent模块的介绍 concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecut ...

  2. Linux系统编程32:进程信号之详解信号集操作函数(sigset_t ,sigpending,sigprocmask)

    文章目录 (1)sigset_t (2)信号集操作函数 (1)sigset_t 前面说过,未决和阻塞分别用位图来表示,于是我们把保存位图这样的数据类型称为sigset_t,sigset_t称为信号集, ...

  3. Linux网络编程——I/O复用之select详解

    https://blog.csdn.net/lianghe_work/article/details/46506143 一.I/O复用概述 I/O复用概念: 解决进程或线程阻塞到某个 I/O 系统调用 ...

  4. Linux系统编程33:进程信号之详解信号的捕捉过程,用户态和内核态及其切换,sigaction和signal

    文章目录 (1)用户态和内核态 (2)用户态和内核态的切换 (3)内核是如何实现信号的捕捉 (4)sigaction (1)用户态和内核态 我们说过,每个Linux进程有4GB的地址空间 其中0-3G ...

  5. Linux系统编程3:基础篇之详解Linux软件包管理器yum

    文章目录 (1)什么是软件包 A:软件包 B:注意事项 C:yum基本使用 (2)安装rzsz (1)什么是软件包 A:软件包 区别Windows,在Linux下安装软件,第一种方法是下载程序源代码, ...

  6. Linux系统编程2:基础篇之详解Linux中的权限问题

    文章目录 权限 (1)超级用户和普通用户 (2)Linux权限管理 A:文件访问者的分类 B:文件类型和访问权限 A:文件类型 B:基本权限 C:权限的表示方法 D:权限的设置 E:粘滞位 补: 权限 ...

  7. linux shell编程if语句内判断参数详解【ZT】

                  shell 编程中使用到得if语句内判断参数 –b 当file存在并且是块文件时返回真 -c 当file存在并且是字符文件时返回真 -d 当pathname存在并且是一个目 ...

  8. 【Linux从青铜到王者】第十五篇:Linux网络编程套接字两万字详解

    系列文章目录 文章目录 系列文章目录 前言 一.网络数据的五元组信息 1.理解源IP地址和目的IP地址 2.理解 "端口号" 和 "进程ID" 3.理解源端口号 ...

  9. Linux C :线程操作和线程同步的多线程并发编程

    在这之前可以先看看这边文章了解线程概念,信号量,条件变量,死锁.管程等概念 https://blog.csdn.net/superSmart_Dong/article/details/11666837 ...

最新文章

  1. Git安装教程(Windows安装)
  2. Linux学习笔记-基本操作2
  3. 广药谋定中国农民丰收节交易会-万祥军:谋定乡村振兴基金
  4. 启明云端分享|ESP32学习笔记参考GPIO口操作
  5. 《Netty权威指南》
  6. Java导出Excel 复杂表头
  7. 【java笔记】Stream流(2):获取流的两种方法
  8. [转]FCKeditor在ASP配置环境中的使用
  9. C++ vector理解
  10. DSP入门小白学习日记第四篇
  11. 1人30天44587行代码,分享舍得网开发经验
  12. python 俩冒号_python中双冒号
  13. 三维管型ybc预览以及动态成型仿真控件
  14. ROS快速入门第三讲——ROS的Subscriber订阅者
  15. 充满正能量阳光活的生日祝福语
  16. 机器学习中常用的分类算法总结
  17. linux oracle vncserver,Linux配置vnc
  18. Homekit智能家居之智能吸顶灯
  19. [RK3288][Android6.0] 调试笔记 --- ro.serialno的获取
  20. 蓝奏批量自定义域名替换源码

热门文章

  1. python开源项目及示例代码
  2. python知识合集
  3. 十天精通CSS3(3)
  4. 一则关于运算符的小例
  5. OSPF Router-ID的选择
  6. 关于登录linux时,/etc/profile、~/.bash_profile等几个文件的执行过程
  7. CCNP精粹系列之三十二--BGP下一跳问题,推荐
  8. [MySQL]快速解决is marked as crashed and should be repaired故障
  9. Salesforce 用机器学习来自动总结文本,AI+SaaS 是未来吗?
  10. 马上开始写 react ES6 --- 基于gulp 和 Babel 的脚手架