如果多个线程访问相同的数据,并且它们中至少有一个修改了数据,那么对共享数据的所有访问必须同步以防止数据竞争。但是,一个正在读取共享数据的线程可能中断另一个正在修改相同共享数据的线程,因此,可能导致线程读取到不一致的数据。

甚至,由于程序在每次执行时系统可能调度不同的线程,导致每次运行程序时错误消息只能间歇地反映当时情况,很难在测试中复现错误。如例 1 所示,哪怕是自增一个计数器这样的简单操作,都可能产生数据竞争。

【例1】没有同步下的并行存储访问

#include

#include

#define COUNT 10000000L

long counter = 0

void incFunc(void) { for (long i = 0; i < COUNT; ++i) ++counter; }

void decFunc(void) { for (long i = 0; i < COUNT; ++I) --counter; }

int main(void)

{

clock_t cl = clock()

thrd_t th1, th2;

if (thrd_create(&th1, (thrd-start_t)incFunc, NULL) != thrd_success

|| thrd_create(&th2, (thrd_start_t)decFunc, NULL) != thrd_success)

{

fpintf(stderr,"Error creating thread/n"); return -1;

}

thrd_join(th1, NULL);

thrd_join(th2, NULL);

printf("Counter: %ld \t", counter);

printf("CPU time: %ld ms\n", (clock()-cl)*1000L/CLOCKS_PER_SEC);

return 0;

}

在程序结束时,计数器应为 0。然而,在没有同步的情况下,结果则不同:程序每次运行时,最终获得计数器值都是不同的。下面是一个典型的输出示例:

Counter: -714573 CPU time: 59 ms

为了保障同步,C 标准库提供了互斥操作(mutex operation)和原子操作(atomic operation)。

互斥

互相排斥(mutex exclusion)技术,简称为互斥(mutex),它用于防止多个线程同时访问共享资源。互斥技术采用一个对象控制独占访问权限,该对象称之为互斥。配合条件变量(condition variable),互斥可以实现广泛的同步访问控制。例如,它们允许程序员为数据访问操作指定执行次序。

在 C 程序中,一个互斥采用类型为 mtx_t 的对象表示,它能在一段时间内被一个线程锁定,而其他线程必须等待,直到它被解锁。在头文件 threads.h 中,包括了关于互斥操作的所有声明。最重要的互斥函数有:

int mtx_init(mtx_t*mtx,int mutextype);

创建一个互斥,该互斥的属性由 mutextype 指定。如果成功创建了一个新互斥,函数 mtx_init()会将新互斥写入由参数 mtx 引用的对象,然后返回宏值 thrd_success。

参数 mutextype 的取值可以是以下 4 个:

mtx_plain

mtx_timed

mtx_plain | mtx_recursive

mtx_timed | mtx_recursive

mtx_plain 表示请求一个简单的互斥,它既不支持超时也不支持递归,而其他 3 个值则表示支持超时和(或)递归。

void mtx_destroy(mtx_t*mtx);

销毁 mtx 引用的互斥,并释放它的所有资源。

int mtx_lock(mtx_t*mtx);

阻塞正在调用的线程,直到该线程获得参数 mtx 引用的互斥。除该互斥支持递归的情况以外,正在调用的线程不能是已持有该互斥的线程。如果调用成功获得互斥,则函数返回值 thrd_success,否则,返回值 thrd_error。

int mtx_unlock(mtx_t*mtx);

释放参数 mtx 引用的互斥。在调用函数 mtx_unlock()之前,调用者必须持有该互斥。如果调用释放互斥成功,则函数返回值 thrd_success,否则,返回值 thrd_error。

通常情况下,在代码某个关键区间(critical section)的起始点调用函数 mtx_lock(),在其结束点调用函数 mtx_unlock(),在这段区间中只有一个线程执行。

函数 mtx_lock()还有两个替代的选择:一个选择是函数 mtx_trylock(),如果该互斥恰好未被其他任何线程获取,它则为当前线程获得互斥,如果该互斥被其他线程获取,它也不会阻塞当前线程;另一个选择是函数 mtx_timedlock(),它仅在指定的时间内阻塞线程。所有这些函数都通过其返回值表明调用它们后,是否成功地获得了互斥。

例 2 中的程序是例 1 的修改版本,它展示了如何使用互斥来消除对变量 counter 的数据竞争。

【例2】在例 1 的程序中添加一个互斥

#include

#include

#define COUNT 10000000L

long counter = 0;

mtx_t mtx; // 为访问counter而设立的互斥

void incFunc(void)

{

for (long i = 0; i < COUNT; ++i)

{ mtx_lock(&mtx); ++counter; mtx_unlock(&mtx); }

}

void decFunc(void)

{

for (long i = 0; i < COUNT; ++i)

{ mtx_lock(&mtx); --counter; mtx_unlock(&mtx); }

}

int main(void)

{

if (mtx_init(&mtx, mtx_plain) != thrd_success)

{

fprintf(stderr, "Error initializing the mutex.\n");

return -1;

}

// 如例14-2所示,启动线程,等待它们完成,打印输出

mtx_destroy(&mtx);

return 0;

}

函数 incFunc()和 decFunc()将不再并行地访问 counter,因为一次只有其中一个可以锁定互斥(为保障可读性,省略错误检查)。现在,在程序结束时,计数器具有正确的值:0。下面是一个典型的输出示例:

Counter: 0 CPU time: 650 ms

实现同步性需要付出代价。较高的 CPU 时间表明:修改后的程序需要大约 10 倍于原来的时间来运行。其原因是,通过锁定互斥实现同步性远比自增和自减一个变量具有更为复杂的操作。在不需要互斥锁定的情况下,使用原子对象可以获得更好的性能。

原子对象

原子对象(atomic object)是一个可通过原子操作(atomic operation)被读取或修改的对象。原子操作是指不能被并行线程中断的操作。在C11标准下,可以使用类型限定符_Atomic声明一个原子对象(如果实现版本定义了宏__STDC_NO_ATOMICS__,则表明该实现版本不支持原子操作,自然也不能声明原子对象)。例如,在例14-2程序中的变量counter可以通过以下方式声明它为原子对象:

_Atomic long counter = ATOMIC_VAR_INIT(0L);

上述声明定义了原子化的 long 类型变量 counter,并将其值初始化为 0。在头文件 stdatomic.h 中定义了宏 ATOMIC_VAR_INIT,以及其他所有用于原子对象的宏、类型和声明。特别是,stdatomic.h 中还定义了对应于所有整数类型的原子类型缩写。例如,类型 atomic_uchar 等效于 _Atomic unsigned char。

语法 _Atomic(T)也可用于为给定的非原子类型 T 指定其对应的原子类型。数组和函数类型不能为原子类型。然而,原子类型可以具有不同于其对应的非原子类型的空间大小和对齐方式。

原子操作

读取或写入一个原子对象是一个原子操作,也就是说它是不能被中断的操作。这意味着:不同的线程可以同时访问一个原子对象而不引起竞态条件。对于每个原子对象,对象的所有修改以一个确定的全局化次序执行,这称为该对象的修改次序(modification order)。

具有结构或联合类型的原子对象只能被作为一个整体读取或写入:为了安全地访问单个成员,原子结构或联合应首先复制到等效的非原子对象中。

注意,无论是使用宏 ATOMIC_VAR_INIT,还是通过泛型函数 ATOMIC_INIT(),一个原子对象的初始化不是一个原子操作。

原子操作通常用于进行读-修改-写操作。例如,后缀自增和自减运算符 ++ 和 --,当它们应用于原子对象时,是原子化的读-修改-写操作。同样,复合赋值运算符,如 +=,当其原子化使用时,它们的左操作数是一个原子对象。

例 1 中的程序可以通过声明变量 counter 作为原子对象,在不受任何其他影响下执行正确的计数,以最终获得 0 值。该方案计时结果显示,使用原子类型变量 counter 比例 2 所使用的互斥方法要快两倍多。

除了已经提到的运算符,还有许多函数可以执行原子操作,包括 atomic_store()、atomic_exchange()和 atomic_compare_exchange_strong()。

原子类型具有无锁(lock-free)属性,它表示不使用锁定和解锁操作实现对一个原子对象的原子访问。该方式只需要使用类型 atomic_flag(它是一个结构类型)以确保实现无锁,atomic_flag 有“设置”和“清除”两种状态。宏 ATOMIC_FLAG_INIT 将一个 atomic_flag 对象初始化为“清除”状态,如以下示例声明所示:

atomic_flag done = ATOMIC_FLAG_INIT;

C11 提供了函数 atomic_flag_test_and_set()和 atomic_flag_clear(),由此对一个 atomic_flag 对象执行状态操作。整型原子类型通常也都是无锁的。要确定一个给定的类型是否是无锁的,程序可以检查宏 ATOMIC_type_LOCK_FREE,其中 type 是一个指定整数类型的大写缩写,如 BOOL、INT,或 LLONG。

与指针类型对应的宏是 ATOMIC_POINTER_LOCK_FREE。所有这些宏的值可能为 0、1 或 2。值为 0,表示该类型不是无锁的;值为 1,表示该类型对特定对象是无锁的;值为 2,表示该类型始终是无锁的。或者,可以调用泛型函数来确定一个给定的原子对象是否是无锁的:

_Bool atomic_is_lock_free(const volatile A *obj);

在函数参数声明中的占位符A代表任一原子类型。因此,参数 obj 为指针,它指向任一给定原子对象。

php多线程原子操作,C语言线程互斥和原子操作相关推荐

  1. QT多线程(三)线程互斥与同步

    前言 线程之间存在着相互制约的关系: 互斥关系,如线程争夺I/O设备而导致一方必须等待一方使用结束后方可使用 同步关系,完成同一任务的线程之间,需要协调它们的工作而相互等待.交互 临界区 先看这个类: ...

  2. 『Linux』第九讲:Linux多线程详解(三)_ 线程互斥 | 线程同步

    「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(二),今天这篇是 Linux多线程详解(三),内容大致是线程互斥与线程同步,讲解下面开始! 「归属专栏」Linux系统编程 ...

  3. python多线程同步与互斥_Python之多线程:线程互斥与线程同步

    一.锁在多线程中的使用:线程互斥 lock = threading.Lock()#创建一个锁对象 1.with lock: pass 和进程使用的方式相同 2.控制线程结束的时间 通过一个全局变量 # ...

  4. 秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量

    前面<秒杀多线程第四篇一个经典的多线程同步问题>提出了一个经典的多线程同步互斥问题,这个问题包括了主线程与子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后面分别用了 ...

  5. Linux 多线程(二)线程安全:线程安全、互斥与互斥锁、死锁、同步与条件变量

    线程安全 互斥 死锁 同步 线程安全 所谓线程安全,其实就是当多个线程对临界资源进行争抢访问的时,不会造成数据二义或者逻辑混乱的情况(通常情况下对全局变量和静态变量进行操作时在会出现) 常见的线程安全 ...

  6. int linux 原子操作_linux c++编程之多线程:原子操作如何解决线程冲突

    在多线程中操作全局变量一般都会引起线程冲突,为了解决线程冲突,引入原子操作. 1.线程冲突 #include #include #include #include int g_count = 0;vo ...

  7. linux下的C语言开发(线程互斥)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 对于编写多线程的朋友来说,线程互斥是少不了的.在linux下面,编写多线程常用的工具其实是pt ...

  8. Java基础_17 | Java多线程程序设计(Java中两种创建线程的方法、多线程之间的同步和互斥)

    1. 多线程实现最核心的机制 一个程序在其执行过程中, 可以产生多个线程, 形成多条执行线索.,每条线程,有产生.存在和消亡的过程,并且独立完成各自的功能,互不干扰. 多线程程序运行只占用一个CPU, ...

  9. c语言线程面试题,java多线程面试题 PDF 下载

    主要内容: 基础知识 并发编程的优缺点 为什么要使用并发编程(并发编程的优点) 充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升 方便进行业务拆分,提 ...

最新文章

  1. 分享一个 markdown 编辑器 - Mditor
  2. 物体检测丨从R-CNN到Mask R-CNN
  3. 【嵌入式开发】C语言 指针数组 多维数组
  4. Angular2 - Starter - Routes, Route Resolver
  5. Scrapy 扩展中间件: 同步/异步提交批量 item 到 MySQL
  6. cocos 报错dts文件未导入_cocos2dx 3.4项目 导入到 eclipse 爬过的坑
  7. LeetCode刷题: 整数反转
  8. spring事务浅析
  9. iOS UIButton文字和图片间距随意调整
  10. Java面试题系列(X)优化tomcat配置
  11. 关系抽取综述及相关顶会论文介绍
  12. 关于有默认值的字段在用EF做插入操作时的思考(续)
  13. html音乐播放器代码大全,关于HTML 音乐播放器代码|音乐播放器网页代码大全(转)...
  14. 2012年3月份第1周51Aspx源码发布详情
  15. IDEA 导入p3c-formatter 格式化代码
  16. 正项级数收敛性的判别法
  17. 苹果付费app共享公众号_8.10号,IOS苹果App Store 5款付费App限时免费,支持iPhone/iPad...
  18. 国外数据平台统计分析sdk
  19. 传统算法与神经网络算法,最简单的神经网络算法
  20. 衡山 22/11/27 vue4

热门文章

  1. 爽!一个注解,搞定 SpringBoot 操作日志
  2. Householder transformation + QL to calculate eigenValue and eigenVectors of Hertian Matrix, cpu code
  3. mycobot 使用教程
  4. Mybatis的mapper代理开发方法
  5. outlook邮箱显示一直启动中_win10outlook邮箱怎么设置开机自动运行 - 卡饭网
  6. 在Excel表格中如何设置字体随单元格大小变化
  7. MATLAB(九)数值微积分
  8. 图片文字翻译的软件有哪些?快收藏这几款实用的软件
  9. 猿创征文|浅谈区块链-区块链是什么?
  10. 用 AsyncDisplayKit 開發響應式 iOS App