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 ...
随机推荐
- Java第二十九天,文件及目录的管理,File类
一.基础知识点 1.路径分隔符 (1)什么是路径分隔符? 这个多被应用在环境变量设置当中,例如当我设置Path环境变量时,多个环境变量的路径要用 ':'(Windows系统用封号分隔)或 ':'(Li ...
- sigmod函数求导
sigmod函数: \[f(z)=\frac{1}{1+e^{-z}} \] 求导: \[\frac{\partial f(z)}{\partial z}=\frac{-1*-1*e^{-z}}{(1 ...
- myvue 模拟vue核心原理
// js部分index.js class Myvue{ constructor(options){ this.data = options.data; this.dep = new Dep(); v ...
- Android Them+SharedPreferences 修改程序所有view字体颜色、大小和页面背景
有这么一个需求,可以对页面的样式进行选择,然后根据选择改变程序所有字体颜色和页面背景.同时下一次启动程序,当前设置依然有效. 根据需求,我们需要一种快速,方便,有效的方式来实现需求,然后可以通过And ...
- 第十一节:configParse模块
作用:配置文件解析模块,用来增删改查配置文件内容,不区分大小写 配置文件案例: tets.ini [模块] key=value import configparser config = configp ...
- 教你如何在工作中“偷懒”,python优雅的帮你解决
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取htt ...
- stand up meeting 1/13/2016
part 组员 工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 UI测试和调整:与主程序完成合并 6 查漏补缺,扫除UI ...
- PHP常量:JSON_UNESCAPED_UNICODE
函数: json_encode() - 对变量进行 JSON 编码 说明: json_encode ( mixed $value [, int $options = 0 [, int $depth = ...
- SpringCloud(一)学习笔记之项目搭建
[springcloud项目名称不支持下划线] 一.创建父项目 File---new---project: 填写项目信息: 默认即可,点击finish创建完成: 由于父项目只用到pom文件 所以把sr ...
- webpack之Loader
我们知道webpack的优点之一就是专注于处理模块化的项目,能做到开箱即用,但同时这也是webpack的缺点,只能用于模块化开发的项目,例如:Vue,React,Angular.Webpack在进行打 ...