C11线程管理:互斥锁

1、概述

  锁类型

  c11提供了跨平台的线程同步手段,用来保护多线程同时访问的共享数据。
  std::mutex,最基本的 Mutex 类,独占的互斥量,不能递归使用。
  std::time_mutex,带超时的独占互斥量,不能递归使用。
  std::recursive_mutex,递归互斥量,不带超时功能。
  std::recursive_timed_mutex,带超时的递归互斥量。

  lock类型

  std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
  std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

  锁函数

  std::try_lock,尝试同时对多个互斥量上锁。
  std::lock,可以同时对多个互斥量上锁。
  std::unlock,解锁。

2、独占互斥量std::mutex

  互斥量使用都很简单,接口用法也很简单。一般是通过lock()来阻塞线程,直到获取到互斥量为止。在获取互斥量完成之后,使用unlock()来解除互斥量的占用。lock()和unlock()必须成对出现。

  std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。

  lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

  unlock(), 解锁,释放对互斥量的所有权。

  try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>

std::mutex g_lock;

void vFunc()
{
    g_lock.lock();

    std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;

    g_lock.unlock();
}

int main()
{
    std::thread t(vFunc);
    std::thread t1(vFunc);
    std::thread t2(vFunc);

    t.join();
    t1.join();
    t2.join();

    return 0;
}
//输出结果
entered thread:4420
leave thread:4420
entered thread:5288
leave thread:5288
entered thread:1432
leave thread:1432

3、std::lock_guard和std::unique_lock

  使用lock_guard和std::unique_lock可以简化lock/unlock的写法,同时也更安全,因为lock_guard使用了RAII技术,在构造分配资源,在析构释放资源,会在构造的时候自动锁定互斥量,在退出作用域之后进行析构自动解锁。所以不用担心没有解锁的情况,更加安全。

  std::lock_guard与 Mutex RAII 相关,方便线程对互斥量上锁,std::unique_lock与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制,它可以自由的释放mutex,而std::lock_guard需要等生命周期结束才能释放。

void vFunc()
{
    std::lock_guard<std::mutex> locker(g_lock);//出作用域自动解锁
    std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;
}

3、递归互斥锁std::recuisive_mutex

  同一个线程不能多次获取同一个独占互斥锁,一个线程多次获取同一个互斥锁发生死锁。

//std::mutex mutex;

void funcA()
{
    std::lock_guard<std::mutex> lock(mutex);
    //do something
}

void funcB()
{
    std::lock_guard<std::mutex> lock(mutex);
    //do something
}

void funcC()
{
    std::lock_guard<std::mutex> lock(mutex);
    funcA();
    funcB();
}

int main()
{
    //发生死锁,funcC已经获取,无法释放,funcA无法获取
    funcC(); 

    return 0;
}

  为了解决死锁的问题,C11有了递归锁std::recursive_mutex,递归锁允许一个线程多次获取该锁。

//std::recursive_mutex mutex;

void funcA()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    //do something
}

void funcB()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    //do something
}

void funcC()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    funcA();
    funcB();
}

int main()
{
    //同一个线程可以多次获取同一互斥量,不会发生死锁
    funcC(); 

    return 0;
}

  需要注意的是,尽量少用递归锁,原因如下:

  允许递归互斥容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题;
  递归锁比非递归锁效率低;
  递归锁虽然可以在同一线程多次获取,但是获取次数过多容易发生问题,引发std::system错误。

4、超时锁

  std::time_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用于在获取锁时增加超时等待功能,设置一个等待获取锁的时间,超时后做其他的事情。超时锁多了两个获取锁的接口,try_lock_for和try_lock_until,这两个接口用来获取互斥量的超时时间。

  try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

  try_lock_until函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex

std::timed_mutex mtx;

void fireworks() {
    // waiting to get a lock: each thread prints "-" every 200ms:
    while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
        std::cout << "-";
    }
    // got a lock! - wait for 1s, then this thread prints "*"
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::cout << "*\n";
    mtx.unlock();
}

int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i<10; ++i)
        threads[i] = std::thread(fireworks);

    for (auto& th : threads) th.join();

    return 0;
}
时间: 2024-07-31 07:10:36

C11线程管理:互斥锁的相关文章

线程的互斥锁和条件变量通信机制

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #include <pthread.h> 5 6 #define BUFFER_SIZE 2 7 struct prodcons 8 { 9 int buffer[BUFFER_SIZE]; 10 pthread_mutex_t lock; 11 int readpos,writepos; 12 pthread_cond_t no

Linux线程同步---互斥锁

线程中互斥锁使用的步骤与信号量相似! 1.首先定义互斥锁变量,并初始化 pthread_mutex_t mutex_lock;pthread_mutex_init(&mutex_lock,NULL);2.在操作前对互斥量进行加锁操作 pthread_mutex_lock(&mutex_lock);3.操作完毕后进行解锁操作 pthread_mutex_unlock(&mutex_lock); 所有操作均在加锁和解锁操作之间进行,保证同时仅仅对有一个操作对关键变量或是区域进行操作.

C11线程管理:条件变量

1.简介 C11提供另外一种用于等待的同步机制,它可以阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程.条件变量要和互斥量配合起来使用. condition_variable,配合std::unique_lock<std::mutex>进行wait操作. condition_variable_any,和任意带有lock.unlock语意 的mutex搭配使用,比较灵活,但是效率比condition_variable低. 条件变量的使用过程如下: a.拥有条件

喜羊羊系列之【 线程 】互斥锁+信号量

1. 编写一个基本的多线程程序(主线程里面创建一个子线程) /************************************************************************* > File Name: 1_homework.c > Author: 梁惠涌 > Addr: > Created Time: 2015年04月22日 星期三 15时22分34秒 ************************************************

转发 :java线程:互斥锁与读写锁

原文链接:http://coolxing.iteye.com/blog/1236909 两种互斥锁机制: 1.synchronized 2.ReentrantLock ReentrantLock是jdk5的新特性,采用ReentrantLock可以完全替代替换synchronized传统的锁机制,而且采用ReentrantLock的方式更加面向对象,也更加灵活,网上有很多关于对比两者锁方式的文章,这里就不多口舌了,大家baidu.google一下就水落石出了.在本博客中也写关于这两种锁方式实现的

Linux 线程编程2.0——线程同步-互斥锁

当我们需要控制对共享资源的存取的时候,可以用一种简单的加锁的方法来控制.我们可以创建一个读/写程序,它们共用一个共享缓冲区,使用互斥锁来控制对缓冲区的存取. 函数 pthread_mutex_init()用来生成一个互斥锁.其函数原型如下: #include<pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr): 第一个参数是互斥变量

线程的互斥锁

一.竞争与同步 当多个线程同时访问其所共享的进程资源时,需要相互协调,以防止出现数据不一致.不完整的问题.这就叫线程同步. 二.互斥量 int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr); 功能:初始化互斥量 //亦可 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;? int pthread_mutex_lock (pthre

C11线程管理:原子变量&amp;单调函数

1.原子变量 C++11提供了原子类型std::atomic<T>,可以使用任意类型作为模板参数,使用原子变量就不需要使用互斥量来保护该变量,用起来更加简洁. 举个例子,如果要做一个计数器,使用mutex时和使用原子变量依次如下: //使用mutex struct Counter { int value; std::mutex mutex; void increment() { std::lock_guard<std::mutex> lock(mutex); ++value; }

第9章 线程编程(4)_线程同步1:互斥锁

5. 线程的互斥和同步 5.1 同步和互斥的概念 (1)线程同步:是一个宏观概念,在微观上包含线程的相互排斥和线程的先后执行的约束问题.解决同步方式一般采用条件变量和信号量. (2)线程互斥:线程执行的相互排斥(注意,它不关心线程间执行的先后顺序!).解决互斥一般使用互斥锁.读写锁和信号量. [编程实验]银行ATM(线程不安全的例子) //account.h #ifndef __ACCOUNT_H__ #define __ACCOUNT_H__ typedef struct { int code