如何编写一个线程安全的程序或者函数库?
2017-11-22 17:59
399 查看
何为线程安全?
在多线程环境中编程,大家一直都强调线程安全,可以什么是线程安全呢?依据《Java 并发编程实践》/《Java Concurrency in Practice》一书,一个线程安全的Class
应当满足三个条件:
从多个线程访问时,其表现出正确的行为
无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织
调用端代码无需额外的同步或其他协调动作
具体说来,线程安全性体现在:
线程安全的函数通过“锁”来保护共享资源不被并发地访问。“线程安全”仅关心函数的实现,而不影响其外部接口。
如何实现线程安全?
实现线程安全的主要方法就是确保多线程调用共享资源时能够正确,有序的调用。可以对共享资源加互斥锁、原子操作等方法。
本文主要介绍如何用互斥锁确保共享资源调用的线程安全。
Mutex和MutexLock
首先介绍两个比较重要的工具类, 这两个工具类在C++11的标准库中都有实现。Mutex
Mutex 是封装临界区,主要实现互斥器的创建和销毁。 临街区在Windows中是CRITICAL_SECTION, 而在Linux上是
pthead_mutex_t. 在C++11中的标准库中即是
std::mutex
MutexLock/MutexGuard
MutexLock主要是负责临界区的进入和退出,也就是加锁和解锁。 在MutexLock执行构造函数时进行加锁操作,在执行析构函数时执行解锁操作,所以MutexLock的作用域就是自己的生命周期。 一般是函数内的局部变量。在C++11的实现是std::lock_guard
Mutex和MutexLock两个类都采用了RAII风格进行封装,这样封装的好处是保证了互斥锁出现死锁等现象。
具体的代码实现可以参考一下代码片。
#include <iostream> #include <pthread.h> using namespace std; class MutexLock { public: MutexLock() { pthread_mutex_init(&mutex_, NULL); } ~MutexLock() { pthread_mutex_destroy(&mutex_); } void Lock() { pthread_mutex_lock(&mutex_); } void Unlock() { pthread_mutex_unlock(&mutex_); } private: MutexLock(const MutexLock&); MutexLock& operator=(const MutexLock&); pthread_mutex_t mutex_; }; class MutexLockGuard { public: explicit MutexLockGuard(MutexLock& mutex): mutex_(mutex) { mutex_.Lock(); } ~MutexLockGuard() { mutex_.Unlock(); } private: MutexLockGuard(const MutexLockGuard&); MutexLockGuard& operator=(const MutexLockGuard&); MutexLock& mutex_; }; class Foo { public: void print() { MutexLockGuard lock(mutex_); cout << "Hello" << endl; } private: MutexLock mutex_; };
当然,如果利用C++11的特性,实现
Class Foo的线程安全,代码可以简化为:
#include <thread> #include <mutex> #include <iostream> class Foo { public: void print() { std::lock_guard<std::mutex> lock(p_mutex); cout << "Hello" << endl; } private: std::mutex p_mutex; };
通过以上例子, 在一个函数内部调用MutexLock可以实现一个临街区,确保共享资源正常调用。
从Stackoverflow借用的答案可以具体阐述如何确保我们的程序是线程安全的。
明确哪些资源是在多线程应用中被共享的
创建一个Mutex, 并在任何你需要访问共享资源之前加锁。(这些共享资源最好是private to class, 否则你不确定是否全面的保护共享资源)
清除掉全局变量。 最好不要用全局变量。
注意
static关键字, 带有
static的函数是不可重入的, 如果想保证线程安全,需要加静态互斥锁。 (从这篇文章可以看出,不同的编译器似乎对static变量的处理不同,gcc中的static变量是线程安全的,VC中的则未作处理。 )
相关文章推荐
- java程序中,如何安全的结束一个正在运行的线程?
- java程序中,如何安全的结束一个正在运行的线程?
- java程序中,如何安全的结束一个正在运行的线程?
- 如何在Java中编写一个线程安全的方法?
- java程序中,如何安全的结束一个正在运行的线程?interrupt()
- java程序中,如何安全的结束一个正在运行的线程?
- java程序中,如何安全的结束一个正在运行的线程
- SAP ABAP/4学习---如何给写好的一个程序分配事务代码,用户可以直接通过事务码来访问程序.或者加入收藏夹(9)
- 编写一个程序,定义一个职工类,输入3个职工的编号、姓名、工资和年龄, 类中的成员函数实现输入、输出,在主函数中定义对象,并计算输出3个职工的平均工资。
- Linux 多线程应用中如何编写安全的信号处理函数
- 通过学习学生信息管理系统软件,C程序中,如何设计和编写一个应用系统?
- [编写高质量代码:改善java程序的151个建议]建议42,如何让一个工具类不可实例化
- 如何判断一个数组是一维数组或者是二维数组?用什么函数?
- 编写高质量代码改善C#程序的157个建议——建议22:确保集合的线程安全
- 如何编写一个能读取Powerbuilder的数据窗口中数据的程序
- 如何编写一个C#程序
- 计算机笔试题:写一个函数,检查字符是否是整数,如果是,返回其整数值。(或者:怎样只用4行代码编写出一个从字符串到长整型的函数)
- 编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
- 8.9 编写函数打开文件用于输入,将文件内容读入 string 类型的 vector 容器,每一行存储为该容器对象 的一个元素。8.10 重写上面的程序,把文件中的每个单词存储为 容器的一个元素
- Android开发笔记之:如何安全中止一个自定义线程Thread的方法