一、数据竞争的两种处理方式
多线程的核心是CPU的时间分片,同一时刻只能有一个线程获取到锁。对于没有获取到锁的线程通常有两种处理方式:自旋锁,没有获取到锁的线程会一直循环等待判断资源是否已经释放锁,不用将线程阻塞起来;互斥锁,把未获取到锁的线程阻塞起来,等待重新调度请求。
二、什么是自旋锁?
1、自旋锁属于busy-waiting(轮询等待)类型锁。
2、自旋锁最多只能被一个可执行线程持有。
3、如果一个可执行线程试图获得一个被其它线程持有的自旋锁,那么线程就会一直进行忙等待,自旋(空转),被阻塞线程依然不会让出CPU,等待自旋锁重新可用。如果自旋锁未被争用,请求锁的执行线程便立刻得到自旋锁,继续执行。
4、获取锁的线程一直处于活跃状态,但并没有执行任何有效的任务,使用自旋锁会造成busy-waiting。
5、自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的。
6、不会使线程进入阻塞状态,减少了不必要的上下文切换,自旋锁主要适用于线程占有锁得时间短的场景。
三、自旋锁实现
C++11 版不带自旋锁的API,以下为实现示例(基于原子)
#include <atomic>
class spinlock_mutex
{
private:
spinlock_mutex(const spinlock_mutex&) = delete;
spinlock_mutex& operator=(const spinlock_mutex) = delete;
public:
spinlock_mutex() = default;
void lock()
{
while (flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
private:
std::atomic_flag flag;
};
说明:atomic_flag是原子操作的,获得锁后会把此变量置为true,然后另外一个线程就会因为flag=true而一直在while处执行循环,直到获得锁的线程将锁释放(将falg置为false),此时线程的flag.test_and_set()就会返回false,跳出while循环,并把flag置为true。
四、使用示例
#include <chrono>
#include <iostream>
#include <string>
#include <mutex>
void func(std::string str)
{
splock.lock();
//do things
for (int i = 0; i < 100; ++i)
{
std::cout << str << std::endl;
//std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
splock.unlock();
}
int main()
{
std::cout << "程序开始执行" << std::endl;
std::thread t1(func, "thing one");
std::thread t2(func, "thing two");
std::thread t3(func, "thing three");
t1.join();
t2.join();
t3.join();
return 0;
}
说明:传入的func函数字符串一旦被输出,就一定会连续输出100个,即表明对for循环加锁成功。
如有错误或不足欢迎评论指出!创作不易,转载请注明出处。如有帮助,记得点赞关注哦(⊙o⊙)
更多内容请关注个人博客:https://blog.csdn.net/qq_43148810