1、分类
线程之间的锁有:互斥锁、条件锁、自旋锁、读写锁、递归锁。一般而言,锁的功能与性能成反比。不过我们一般不使用递归锁(C++标准库提供了std::recursive_mutex),这里仅介绍前两种锁。
1.1、互斥锁
互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量。也就是说是为了避免多个线程在某一时刻同时操作一个共享资源。例如线程池中的有多个空闲线程和一个任务队列。任何是一个线程都要使用互斥锁互斥访问任务队列,以避免多个线程同时访问任务队列以发生错乱。
在某一时刻,只有一个线程可以获取互斥锁,在释放互斥锁之前其他线程都不能获取该互斥锁。如果其他线程想要获取这个互斥锁,那么这个线程只能以阻塞方式进行等待。
类型:pthread_mutex_t,
函数:
- pthread_mutex_init(pthread_mutex_t * mutex, const phtread_mutexattr_t * mutexattr); //动态方式创建锁,相当于new动态创建一个对象
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //以静态方式创建锁
- pthread_mutex_destory(pthread_mutex_t *mutex) //释放互斥锁,相当于delete
- pthread_mutex_lock(pthread_mutex_t *mutex)
- pthread_mutex_unlock(pthread_mutex_t *mutex)
- int pthread_mutex_trylock(pthread_mutex_t * mutex); //会尝试对mutex加锁。如果mutex之前已经被锁定,返回非0,;如果mutex没有被锁定,则函数返回并锁定mutex;
1.2、条件锁
条件锁就是所谓的条件变量,某一个线程因为某个条件为满足时可以使用条件变量使改程序处于阻塞状态。一旦条件满足以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。最为常见就是在线程池中,起初没有任务时任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒一个线程来处理这个任务。这个过程中就使用到了条件变量pthread_cond_t。
类型:pthread_cond_t
函数:
- pthread_cond_init(pthread_cond_t * condtion, const phtread_condattr_t * condattr); //对条件变量进行动态初始化,相当于new创建对象
- pthread_cond_t condition = PTHREAD_COND_INITIALIZER;//静态初始化条件变量
- pthread_cond_destory(pthread_cond_t * condition); //释放动态申请的条件变量,相当于delete释放对象
- pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex); //该函数以阻塞方式执行。如果某个线程中的程序执行了该函数,那么这个线程就会以阻塞方式等待,直到收到pthread_cond_signal或者pthread_cond_broadcast函数发来的信号而被唤醒。
2、示例
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//init cond
void *thread1(void*);
void *thread2(void*);
int i = 1; //global
int main(void){
pthread_t t_a;
pthread_t t_b;//two thread
pthread_create(&t_a,NULL,thread2,(void*)NULL);
pthread_create(&t_b,NULL,thread1,(void*)NULL);//Create thread
printf("t_a:0x%x, t_b:0x%x", t_a, t_b);
pthread_join(t_b,NULL);//wait a_b thread end
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk){
for(i = 1;i<= 9; i++){
pthread_mutex_lock(&mutex); //互斥锁
printf("call thread1 \n");
if(i%3 == 0)
{
pthread_cond_signal(&cond); //send sianal to t_b
printf("thread1:******i=%d\n", i);
}
else
printf("thread1: %d\n",i);
pthread_mutex_unlock(&mutex);
printf("thread1: sleep i=%d\n", i);
sleep(1);
printf("thread1: sleep i=%d******end\n", i);
}
}
void *thread2(void*junk){
while(i < 9)
{
pthread_mutex_lock(&mutex);
printf("call thread2 \n");
if(i%3 != 0)
pthread_cond_wait(&cond,&mutex); //wait
printf("thread2: %d\n",i);
pthread_mutex_unlock(&mutex);
printf("thread2: sleep i=%d\n", i);
sleep(1);
printf("thread2: sleep i=%d******end\n", i);
}
}
编译执行
[min@bogon:] mutex $ gcc mutex_demo.cpp -o mutex_demo
[min@bogon:] mutex $ ./mutex_demo
t_a:0x242f000, t_b:0x24b2000
call thread1
thread1: 1
thread1: sleep i=1
call thread2
thread1: sleep i=1******end
call thread1
thread1: 2
thread1: sleep i=2
thread1: sleep i=2******end
call thread1
thread1:******i=3
thread1: sleep i=3
thread2: 3
thread2: sleep i=3
thread2: sleep i=3******end
thread1: sleep i=3******end
call thread2
call thread1
thread1: 4
thread1: sleep i=4
thread1: sleep i=4******end
call thread1
thread1: 5
thread1: sleep i=5
thread1: sleep i=5******end
call thread1
thread1:******i=6
thread1: sleep i=6
thread2: 6
thread2: sleep i=6
thread1: sleep i=6******end
call thread1
thread1: 7
thread2: sleep i=7******end
thread1: sleep i=7
call thread2
thread1: sleep i=7******end
call thread1
thread1: 8
thread1: sleep i=8
thread1: sleep i=8******end
call thread1
thread1:******i=9
thread1: sleep i=9
thread2: 9
thread2: sleep i=9
thread2: sleep i=9******end
thread1: sleep i=9******end
3、其他
3.1、std::mutex
C++11 中新增了mutex,用来提升mutex的使用小笼包,就是简单的lock,unlock,如下是std::mutex的使用示例“:
#include <iostream>
#include <map>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>
std::map<std::string, std::string> g_pages;
std::mutex g_pages_mutex;
void save_page(const std::string &url)
{
// simulate a long page fetch
std::this_thread::sleep_for(std::chrono::seconds(2));
std::string result = "fake content";
g_pages_mutex.lock();
g_pages[url] = result;
g_pages_mutex.unlock();
}
int main()
{
std::thread t1(save_page, "http://foo");
std::thread t2(save_page, "http://bar");
t1.join();
t2.join();
g_pages_mutex.lock(); // not necessary as the threads are joined, but good style
for (const auto &pair : g_pages) {
std::cout << pair.first << " => " << pair.second << '\n';
}
g_pages_mutex.unlock();
}
t2.join();
}
3.2、std::mutex与pthread_mutex区别
pthread_mutex初始化时,需要传入参数mutexattr,其包含如下几种,但是std::mutex 只有一种,就是嵌套锁。
- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
3.3、std::lock_guard
简单理解,std::lock_guard是与std::mutex配合使用,把锁放到std::lock_guard中时,std::mutex自动上锁,std::lock_guard析构时,同时把std::mutex解锁。
示例代码:
#include <thread>
#include <mutex>
int g_i = 0;
std::mutex g_i_mutex; // protects g_i
void safe_increment()
{
std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
// g_i_mutex is automatically released when lock
// goes out of scope
}
int main()
{
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
}
std::lock_guard是一个局部变量,创建时,g_i_mutex 上锁,析构时g_i_mutex解锁。这个功能在函数体比较长,尤其是存在多个分支的时候很有用。