一、进程同步机制

1.1 互斥量(mutex)

我们已经知道了互斥量可以用于在线程间同步,但实际上,互斥量也可以用于进程间的同步。为了达到这一目的,可以在pthread_mutex_init初始化之前,修改其属性为进程间共享。mutex的属性修改函数主要有以下几个:

主要应用函数:

pthread_mutexattr_t mattr 类型:     用于定义互斥量的属性
pthread_mutexattr_init函数:            初始化一个mutex属性对象
pthread_mutexattr_destroy函数:     销毁mutex属性对象 (而非销毁锁)
pthread_mutexattr_setpshared函数:  修改mutex属性。

int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
我们重点看第二个参数:pshared,它有以下两个取值:

  • 线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)
  • 进程锁:PTHREAD_PROCESS_SHARED

要想实现进程间同步,需要将mutex的属性改为PTHREAD_PROCESS_SHARED。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/wait.h>struct mt {int num;pthread_mutex_t mutex;pthread_mutexattr_t mutexattr;
};int main(void)
{int i;struct mt *mm;pid_t pid;mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);memset(mm, 0, sizeof(*mm));pthread_mutexattr_init(&mm->mutexattr);                                  //初始化mutex属性对象pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED);    //修改属性为进程间共享pthread_mutex_init(&mm->mutex, &mm->mutexattr);                          //初始化一把mutex琐pid = fork();if (pid == 0) {for (i = 0; i < 10; i++) {sleep(1);pthread_mutex_lock(&mm->mutex);(mm->num)++;pthread_mutex_unlock(&mm->mutex);printf("-child----------num++   %d\n", mm->num);}} else if (pid > 0) {for ( i = 0; i < 10; i++) {sleep(1);pthread_mutex_lock(&mm->mutex);mm->num += 2;pthread_mutex_unlock(&mm->mutex);printf("-------parent---num+=2  %d\n", mm->num);}wait(NULL);}pthread_mutexattr_destroy(&mm->mutexattr);          //销毁mutex属性对象pthread_mutex_destroy(&mm->mutex);                  //销毁mutexmunmap(mm,sizeof(*mm));                             //释放映射区return 0;
}

1.2 文件锁

顾名思义,就是通过文件实现锁机制。具体来讲,是通过借助 fcntl函数来实现锁机制。当操作文件的进程没有获得锁时,虽然可以打开文件,但无法对文件执行执行read、write操作。

fcntl函数:

函数原型:int fcntl(int fd, int cmd, ... /* arg */ );
函数作用:获取、设置文件访问控制属性。

参数介绍:

参数cmd有以下取值:

F_SETLK (struct flock *) 设置文件锁(trylock)
F_SETLKW (struct flock *)   设置文件锁(lock)W --> wait
F_GETLK (struct flock *)    获取文件锁

数据类型flock原型如下:

struct flock {...short l_type;       锁的类型:F_RDLCK 、F_WRLCK 、F_UNLCKshort l_whence;    偏移位置:SEEK_SET、SEEK_CUR、SEEK_END off_t l_start;           起始偏移:1000off_t l_len;            长度:0表示整个文件加锁pid_t l_pid;         持有该锁的进程ID:(F_GETLK only)...};

进程间文件锁示例

多个进程对加锁文件进行访问:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>void sys_err(char *str)
{perror(str);exit(1);
}int main(int argc, char *argv[])
{int fd;struct flock f_lock;if (argc < 2) {printf("./a.out filename\n");exit(1);}if ((fd = open(argv[1], O_RDWR)) < 0)sys_err("open");f_lock.l_type = F_WRLCK;        /*选用写琐*/
//    f_lock.l_type = F_RDLCK;      /*选用读琐*/ f_lock.l_whence = SEEK_SET;f_lock.l_start = 0;f_lock.l_len = 0;               /* 0表示整个文件加锁 */fcntl(fd, F_SETLKW, &f_lock);printf("get flock\n");sleep(10);f_lock.l_type = F_UNLCK;fcntl(fd, F_SETLKW, &f_lock);printf("un flock\n");close(fd);return 0;
}

文件锁类似于读写锁,依然遵循“读共享、写独占”特性。但是,如果进程不加锁直接操作文件,依然可访问成功,但数据势必会出现混乱。

既然文件锁可用应用在进程中,那在多线程中,可以使用文件锁吗?

答案是不行的。因为多线程间共享文件描述符,而给文件加锁,是通过修改文件描述符所指向的文件结构体中的成员变量来实现的。因此,多线程中无法使用文件锁。

二、线程同步机制

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。

2.1 互斥锁(mutex)

通过锁机制实现线程间的同步。

1、初始化锁

在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。

静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);

2、加锁

对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。

int pthread_mutex_lock(pthread_mutex *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);

3、解锁

在完成了对共享资源的访问后,要对互斥量进行解锁。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

4、销毁锁

锁在是使用完成后,需要进行销毁以释放资源。

int pthread_mutex_destroy(pthread_mutex *mutex);

2.2 条件变量(cond)

条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

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

1、初始化条件变量

静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

2、等待条件成立

释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

3、激活条件变量

pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞

4、清除条件变量

无线程等待,否则返回EBUSY

int pthread_cond_destroy(pthread_cond_t *cond);

使用示例:

while (1)  {  //这个mutex主要是用来保证pthread_cond_wait的并发性  pthread_mutex_lock(&mtx);  while (head == NULL)  {  //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何  //这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线  //程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。  //这个时候,应该让线程继续进入pthread_cond_wait  // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,  //然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立  //而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源  //用这个流程是比较清楚的  pthread_cond_wait(&cond, &mtx);  p = head;  head = head->n_next;  printf("Got %d from front of queue/n", p->n_number);  free(p);  }  pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁  }

2.3 信号量(sem)

如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。

1、信号量初始化

int sem_init (sem_t *sem , int pshared, unsigned int value);

这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

2、等待信号量

给信号量减1,然后等待直到信号量的值大于0。

int sem_wait(sem_t *sem);

3、释放信号量

信号量值加1。并通知其他等待线程。

int sem_post(sem_t *sem);

4、销毁信号量

我们用完信号量后都它进行清理。归还占有的一切资源。

int sem_destroy(sem_t *sem);

2.4 读写锁

读写锁与互斥量的功能类似,对临界区的共享资源进行保护!互斥量一次只让一个线程进入临界区,读写锁比它有更高的并行性。读写锁有以下特点:

  1. 如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样就可以多个线程并行操作。但这个时候,如果再进行写锁加锁就会发生阻塞,写锁请求阻塞后,后面如果继续有读锁来请求,这些后来的读锁都会被阻塞!这样避免了读锁长期占用资源,防止写锁饥饿!

  2. 如果一个线程用写锁锁住了临界区,那么其他线程不管是读锁还是写锁都会发生阻塞!

1、宏常量初始化

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

2、函数初始化

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

rwlock:读写锁的pthread_rwlock_t结构指针

attr:读写锁的属性结构指针。不需要别的属性默认为NULL。

3、读写锁加锁与解锁

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
#include <pthread.h>
#include <time.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);

try类函数加锁:如果获取不到锁,会立即返回错误EBUSY!
timed类函数加锁:如果规定的时间内获取不到锁,会返回ETIMEDOUT错误

4、销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

2.5 自旋锁

自旋锁与互斥量功能一样,唯一一点不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁!!!

自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间。

自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_xxx()中mutex换成spin,如:pthread_spin_init()。

2.6 屏障

barrier(屏障)与互斥量,读写锁,自旋锁不同,它不是用来保护临界区的。相反,它跟条件变量一样,是用来协同多线程一起工作!!!

条件变量是多线程间传递状态的改变来达到协同工作的效果。屏障是多线程各自做自己的工作,如果某一线程完成了工作,就等待在屏障那里,直到其他线程的工作都完成了,再一起做别的事。举个通俗的例子:

  1. 对于条件变量。在接力赛跑里,1号队员开始跑的时候,2,3,4号队员都站着不动,直到1号队员跑完一圈,把接力棒给2号队员,2号队员收到接力棒后就可以跑了,跑完再给3号队员。这里这个接力棒就相当于条件变量,条件满足后就可以由下一个队员(线程)跑。

  2. 对于屏障。在百米赛跑里,比赛没开始之前,每个运动员都在赛场上自由活动,有的热身,有的喝水,有的跟教练谈论。比赛快开始时,准备完毕的运动员就预备在起跑线上,如果有个运动员还没准备完(除去特殊情况),他们就一直等,直到运动员都在起跑线上,裁判喊口号后再开始跑。这里的起跑线就是屏障,做完准备工作的运动员都等在起跑线,直到其他运动员也把准备工作做完!

1、创建屏障

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);

barrier:pthread_barrier_t结构体指针
attr:屏障属性结构体指针
count:屏障等待的线程数目,即要count个线程都到达屏障时,屏障才解除,线程就可以继续执行

2、等待

#include <pthread.h>int pthread_barrier_wait(pthread_barrier_t *barrier);

函数的成功返回值有2个,第一个成功返回的线程会返回PTHREAD_BARRIER_SERIAL_THREAD,其他线程都返回0。可以用第一个成功返回的线程来做一些善后处理工作。

3、销毁屏障

#include <pthread.h>
int pthread_barrier_destroy(pthread_barrier_t *barrier);

原文链接:
https://blog.csdn.net/KingCat666/article/details/75269593
https://developer.aliyun.com/article/642544

linux进程--多线程/多进程同步(十)相关推荐

  1. linux 多线程 多进程同步

    多线程 同步的方法 1. 临界区 2. 互斥量(注意mutex只能用于线程的互斥,不能用于进程) 3. 信号量 4. 事件 多进程 同步方法 管道(Pipe)及有名管道(named pipe):管道可 ...

  2. Linux进程管理: 多进程编程

    多进程编程 mind-Mapping保存有xmind原始文件,可直接获取 无名管道PIPE 命名管道FIFO POSIX共享内存 POSIX消息队列 POSIX信号量 SYS V共享内存 SYS V消 ...

  3. linux 进程间界面嵌套,WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)...

    WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验.如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程.然而,就不能让 ...

  4. linux 多进程 同步,Linux内核同步,进程,线程同步

    包括我自己在内,很多人对内核,进程,线程同步都不是很清楚,下面稍微总结一下: 内核同步: 主要是防止多核处理器同时访问修改某段代码,或者在对设备驱动程序进行临界区保护.主要有一下几种方式: 1. Mu ...

  5. 多进程与多线程通信同步机制

    多进程通信方式 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系通常是指父子进程关系. 命名管道FIFO:有名管道也是半双工的通信方式,但 ...

  6. linux 多线程 多进程 利用率,多进程与多线程的深度比较

    嵌入式Linux中文站,关于多进程和多线程,教科书上最经典的一句话是"进程是资源分配的最小单位,线程是CPU调度的最小单位".这句话应付考试基本上够了,但如果在工作中遇到类似的选择 ...

  7. 什么是多进程-多线程-多协程 ----进程和多进程

    进程和多进程 进程和多进程 进程 概念: 组成: 基本状态: 创建: 如何创建子进程? OS.fork 创建子进程 返回值 os.getpid():获取进程的进程号 os.getppid():获取父进 ...

  8. Linux多线程的同步-----信号量和互斥锁

    前面两篇给基本概念讲过了,大家有兴趣的可以去看一下: Linux多线程_神厨小福贵!的博客-CSDN博客进程和线程的区别有哪些呢?进程是资源分配的最小单位,线程是CPU调度的最小单位进程有自己的独立地 ...

  9. Linux多线程与同步

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 典型的UNIX系统都支持一个进程创建多个线程(thread).在Linux进程基础 ...

最新文章

  1. 汇编语言 第3版 王爽 检测点6.1自己的答案
  2. 大脑天天超负荷,三分天赋,七分练,世间惊现普通脑修炼秘籍
  3. c#_TcpListenerTcpClient
  4. 稳扎稳打Silverlight(13) - 2.0交互之鼠标事件和键盘事件
  5. 【级数】【马尔科夫链】n乘以x的n次方的和函数
  6. 统计自然语言处理基础(一)
  7. boost::geometry::util::calculation_type用法的测试程序
  8. python爬虫技术路线_爬虫学习——中国大学最好排名(技术路线:requests库和bs4)(来源于北理工Python网络爬虫与信息提取网络公开课)...
  9. Android下实现GPS定位服务
  10. 【CF551D】GukiZ and Binary Operations
  11. nodejs,webpack安装以及初步运用
  12. 数学建模更新10(蒙特卡罗模拟)
  13. 网卡5790c linux驱动,富士通DPK5790H驱动
  14. 概率论与数理统计——卡方分布的期望与方差
  15. Docker安装Adguardhome
  16. python培训班深圳-深圳python人工智能培训班
  17. 【UCIe】UCIe NOP 介绍
  18. C#-获取当前程序集Assembly的文件名
  19. offlineimap读取qq邮箱
  20. 索罗斯:走在时间前面的狐狸

热门文章

  1. 疯狂python讲义视频 百度云-疯狂Python讲义 PDF 含源码工具版
  2. 初学者学python看什么书-python初学者看什么书
  3. python安装包为什么这么小-python(x,y)安装好了为何还是加载不了包
  4. python类型转换-Python的数据类型转换函数
  5. python类型转换-马哥教育官网-专业Linux培训班,Python培训机构
  6. python绘制条形图-Python数据分析条形图的各种绘制方式
  7. python和php-PHP和Python如何选择?或许可以考虑这三个问题
  8. python写web难受-用Python编写web API的教程
  9. python语法syntaxerror怎么修改-Python 语法错误
  10. Makefile文件的编写规则