Halo
发布于 2022-05-24 / 136 阅读 / 0 评论 / 0 点赞

c++ 同步和锁

volatile

  • volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存
  • volatile 可以保证对特殊地址的稳定访问
  • volatile可以保证每次对变量的访问都是从内存地址直接读取,而不是被编译器优化后可能导致的读错误
  • volatile is for memory where reads and writes should not be optimized away. It’s a tool for working with special memory.
/* Compile code with optimization option */
#include <stdio.h>

int main(void)
{
	const volatile int local = 10; // const int local = 10;
	int *ptr = (int*) &local;

	printf("Initial value of local : %d \n", local);

	*ptr = 100;

	printf("Modified value of local: %d \n", local);

	return 0;
}

debug 和 release 版本下输出都是:

Initial value of local : 10
Modified value of local: 100

如果是: const int local = 10;
debug:

Initial value of local : 10
Modified value of local: 100

release(被优化出问题了):

Initial value of local : 10
Modified value of local: 10

Mutex

  • mutex 互斥量, 实现多线程并发执行时对互斥资源线程安全的访问
  • lll_lock 是mutex 关键宏, 其核心指令是 cmpxchgl ,汇编级别的CAS( compare and swap
#define lll_lock(futex, private) \
  (void)                                      \
    ({ int ignore1, ignore2;                              \
       if (__builtin_constant_p (private) && (private) == LLL_PRIVATE)        \
     __asm __volatile ("cmpxchgl %1, %2\n\t"                   \
               "jnz _L_lock_%=\n\t"                   \
               ".subsection 1\n\t"                    \
               ".type _L_lock_%=,@function\n"             \
               "_L_lock_%=:\n"                    \
               "1:\tleal %2, %%ecx\n"                 \
               "2:\tcall __lll_lock_wait_private\n"           \
               "3:\tjmp 18f\n"                    \
               "4:\t.size _L_lock_%=, 4b-1b\n\t"              \
               ".previous\n"                      \
               LLL_STUB_UNWIND_INFO_3                 \
               "18:"                          \
               : "=a" (ignore1), "=c" (ignore2), "=m" (futex)     \
               : "0" (0), "1" (1), "m" (futex),           \
                 "i" (MULTIPLE_THREADS_OFFSET)            \
               : "memory");                       \

示例

long num = 0;
std::mutex num_mutex;

void numplus() {
    num_mutex.lock();
    for (long i = 0; i < 1000000; ++i) {
        num++;
    }
    num_mutex.unlock();
};

void numsub() {
    num_mutex.lock();
    for (long i = 0; i < 1000000; ++i) {
        num--;
    }
    num_mutex.unlock();
}

int main() {
    std::thread t1(numplus);
    std::thread t2(numsub);
    t1.join();
    t2.join();
    std::cout << num << std::endl;
}

互斥量管理 lock_guard

lock_guard 通常用来管理一个 std::mutex 类型的对象.
在构造函数中自动绑定它的互斥体并加锁,在析构函数中解锁,大大减少了死锁的风险.

  • 创建即加锁,作用域结束自动析构并解锁,无需手工解锁
  • 不能中途解锁,必须等作用域结束才解锁
#include <iostream>
#include <mutex>
#include <thread>

class Widget{
public:
    Widget() = default;
    ~Widget() = default;

    void fun(){
        std::lock_guard<std::mutex> lock(lock_);
        std::cout << "Widget::fun run" << std::endl;
    }

private:
    std::mutex lock_;
};

void TestThread1(Widget* w){
    w->fun();
}

int main()
{
    Widget* w = new Widget();
    std::thread t1(&TestThread1, w);

    t1.join();

    return 0;
}

互斥量管理 unique_guard

除了lock_guard的功能外,提供了更多的member_function,相对来说更灵活一些。如: lock, try_lock, try_lock_for, try_lock_until, unlock

#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <chrono>
 
int main()
{
    std::mutex counter_mutex;
    std::vector<std::thread> threads;
 
    auto worker_task = [&](int id, int wait_seconds, int acquire_seconds) {
        // wait for a few seconds before acquiring lock.
        std::this_thread::sleep_for(std::chrono::seconds(wait_seconds));
 
        std::unique_lock<std::mutex> lock(counter_mutex, std::defer_lock);
        if (lock.try_lock()) {
            std::cout << id << ", lock acquired.\n";
        } else {
            std::cout << id << ", failed acquiring lock.\n";
            return;
        }
 
        // keep the lock for a while.
        std::this_thread::sleep_for(std::chrono::seconds(acquire_seconds));
 
        std::cout << id << ", releasing lock.\n";
        lock.unlock();
    };
 
    threads.emplace_back(worker_task, 0, 0, 2);
    threads.emplace_back(worker_task, 1, 1, 0);
    threads.emplace_back(worker_task, 2, 3, 0);
 
    for (auto &thread : threads) thread.join();
}

std::atomic

  • std::atomic模板类可以使对象操作为原子操作,避免多线程竞争问题
  • 底层实现是, 机器指令的CACHE LOCK 或 BUS LOCKBUS LOCK
  • std::atomic is for data accessed from multiple threads without using mutexes. It’s a tool for writing concurrent software.
class Test
{
public:    
    Test() = default;

    void CThreadFunc()
    {
        for (int i = 0; i < 10000; ++i)
        {
            //std::lock_guard<std::mutex> lck(Test::m_s_ivalue_mutex); //m_iValue需要加锁才可正常工作
            m_iValue++;

            m_atomic_value++;//不加锁,也可正常工作
        }
    }

    void Start()
    {
        std::vector<std::thread> threads;
        for (int i = 0; i < 10; ++i)
        {
            threads.push_back(std::thread(&Test::CThreadFunc, this));
        }

        for (auto& th : threads)
        {
            if (th.joinable())
            {
                th.join();
            }
        }     
        std::cout << "m_iValue:" << m_iValue << ", m_atomic_value:" << m_atomic_value << std::endl;
    }

private:
    int m_iValue = 0;
    std::atomic<int> m_atomic_value = 0;//sta::atomic<T> 原子操作
    static std::mutex m_s_ivalue_mutex;
};

semaphore

  • 是一种轻量的同步原件,用于制约对共享资源的并发访问
  • semaphore 是 mutex(init_MUTEX_LOCKED) 的包装, 也是基于CAS

int sem_init(sem_t *__sem, int __pshared, unsigned int __value)

  • sem :指向信号量对象
  • pshared : 指明信号量的类型。不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。
  • value : 指定信号量值的大小
  1. __value:代表初始化的__sem值。

int sem_post(sem_t *__sem)

__sem的值+1

sem_wait(sem_t* sem)

用来等待信号量的值大于0(value > 0),等待时该线程为阻塞状态
解除阻塞后sem值会减去1

sem_trywait(sem_t *sem)

sem_wait()的非阻塞版本,直接将sem的值减去1

sem_destroy(sem_t* sem)

释放信号量sem

sem_getvalue(sem_t* sem, int* valp)

获取信号量sem的值并且保存在valp中

#include <semaphore.h>
class FooBar
{
private:
	int n;
 
protected:
	sem_t FooReady;
	sem_t BarReady;
 
public:
	FooBar(int n)
	{
		this->n = n;
		sem_init(&FooReady, 0, 0);
		sem_init(&BarReady, 0, 1); //由于先输出foo所以BarReady设置为1
	}
 
	void foo(function<void()> printFoo)
	{
 
		for (int i = 0; i < n; i++)
		{
			sem_wait(&BarReady); //P(BarReady)
			printFoo();
			sem_post(&FooReady); //V(FooReady)
		}
	}
 
	void bar(function<void()> printBar)
	{
		for (int i = 0; i < n; i++)
		{
			sem_wait(&FooReady); //P(FooReady)
			printBar();
			sem_post(&BarReady); //V(BarReady)
		}
	}
};

评论