线程同步概念及应用
一、线程同步的概念
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程处于等待状态。
二、Linux线程同步对象
2.1 Linux互斥锁(pthread_mutex_t)
对于两个或多个线程可能会同时读或写的变量应该使用互斥量进行保护。
2.2 Linux自旋锁(pthread_spinlock_t)
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,直到其他线程释放锁,才能继续往下执行。
自旋锁在等待的过程中,会不停地检测锁是否被释放,这会占用CPU资源。自旋锁不会引起线程切换(空转CPU);互斥锁在等待期间不会占用资源,但“请求加锁”和“加锁”(唤醒)操作会有较大开销。互斥锁可能引起线程切换。选用自旋锁还是互斥锁评估两者开销决定。
2.3 Linux信号量(sem_t)
它与互斥体(pthread_mutex_t)相似,但是有区别。一个互斥量只能被锁定一次,而信号量可以多次使用。信号量通常用来保护一定数量的相同资源,如数据采集时的双缓冲区。
2.4 Linux条件变量(pthread_cond_t)
条件变量一般用于“生产者/消费者”(producer/consumer)模型中。搭配互斥体或者信号量使用。生产者(producer)生产一定数量后,通过条件变量通知阻塞中的一定数量的消费者(consumer)进行消费。
2.5 Linux读写锁(pthread_rwlock_t)
对互斥体(pthread_mutex_t)进行优化,针对某一个变量加锁的策略优化。
- 读锁用于共享模式:如果当前读写锁已经被某线程以读模式占用,则其他线程调用pthread_rwlock_rdlock(请求读锁)时会立刻获得读锁;如果当前读写锁已经被某线程以读模式占用,则其他线程调pthread_rwlock_wrlock(请求写锁)时会陷入阻塞。
- 写锁用于独占模式:如果当前读写锁被某线程以写模式占用,则无论是调用pthread_rwlock_rdlock还是调用pthread_rwlock_wrlock,都会陷入阻塞。即在写模式下不允许任何读锁请求通过,也不允许任何写锁请求通过,读锁请求和写锁请求都要陷入阻塞中,直到线程释放写锁。
三、Linux线程同步应用
3.1 信号量应用
作用:
- 唤醒线程
- 休眠线程
semaphore.cpp:
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <list>
#include <semaphore.h>
#include <iostream>
class Task
{
public:
Task(int taskID)
{
this->taskID = taskID;
}
void doTask()
{
std::cout << "handle a task, taskID: " << taskID << ", threadID: " << pthread_self() << std::endl;
}
private:
int taskID;
};
pthread_mutex_t mymutex; //互斥锁
std::list<Task*> tasks;
sem_t mysemaphore; //信号量
void print_semaphore_value(int type)
{
int semaphore_value = -1;
sem_getvalue(&mysemaphore, &semaphore_value);
if (type == 0)
std::cout << "!!! producer_thread semaphore_value: " << semaphore_value << std::endl;
else
std::cout << "!!! consumer_thread semaphore_value: " << semaphore_value << std::endl;
}
void* consumer_thread(void* param)
{
Task* pTask = NULL;
while(true)
{
//打印信号量的值
print_semaphore_value(1);
//等待信号量
if (sem_wait(&mysemaphore) != 0) //将信号量对象进行解锁,将信号量的资源计数减少1。如果信号量的值为0将阻塞在此,直到信号量的值大于0,减1后继续往下执行。
continue;
if (tasks.empty())
continue;
//锁住互斥锁
pthread_mutex_lock(&mymutex);
pTask = tasks.front();
tasks.pop_front();
//解锁互斥锁
pthread_mutex_unlock(&mymutex);
if(pTask == NULL)
continue;
pTask->doTask();
delete pTask;
pTask = NULL;
}
return NULL;
}
void* producer_thread(void* param)
{
int taskID = 0;
Task* pTask = NULL;
while(true)
{
pTask = new Task(taskID);
//锁住互斥锁
pthread_mutex_lock(&mymutex);
tasks.push_back(pTask);
std::cout << "produce a task, taskID: " << taskID << ", threadID: " << pthread_self() << std::endl;
//解锁互斥锁
pthread_mutex_unlock(&mymutex);
//释放信号量,通知消费者线程
sem_post(&mysemaphore); //将信号量对象进行加锁,将信号量的资源计数增加1
//打印信号量的值
print_semaphore_value(0);
taskID++;
sleep(1);
}
return NULL;
}
int main()
{
//初始化互斥锁
pthread_mutex_init(&mymutex, NULL);
//初始化信号量的计数为0
sem_init(&mysemaphore, 0, 0);
//创建5个消费者线程
pthread_t consumerThreadID[5];
for(int i = 0; i < 5; ++i)
pthread_create(&consumerThreadID[i], NULL, consumer_thread, NULL);
//创建1个生产者线程
pthread_t producerThreadID;
pthread_create(&producerThreadID, NULL, producer_thread, NULL);
//回收子线程
pthread_join(producerThreadID, NULL);
for(int i = 0; i < 5; ++i)
pthread_join(consumerThreadID[i], NULL);
//删除信号量
sem_destroy(&mysemaphore);
//删除互斥锁
pthread_mutex_destroy(&mymutex);
return 0;
}
在以上代码创建了1个生产者和5个消费者线程,初始信号量计数为0代表一开始没有可执行任务,所以5个消费者线程均被阻塞在sem_wait调用处。接着生产者每隔1秒产生1个任务,然后通过调用sem_post将信号量的资源计数增加1,此时1个线程会被唤醒,我们从任务队列中取出任务并执行,由于任务对象是新建的,所以我们需要删掉以免内存泄漏。
在调用sem_wait和sem_post时会对信号量对象进行加锁和解锁,为什么这里还需要使用一个互斥体呢?其实,这个互斥体是用来保护队列tasks的,因为多个线程会同时读写它。
编译:
g++ -g -o semaphore semaphore.cpp -lpthread
执行:
./semaphore
运行结果:
produce a task, taskID: 0, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
!!! consumer_thread semaphore_value: 1
handle a task, taskID: 0, threadID: 140254354978560
!!! consumer_thread semaphore_value: 0
!!! consumer_thread semaphore_value: 0
!!! consumer_thread semaphore_value: 0
!!! consumer_thread semaphore_value: 0
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 1, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 1, threadID: 140254354978560
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 2, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 2, threadID: 140254363371264
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 3, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 3, threadID: 140254371763968
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 4, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 4, threadID: 140254380156672
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 5, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 5, threadID: 140254388549376
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 6, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 6, threadID: 140254354978560
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 7, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 7, threadID: 140254363371264
!!! consumer_thread semaphore_value: 0
produce a task, taskID: 8, threadID: 140254346585856
!!! producer_thread semaphore_value: 1
handle a task, taskID: 8, threadID: 140254371763968
!!! consumer_thread semaphore_value: 0
3.2 条件变量应用
条件变量需和互斥体一起配合使用
作用:
- 唤醒线程
- 休眠线程
cond.cpp:
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <list>
#include <semaphore.h>
#include <iostream>
class Task
{
public:
Task(int taskID)
{
this->taskID = taskID;
}
void doTask()
{
std::cout << "handle a task, taskID: " << taskID << ", threadID: " << pthread_self() << std::endl;
}
private:
int taskID;
};
pthread_mutex_t mymutex; //互斥锁
std::list<Task*> tasks;
pthread_cond_t mycv; //条件变量
void* consumer_thread(void* param)
{
Task* pTask = NULL;
while(true)
{
//锁住互斥锁
pthread_mutex_lock(&mymutex);
while(tasks.empty())
{
//如果获得了互斥锁,但是条件不满足,则pthread_cond_wait会释放锁,不往下执行
//发生变化后,如果条件合适,则pthread_cond_wait将直接获得锁
pthread_cond_wait(&mycv, &mymutex);
}
pTask = tasks.front();
tasks.pop_front();
//解锁互斥锁
pthread_mutex_unlock(&mymutex);
if(pTask == NULL)
continue;
pTask->doTask();
delete pTask;
pTask = NULL;
}
return NULL;
}
void* producer_thread(void* param)
{
int taskID = 0;
Task* pTask = NULL;
while(true)
{
pTask = new Task(taskID);
//锁住互斥锁
pthread_mutex_lock(&mymutex);
tasks.push_back(pTask);
std::cout << "produce a task, taskID: " << taskID << ", threadID: " << pthread_self() << std::endl;
//解锁互斥锁
pthread_mutex_unlock(&mymutex);
//释放信号量,通知消费者线程
pthread_cond_signal(&mycv);
taskID++;
sleep(1);
}
return NULL;
}
int main()
{
//初始化互斥锁
pthread_mutex_init(&mymutex, NULL);
//初始化条件变量
pthread_cond_init(&mycv, NULL);
//创建5个消费者线程
pthread_t consumerThreadID[5];
for(int i = 0; i < 5; ++i)
pthread_create(&consumerThreadID[i], NULL, consumer_thread, NULL);
//创建1个生产者线程
pthread_t producerThreadID;
pthread_create(&producerThreadID, NULL, producer_thread, NULL);
//回收子线程
pthread_join(producerThreadID, NULL);
for(int i = 0; i < 5; ++i)
pthread_join(consumerThreadID[i], NULL);
//删除条件变量
pthread_cond_destroy(&mycv);
//删除互斥锁
pthread_mutex_destroy(&mymutex);
return 0;
}
编译:
g++ -g -o cond cond.cpp -lpthread
执行:
./cond
运行结果:
produce a task, taskID: 0, threadID: 140019888551680
handle a task, taskID: 0, threadID: 140019896944384
produce a task, taskID: 1, threadID: 140019888551680
handle a task, taskID: 1, threadID: 140019896944384
produce a task, taskID: 2, threadID: 140019888551680
handle a task, taskID: 2, threadID: 140019905337088
produce a task, taskID: 3, threadID: 140019888551680
handle a task, taskID: 3, threadID: 140019913729792
produce a task, taskID: 4, threadID: 140019888551680
handle a task, taskID: 4, threadID: 140019922122496
produce a task, taskID: 5, threadID: 140019888551680
handle a task, taskID: 5, threadID: 140019930515200
produce a task, taskID: 6, threadID: 140019888551680
handle a task, taskID: 6, threadID: 140019896944384
produce a task, taskID: 7, threadID: 140019888551680
handle a task, taskID: 7, threadID: 140019905337088
produce a task, taskID: 8, threadID: 140019888551680
handle a task, taskID: 8, threadID: 140019913729792
3.3 读写锁应用
应用场景:如果某个变量会被多个线程访问,并且读的需求比写的需求多的时候,可以使用读写锁。如果这个变量没有任何写需求(修改变量),那么可以完全不使用锁。
rwlock.cpp:
#include <pthread.h>
#include <unistd.h>
#include <iostream>
int resourceID = 0;
pthread_rwlock_t myrwlock; //读写锁
void* read_thread(void *param)
{
while(true)
{
//请求读锁
pthread_rwlock_rdlock(&myrwlock);
std::cout << "read thread ID:" << pthread_self() << ", resourceID: " << resourceID << std::endl;
//使用随眠模式模拟读线程读的过程花了很长时间
sleep(1);
//释放读写锁
pthread_rwlock_unlock(&myrwlock);
}
return NULL;
}
void* write_thread(void *param)
{
while(true)
{
//请求写锁
pthread_rwlock_wrlock(&myrwlock);
++resourceID;
std::cout << "write thread ID:" << pthread_self() << ", resourcesID: " << resourceID << std::endl;
//释放读写锁
pthread_rwlock_unlock(&myrwlock);
//使用睡眠模式模拟写线程写过程花了很多时间
//放在这里,增加请求读锁线程获得锁的概率
sleep(1);
}
}
int main()
{
//初始化读写锁属性
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
//设置成请求写锁优先
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
pthread_rwlock_init(&myrwlock, &attr);
//创建5个请求读锁线程
pthread_t readThreadID[5];
for(int i = 0; i < 5; ++i)
{
pthread_create(&readThreadID[i], NULL, read_thread, NULL);
}
//创建一个请求写锁线程
pthread_t writeThreadID;
pthread_create(&writeThreadID, NULL, write_thread, NULL);
//回收写线程
pthread_join(writeThreadID, NULL);
//回收读线程
for(int i = 0; i < 5; ++i)
pthread_join(readThreadID[i], NULL);
//销毁读写锁变量
pthread_rwlock_destroy(&myrwlock);
return 0;
}
编译:
g++ -g -o rwlock rwlock.cpp -lphread
执行:
./rwlock
运行结果:
write thread ID:140224143787776, resourcesID: 1
read thread ID:140224152180480, resourceID: 1
read thread ID:140224160573184, resourceID: 1
read thread ID:140224168965888, resourceID: 1
read thread ID:140224177358592, resourceID: 1
read thread ID:140224185751296, resourceID: 1
read thread ID:140224152180480, resourceID: 1
write thread ID:140224143787776, resourcesID: 2
read thread ID:140224160573184, resourceID: 2
read thread ID:140224152180480, resourceID: 2
read thread ID:140224168965888, resourceID: 2
read thread ID:140224177358592, resourceID: 2
read thread ID:140224185751296, resourceID: 2
read thread ID:140224160573184, resourceID: 2
read thread ID:140224152180480, resourceID: 2
read thread ID:140224168965888, resourceID: 2
write thread ID:140224143787776, resourcesID: 3
read thread ID:140224160573184, resourceID: 3
read thread ID:140224168965888, resourceID: 3
read thread ID:140224152180480, resourceID: 3
read thread ID:140224177358592, resourceID: 3
read thread ID:140224185751296, resourceID: 3
read thread ID:140224160573184, resourceID: 3
read thread ID:140224168965888, resourceID: 3
read thread ID:140224152180480, resourceID: 3