linux多线程同步的四种方式
1. 在并发情况下,指令执行的先后顺序由内核决定。同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清楚是哪一个先执行。如果运行的结果依赖于多线程执行的顺序,那么就会形成竞争条件,每次运行的结果可能会不同,所以应该尽量避免竞争条件的形成。
2. 最常见的解决竞争条件的方法是将原先分离的两个指令构成一个不可分割的原子操作,其他任务就不能插入到原子操作中!
3. 对多线程来说,同步指的是在一定时间内只允许某一个线程访问某个资源,而在此时间内,不允许其他线程访问该资源!
4. 线程同步的常见方法:互斥锁,条件变量,读写锁,信号量
一、互斥锁(互斥量)
互斥锁是一种特殊的变量,有上锁(lock)和解锁(unlock)两种状态。
当处于解锁状态时,线程想获取该互斥锁,就可以获取不被阻塞,互斥锁变为锁定状态;
当处于锁定状态时,线程获取互斥锁被阻塞,并加入到这个互斥锁的等待队列中。
互斥锁有点像打印机,空闲时你可以打印;别人在打印时,你就需要排队等待打印机空闲。
1.1 创建并初始化一个互斥锁
使用 pthread_mutex_t 类型的变量来表示互斥锁。在使用之前,必须对其进行初始化。
静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化:pthread_mutex_init 函数
一般我们都使用静态初始化。理由:静态初始化通常比 pthread_mutex_init 更有效,而且可以在定义为全局变量时即完成初始化,这样可以保证在任何线程开始执行之前,初始化既已完成。
1.2 互斥锁相关属性及分类
//初始化互斥锁属性
pthread_mutexattr_init(pthread_mutexattr_t attr); //销毁互斥锁属性
pthread_mutexattr_destroy(pthread_mutexattr_t attr); //用于获取互斥锁属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr , int *restrict pshared); //用于设置互斥锁属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr , int pshared);
attr表示互斥锁的属性,pshared表示互斥锁的共享属性,有两种取值:
1)PTHREAD_PROCESS_PRIVATE:锁只能用于一个进程内部的两个线程进行互斥(默认情况)
2)PTHREAD_PROCESS_SHARED:锁可用于两个不同进程中的线程进行互斥,使用时还需要在进程共享内存中分配互斥锁,然后该互斥锁指定属性就可以了。
1.3 互斥锁常用函数
// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex); // 对互斥锁的锁定
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 对互斥锁的解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_lock函数会调用这个函数线程一直阻塞到互斥锁可用为止,而pthread_mutex_trylock会立即返回
二、条件变量
用处:当线程在等待满足某些条件时使线程进入睡眠状态,一旦条件满足,就唤醒线程。由于涉及到共享数据,因此条件变量是结合互斥锁来使用的。使用 pthread_cond_t 来表示条件变量。
2.1 相关函数
1)创建
静态初始化:pthread_cond_t convar = PTHREAD_COND_INITIALIZER
动态初始化:int pthread_cond_init(&condvar, NULL)
2) 销毁
int pthread_cond_destroy(&condvar)
3)等待
条件等待:int pthread_cond_wait(&condvar, &mutex)
计时等待:int pthread_cond_timewait(&condvar, &mutex, time)
1. 计时等待如果在给定时刻前条件没有被满足,则返回 ETIMEOUT,结束等待
2. 无论哪种等待方式,都必须有一个互斥锁配合,以方式多个线程同时请求pthread_cond_wait形成竞争条件。也就是说,在使用 pthread_cond_wait 之前,必须使用互斥锁加锁(pthread_mutex_lock);pthread_cond_timewait 同理。
4)唤醒
唤醒一个等待线程:pthread_cond_signal(&condvar)
唤醒所有等待线程:pthread_cond_broadcast(&cond)
重要的是,pthread_cond_signal 不会存在惊群效应,也就是它只唤醒一个等待线程,不会给所有线程发信号唤醒他们,然后要求他们自己去争抢资源!pthread_cond_signal 会根据等待线程的优先级和等待时间来确定唤醒哪一个等待线程!
2.2 条件变量应用实例------condition_variables.c
#include <stdio.h>
#include <pthread.h> int i = ; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER; void *threadfunc(void *pvoid)
{
while ()
{
pthread_mutex_lock(&mutex);
if (i < )
{
i++;
pthread_cond_signal(&condvar); /**< 子线程唤醒主线程 */
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
} return NULL;
} int main()
{
pthread_t tid;
pthread_create(&tid, NULL, &threadfunc, NULL); pthread_mutex_lock(&mutex); while (i < )
{
pthread_cond_wait(&condvar, &mutex);
} printf("i = %d\n", i);
pthread_mutex_unlock(&mutex);
pthread_join(tid, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condvar); return ;
}
该实例中,主线程监视全局变量 i 的值,如果 i 小于100,则等待。子线程递增 i 的值,直到 i 等于200。主线程直 i 大于或等于100之后,才能继续执行。 运行结果可能是 i = 100,也可能是i = xxx,其中xxx是一个100到200之间的数。子线程每次唤醒条件变量并释放互斥锁之后,将于主线程一同竞争互斥锁。当 i 大于100时,如果主线程获得互斥锁,就会显示 i 的值。也就是说,等待条件变量的线程在被唤醒时,并不自动获得互斥锁。
编译代码命令:
gcc -o condition_variables condition_variables.c -pthread

三、读写锁
读写锁与互斥锁类型,也叫共享互斥锁。互斥锁有上锁(lock)和解锁(unlock)两种状态,而且一次只有一个线程可以对其加锁。读写锁可以有三种状态:读模式加锁,写模式加锁,不加锁。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁(允许多个线程读但只允许一个线程写)
读写锁的特点:
如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作
如果有其他线程写数据,则其他线程都不允许读、写操作
读写锁的规则:
如果某线程申请了读锁,其他线程可以再申请读锁,但不能申请写锁
如果与其他线程申请了写锁,则其他线程不能申请读锁,也不能申请写锁
读写锁适合于对数据结构的读次数比写次数多得多的情况
#include <pthread.h>
// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); // 申请读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock ); // 申请写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock ); // 尝试以非阻塞的方式来在读写锁上获取写锁,
// 如果有任何的读者或写者持有该锁,则立即失败返回。
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 解锁
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock); // 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
实例:
// 一个使用读写锁来实现 4 个线程读写一段数据是实例。
// 在此示例程序中,共创建了 4 个线程,
// 其中两个线程用来写入数据,两个线程用来读取数据
#include <stdio.h>
#include <unistd.h>
#include <pthread.h> pthread_rwlock_t rwlock; //读写锁
int num = ; //读操作,其他线程允许读操作,却不允许写操作
void *fun1(void *arg)
{
while()
{
pthread_rwlock_rdlock(&rwlock);
printf("first read num == %d\n", num);
pthread_rwlock_unlock(&rwlock);
sleep();
}
} //读操作,其他线程允许读操作,却不允许写操作
void *fun2(void *arg)
{
while()
{
pthread_rwlock_rdlock(&rwlock);
printf("second read num == %d\n", num);
pthread_rwlock_unlock(&rwlock);
sleep();
}
} //写操作,其它线程都不允许读或写操作
void *fun3(void *arg)
{
while()
{
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write thread first\n");
pthread_rwlock_unlock(&rwlock);
sleep();
}
} //写操作,其它线程都不允许读或写操作
void *fun4(void *arg)
{
while()
{
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write thread second\n");
pthread_rwlock_unlock(&rwlock);
sleep();
}
} int main()
{
pthread_t ptd1, ptd2, ptd3, ptd4; pthread_rwlock_init(&rwlock, NULL);//初始化一个读写锁 //创建线程
pthread_create(&ptd1, NULL, fun1, NULL);
pthread_create(&ptd2, NULL, fun2, NULL);
pthread_create(&ptd3, NULL, fun3, NULL);
pthread_create(&ptd4, NULL, fun4, NULL); //等待线程结束,回收其资源
pthread_join(ptd1, NULL);
pthread_join(ptd2, NULL);
pthread_join(ptd3, NULL);
pthread_join(ptd4, NULL); pthread_rwlock_destroy(&rwlock);//销毁读写锁 return ;
}
四、信号量
信号量广泛用于进程或线程间的同步或互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
根据信号量的值来判断是否对公共资源具有访问权限,当信号量的值大于0时,可以访问,否则将阻塞。带有两个原子操作 P 和 V,一次 P 操作使信号量减1,一次 V 操作使信号量加 1。
#include <semaphore.h>
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value); // 信号量 P 操作(减 1)
int sem_wait(sem_t *sem); // 以非阻塞的方式来对信号量进行减 1 操作
int sem_trywait(sem_t *sem); // 信号量 V 操作(加 1)
int sem_post(sem_t *sem); // 获取信号量的值
int sem_getvalue(sem_t *sem, int *sval); // 销毁信号量
int sem_destroy(sem_t *sem);

// 信号量用于同步实例
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h> sem_t sem_g,sem_p; //定义两个信号量
char ch = 'a'; void *pthread_g(void *arg) //此线程改变字符ch的值
{
while()
{
sem_wait(&sem_g);
ch++;
sleep();
sem_post(&sem_p);
}
} void *pthread_p(void *arg) //此线程打印ch的值
{
while()
{
sem_wait(&sem_p);
printf("%c",ch);
fflush(stdout); // 刷新标准输出缓冲区
sem_post(&sem_g);
}
} int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
sem_init(&sem_g, , ); // 初始化信号量为0
sem_init(&sem_p, , ); // 初始化信号量为1 // 创建两个线程
pthread_create(&tid1, NULL, pthread_g, NULL);
pthread_create(&tid2, NULL, pthread_p, NULL); // 回收线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL); return ;
}
结果会依次打印26个字母,代码的执行顺序是,打印线程------>自增线程
参考:https://blog.csdn.net/daaikuaichuan/article/details/82950711
https://www.cnblogs.com/yinbiao/p/11190336.html
linux多线程同步的四种方式的更多相关文章
- 【Linux】多线程同步的四种方式
背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题? 通过多线程模拟多窗口售票为例: #include <iostream> #include<pthread.h> ...
- linux下实现web数据同步的四种方式(性能比较)
实现web数据同步的四种方式 ======================================= 1.nfs实现web数据共享2.rsync +inotify实现web数据同步3.rsyn ...
- 实现web数据同步的四种方式
http://www.admin10000.com/document/6067.html 实现web数据同步的四种方式 1.nfs实现web数据共享 2.rsync +inotify实现web数据同步 ...
- linux创建文件的四种方式(其实是两种,强行4种)
linux创建文件的四种方式: 1.vi newfilename->i->编辑文件->ESC->:wq! 2.touch newfilename 3.cp sourcePath ...
- JAVA多线程实现的四种方式
Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService.Cal ...
- 【转】JAVA多线程实现的四种方式
原文地址:http://www.cnblogs.com/felixzh/p/6036074.html Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callabl ...
- JAVA多线程实现的四种方式(转自https://www.cnblogs.com/felixzh/p/6036074.html)
Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService.Cal ...
- 关于Java多线程(JAVA多线程实现的四种方式)
Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService.Cal ...
- Java线程同步的四种方式详解(建议收藏)
Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...
随机推荐
- Linux网络架设篇,虚拟机l系统中网卡设备名与配置文件不符如何处理?
很多情况下,当我们在虚拟机中安装好linux系统后,并不能成功连上网.当我们配置好相关IP地址后同样不能成功连接网络.并且会体会网卡名与配置名不符,这时候应该怎么办呢? 1.清空下面文件 /etc/u ...
- Linux 磁盘管理篇,目录管理(一)
目录: 当我们在linux的ext2档案建立一个目录时,ext2会分配一个inode与至少一块Block给该目录,其中inode记录该目录在相关属性,并指向分配到在那块Block,而block ...
- k8s Service学习
service的概念 kubernetes service定义了一个抽象概念,一个pod的逻辑分组,一种可以访问的策略---通常称为服务.这组pod能够被service访问到,通常通过label se ...
- 创建 SysV 风格的 linux daemon 程序
本文介绍如何使用 C 语言创建 Linux 系统中 SysV 风格的 daemon 程序.注意:这是一种旧式的 daemon 程序写法,进入 systemd 时代后是不需要通过这样的方式创建 daem ...
- Java 泛型、通配符? 解惑
Java 泛型通配符?解惑 分类: JAVA 2014-05-05 15:53 2799人阅读 评论(4) 收藏 举报 泛型通配符上界下界无界 目录(?)[+] 转自:http://www.linux ...
- Ant安装与配置
1. 到apache 官网去下载最新版本的ant,http://ant.apache.org/:下载后直接解压缩到电脑上,不需要安装: 2.环境变量配置: 2.1 ->计算机右键->属性- ...
- python超实用的30 个简短的代码片段(二)
Python是目前最流行的语言之一,它在数据科学.机器学习.web开发.脚本编写.自动化方面被许多人广泛使用. 它的简单和易用性造就了它如此流行的原因. 如果你正在阅读本文,那么你或多或少已经使用过P ...
- Q - QQpet exploratory park HDU - 1493 (概率DP)
题目大意: 一共有61个位置,标记为0~60.其中有10个重要位置,分别为:5, 12, 22, 29, 33, 38, 42, 46, 50 and 55. 有一个筛子,一共6个面,标有1~6.摇到 ...
- 在Eclipse上实现简单的JDBC增删查改操作
在Javaweb的学习里,学到了如何完成简单的增删查改操作,在这里撰写一篇文章以便自己整理回忆. 首先要建立一些包和导入一些文件.建一些类.具体框架如图 编写Product类 public clas ...
- 总结php删除html标签和标签内的内容的方法
来源:https://www.cnblogs.com/shaoguan/p/7336984.html 经常扒别人网站文章的坑们:我是指那种批量式采集的压根不看内容的:少不了都会用到删除html标签的函 ...