深入 C 语言和程序运行原理
于航
PayPal 技术专家
21121 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
深入 C 语言和程序运行原理
15
15
1.0x
00:00/00:00
登录|注册

14|标准库:如何使用互斥量等技术协调线程运行?

_Thread_local:定义线程本地变量
cnd_destroy:销毁条件变量
cnd_broadcast:通知所有等待的线程
cnd_signal:通知一个等待的线程
cnd_wait:等待条件变量
cnd_init:初始化条件变量
memory_order:指定内存顺序
atomic_fetch_add_explicit:原子加法操作
_Atomic:定义原子类型变量
mtx_destroy:销毁互斥量
mtx_unlock:解锁
mtx_lock:加锁
mtx_init:初始化互斥量
mtx_timed:支持超时属性
mtx_recursive:支持递归锁定
mtx_plain:基本类型,不支持递归锁定
《现代操作系统》
《C++ Concurrency in Action》
x86-64 指令集中的 mfence、lfence 与 sfence 指令的作用
示例代码:展示了线程本地变量的使用和线程间的数据隔离
使用
定义:每个线程独有的变量,避免数据竞争
示例代码:展示了如何使用条件变量同步线程执行
使用
定义:线程同步机制,用于线程间的通知和等待
示例代码:展示了如何使用原子操作解决数据竞争问题
使用
实现:依赖于 CPU 提供的原子机器指令
定义:不可分割的操作,保证了操作的原子性
示例代码:展示了如何使用互斥量保护共享资源
使用
类型
定义:一种同步机制,用于防止多个线程同时访问共享资源
资源推荐
思考题
线程本地变量 (Thread-Local Storage, TLS)
条件变量 (Condition Variable)
原子操作
互斥量 (Mutex)
C语言并发编程技术

该思维导图由 AI 生成,仅供参考

你好,我是于航。
在上一讲中,我主要介绍了有关并发编程的一些基础知识,并通过一个简单的例子展示了如何在 C 语言中进行线程创建等基本操作。同时我也向你介绍了,数据竞争、竞态条件,以及指令重排等因素,都在如何影响着多线程应用的执行正确性。那么,有哪些方法可以辅助我们解决这些问题呢?
今天我们就来看看 C 语言为并发编程提供的几大利器:互斥量、原子操作、条件变量,以及线程本地变量。

使用互斥量

从本质上来看,互斥量(Mutex)其实就是一把锁。一个线程在访问某个共享资源前,需要先对互斥量进行加锁操作。此时,其他任何想要再对互斥量进行加锁的线程都会被阻塞,直至当前线程释放该锁。而当锁被释放后,所有之前被阻塞的线程都开始继续运行,并再次重复之前的步骤,开始“争夺”可以对互斥量进行加锁的名额。通过这种方式,我们便可以保证每次在对多线程共享的资源进行操作时,都仅只有一个线程。
在 C 语言中,我们可以通过头文件 threads.h 提供的,以 “mtx_” 为前缀的相关接口来使用互斥量的能力。你应该还记得我在上一讲中提到的,那段存在数据竞争的 C 示例代码。这里我对它进行了改写,如下所示:
#include <threads.h>
#include <stdio.h>
#define THREAD_COUNT 10
#define THREAD_LOOP 100000000
mtx_t mutex;
long counter = 0;
int run(void* data) {
for (int i = 0; i < THREAD_LOOP; i++) {
mtx_lock(&mutex); // 对互斥量加锁,
counter++;
mtx_unlock(&mutex); // 释放一个互斥量;
}
printf("Thread %d terminates.\n", *((int*) data));
return thrd_success;
}
int main(void) {
#ifndef __STDC_NO_THREADS__
int ids[THREAD_COUNT];
mtx_init(&mutex, mtx_plain); // 创建一个简单、非递归的互斥量对象;
thrd_t threads[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
ids[i] = i + 1;
thrd_create(&threads[i], run, ids + i);
}
for (int i = 0; i < THREAD_COUNT; i++)
thrd_join(threads[i], NULL);
printf("Counter value is: %ld.\n", counter);
mtx_destroy(&mutex); // 销毁一个互斥量对象;
#endif
return 0;
}
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

C语言中的多线程编程技术是解决并发应用执行正确性和性能要求的重要方法。本文介绍了互斥量、原子操作、条件变量和线程本地变量等概念和使用方法。互斥量通过加锁操作确保多个线程有序地使用共享资源,而原子操作则提供了更便捷的方式避免数据竞争。条件变量可以更好地解决线程间数据依赖的问题,避免忙等待,提高CPU资源利用。线程本地变量可以避免数据竞争,并存储线程独有的信息。通过本文的介绍,读者可以快速了解C语言中使用互斥量和原子操作的方法,以及它们在多线程应用中的重要作用。文章还提到了x86-64指令集中的mfence、lfence和sfence指令的作用,以及鼓励读者进行更多的探索和讨论。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入 C 语言和程序运行原理》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(13)

  • 最新
  • 精选
  • ZR2021
    使用while是防止广播通知方式的虚假唤醒吧,需要用户进一步判断,但是,我看还有人说可能存在非广播方式的虚假唤醒,不知道什么场景下会出现这种情况,还有,我们现在使用的都是pthread 库,不知道跟老师讲的c库自带的这些原子等操作有什么区别……

    作者回复: 1. 没错!防止 Spurious Wakeup 是其中一个重要原因; 2. 实际上 C 标准中的大部分线程控制,以及条件变量等能力的实现,在 Unix 等系统上都是直接通过 pthread 库来实现的。你会发现接口使用上虽然有差异,但却也并不大。C 标准只是将最小的可用子集进行了标准化。

    2022-01-18
    4
  • ckj
    请问下 条件变量的例子中 21行 main中的互斥量重新加锁是不是需要等run中的释放之后才能加锁? 因为mtx_plain?

    作者回复: 是这样的。

    2022-05-21
    2
  • 用了条件变量后,还是有循环一直在判断 done 的值啊,只不过变成到 main 函数中判断了,感觉和之前没啥区别啊

    作者回复: 实际上这里的 while 循环不会持续执行,所有执行到 cnd_wait 的线程都会被阻塞,直到有子线程在适当时刻通过 cnd_signal “通知” main 线程。

    2022-02-13
    2
  • 白花风信子
    并发编程这儿感觉有点不熟悉.....请问有什么练习的建议吗?感觉和平常接触的有很多不同。

    作者回复: 并发编程由于相关概念较多,可以试着先从 13 14 这两讲中提到的基础内容熟悉起来。比如可以找一本专门讲并发编程的书看,这样可以有一个较为统一和完整的认知。当然,专门讲 C 并发的书不是很多,可以选择性看看 Thierry Delisle 写的《Concurrency in C》 ,或者《C++ Concurrency in Action》。

    2022-01-15
    2
  • 赖淦
    老师,条件变量和信号量是相同的概念吗?

    作者回复: 不是哈。

    2022-07-17
    2
    1
  • 左星辰
    memory_order的设置会被编译器处理很好理解,但是是如何被处理器理解的呢?

    作者回复: memory_order 的设置会被编译器编译为对应平台上的 fence 指令,比如 x86-64 下的 mfence、lfence 与 sfence 指令。可以看看这三个指令的具体用途,其他平台均是类似的。

    2022-04-24
    1
  • 猪小擎
    mutex有syscall吗?需要调用fetex系统调用吗?写一段cpp代码,在20个线程里每个县城++10万次,一个用mutex抢一个锁,一个用原子变量++,在mac的m1 max上运行,mutex要快得多

    作者回复: 据我所知 mutex 没有直接对应的 syscall,Linux 上的 mutex 是基于 futex 实现的。

    2022-06-15
  • liu_liu
    使用 while 的原因是,当阻塞的线程被重新调度运行时,done 的值可能被改变了,不是预期值。
    2022-01-14
    1
    4
  • dapaul
    老师,使用条件变量那段的例子不会死锁吗?main线程中加锁,然后阻塞等待,锁没释放。创建的线程等锁去释放条件变量,一直等不到锁,两边都在等
    2022-07-14
    2
    3
  • Cyan
    提个问题,问什么我将“使用条件变量”的C语言程序,改为C++程序,程序退出的时候就会报core dump呢?具体的程序如下: ``` #include <iostream> #include <mutex> #include <condition_variable> #include <thread> #include <chrono> std::mutex mtx; std::condition_variable cv; int done = 0; int run(void) { std::unique_lock<std::mutex> lock(mtx); done = 1; cv.notify_one(); lock.unlock(); return 1; } int main(void) { std::thread thd(run); std::unique_lock<std::mutex> lock(mtx); while (done == 0) { cv.wait(lock); } lock.unlock(); std::cout << "The value of done is: " << done << std::endl; return 0; } ``` 解答成在lock.unlock()前添加一行trd.join()即可,但是我不太理解,麻烦各位大佬答疑解惑
    2023-11-07归属地:广东
    1
收起评论
显示
设置
留言
13
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部