C/C++ — 线程锁

C/C++ — 线程锁

锁的分类比较

1、单线程无锁速度最快,应用场合局限性很大。

2、多线程无锁速度第二快,但是同时操作同一块内存空间,会造成数据污染或者奔溃。

3、多线程原子锁第三快,并且结果不会被污染。

4、多线程**互斥锁(量)**比较慢,与原子锁相比较相差10倍,结果不会被污染。

5、多线程自旋锁最慢,是原子锁的三十分之一,结果正确

所以: 原子锁速度最快,互斥锁和自旋锁相对比较慢但是数据都可以保证。

自旋锁

非阻塞锁,就是Objective-C中的原子性,同一时间只允许一个线程写入。如果该锁已经被占用,则不会被挂起。一致消耗CPU,不停的试图获取锁。

互斥锁(量)

阻塞锁,就是Objective-C中的非原子性。某个线程无法获取互斥锁时,该线程直接挂起,不在消耗资源,当线程释放互斥量之后,操作系统会激活被挂起的线程。

原子锁

atomic原子操作:是在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等

测试代码:

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <time.h>

#define TEST_DATA_LENGTH 100000 //每个线程操作次数
#define THREAD_NUM 10 //线程个数

using namespace std;//引入std命名空间

mutex m;//声明互斥锁m

long n_total = 0;
long m_total = 0;
atomic<long> a_total = 0;//原子量的初始化

//在不采用互斥锁和原子类的情况下
void test_f_normal()
{
    for(int i = 0; i < TEST_DATA_LENGTH; i++)
    {
        n_total += 1;
    }
}

//使用mutex互斥锁
void test_f_mutex()
{
    for(int i = 0; i < TEST_DATA_LENGTH; i++)
    {
        m.lock();
        m_total += 1;
        m.unlock();
    }
}
//使用原子操作
void test_f_atomic()
{
    for(int i = 0; i < TEST_DATA_LENGTH; i++)
    {
        a_total += 1;
    }
}

void main()
{
    thread ts[THREAD_NUM];
    //normal mode ,result is error
    clock_t start = clock();

    for(int i = 0; i < THREAD_NUM; i++)
    {
        ts[i] = thread(&test_f_normal);
    }

    for(int i = 0; i < THREAD_NUM; i++)
    {
        ts[i].join();
    }

    clock_t end = clock();
    cout << "total: " << n_total << endl;
    cout << "test_f_normal: " << end - start << endl;


    //use mutex ,
    clock_t mstart = clock();

    for(int i = 0; i < THREAD_NUM; i++)
    {
        ts[i] = thread(&test_f_mutex);
    }

    for(int i = 0; i < THREAD_NUM; i++)
    {
        ts[i].join();
    }

    clock_t mend = clock();
    cout << "total: " << m_total << endl;
    cout << "test_f_mutex: " << mend - mstart << endl;

    //use atomic
    clock_t astart = clock();

    for(int i = 0; i < THREAD_NUM; i++)
    {
        ts[i] = thread(&test_f_atomic);
    }

    for(int i = 0; i < THREAD_NUM; i++)
    {
        ts[i].join();
    }

    clock_t aend = clock();
    cout << "total: " << a_total << endl;
    cout << "test_f_atomic: " << aend - astart << endl;

    system("pause");
    return;
}


// 结果
total: 601409
test_f_normal: 29
total: 1000000
test_f_mutex: 11274
total: 1000000
test_f_atomic: 35

总结:
1.在不使用互斥锁和原子量的时候,多线程的操作会使结果是错误的.
2.原子操作的实现跟普通数据类型类似,但是它能够在保证结果正确的前提下,提供比mutex等锁机制更好的性能

提示:开发过程中,对于多线程的情况下,单个基础数据类型的数据共享安全,尽量使用原子操作代替锁机制. 当需要对代码块进行数据安全保护的时候,就需要选择使用锁机制或者自旋锁了。

扩展
Interlocked原子访问

在Windows核心编程中,可以通过Interlocked系列函数实现原子方式控制一个值。

Interlocked函数的工作原理取决于代码运行的CPU平台,如果是x86系列CPU,那么Interlocked函数会在总线上维持一个硬件信号,这个信号会阻止其他CPU访问同一个内存地址。([文档]

LONG InterlockedExchange(
  LPLONG Target,
  LONG Value
);

InterlockedExchange (a,b) 能以原子的方式交换两个参数a、b并返回a以前的值。因为InterlockedExchange是原子函数,不会要求中止中断,所以交换指针的方式是安全的。

假设有线程1和线程2调用func()函数,线程1先调用InterlockedExchange(&g, TRUE);,线程2在调用时,函数InterlockedExchange()总会返回TRUE ,则线程2 sleep,而直到线程1调用InterlockedExchange(&g, FALSE);后线程2才可能由于调用InterlockedExchange(&g, TRUE),返回FALSE而退出循环接着工作。这样在进行其他的操作这里就能操作共享数据而不会引起争议。当然这种方法会浪费CPU时间,因为CPU要不断地执行InterlockedExchange()函数,使用时应注意。

// 线程1
InterlockedExchange(&g, TRUE);				// 步一
//..
InterlockedExchange(&g, FALSE);				// 步三
 
 
//线程2
while(TRUE == InterlockedExchange(&g, TRUE))// 步二
{
	sleep(0);
	// 等待
}
 
// 线程2跳出循环了							 // 步四

pthread_mutex_t

利用线程的原子锁实现原子访问

#include <pthread.h>
#include <stdio.h>
#include <string>

class CLocker
{
    pthread_mutex_t mutex;

    bool m_bLog;
public:

    CLocker(){
        pthread_mutex_init (&mutex,NULL);
        m_bLog = false;
    }

    void lock(){
        pthread_mutex_lock(&mutex);
    }

    void lock(std::string strFuncName){
        if (m_bLog == true)
             printf("[LOCK]: %s \n",strFuncName.c_str());

        pthread_mutex_lock(&mutex);
    }

    bool trylock(){
       return (pthread_mutex_trylock(&mutex) == 0)?true:false;
    }

    bool trylock(std::string strFuncName){
        if (m_bLog == true)
            printf("[TRYLOCK]: %s \n",strFuncName.c_str());
        return (pthread_mutex_trylock(&mutex) == 0)?true:false;
    }

    void unlock(std::string strFuncName){
        if (m_bLog == true)
            printf("[UNLOCK]: %s \n",strFuncName.c_str());
        pthread_mutex_unlock(&mutex);
    }

    void unlock(){
        pthread_mutex_unlock(&mutex);
    }

    void enablelog(bool bLog){
        m_bLog = bLog;
    }
};
无锁化编程

__sync_fetch_and_or

__sync_fetch_and_and

我们知道,count++这种操作不是原子的。一个自加操作,本质是分成三步的:
1、 从缓存取到寄存器。

2、 在寄存器加1。

3、 存入缓存。

__sync_fetch_and_add系列一共有十二个函数,有加/减/与/或/异或/等函数的原子性操作函数,

__snyc_fetch_and_add : 先fetch然后自加,返回的是自加以前的值
__snyc_add_and_fetch : 先自加然后返回,返回的是自加以后的值 (参照 ++i 和 i++)


__snyc_fetch_and_add的一个简单使用
int count = 4;
__sync_fetch_and_add(&count, 1); // __sync_fetch_and_add(&count, 1) == 4
cout<<count<<endl; //--->count=5

​ 对于多线程对全局变量进行自加,我们就再也不用理线程锁了。

下面这行代码,和上面被pthread_mutex保护的那行代码作用是一样的,而且也是线程安全的。

__sync_fetch_and_add( &global_int, 1 );

__sync_fetch_and_add,速度是线程锁的6~7倍

函数集

//在用gcc编译的时候要加上选项 -march=i686
type __sync_fetch_and_add (type *ptr, type value, ...);
type __sync_fetch_and_sub (type *ptr, type value, ...);
type __sync_fetch_and_or (type *ptr, type value, ...);
type __sync_fetch_and_and (type *ptr, type value, ...);
type __sync_fetch_and_xor (type *ptr, type value, ...);
type __sync_fetch_and_nand (type *ptr, type value, ...);
type __sync_add_and_fetch (type *ptr, type value, ...);
type __sync_sub_and_fetch (type *ptr, type value, ...);
type __sync_or_and_fetch (type *ptr, type value, ...);
type __sync_and_and_fetch (type *ptr, type value, ...);
type __sync_xor_and_fetch (type *ptr, type value, ...);
type __sync_nand_and_fetch (type *ptr, type value, ...);

type可以是1,2,3或者8字节长度的int类型,即

int8_t    
uint8_t

int16_t
uint16_t

int32_t
uint32_t

int64_t
uint64_t

除了上面提到的12个外 还有4个可以实现互斥锁的功能

//以下两个函数提供原子的比较和交换, 如果*ptr = oldValue, 就将newValue写入*ptr
//第一个函数在相等并写入的情况下返回true
//第二个函数返回操作之前的值

bool __sync_bool_compare_and_swap(type* ptr, type oldValue, type newValue, ....);

type __sync_val_compare_and_swap(type* ptr, type oldValue, type newValue, ....);

//将*ptr设为value并返回*ptr操作之前的值
type __sync_lock_test_and_set(type *ptr, type value, ....);

//置*ptr为0
void __sync_lock_release(type* ptr, ....);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值