一、线程概念:是进程内的一个执行分支,线程的执行粒度要比进程要细。
1、Linux中线程该如何理解:
①在Linux中,线程在进程“内部”执行,线程在进程的地址空间中进行。任何执行流要执行,都要有资源,而地址空间是进程的资源窗口。
②在linux中,线程的执行力度要比进程更细,线程执行进程代码的一部分。
③CPU只有调度执行流的概念。线程是CPU调度的基本单元,而进程是承担系统资源的基本实体。
2、重新定义线程和进程:
①什么叫做线程?我们认为线程操作系统调度的基本单位。
②进程=内核数据结构task_struct+代码和数据---v1
③重新理解进程?内核观点:进程是承担分配系统资源的基本实体。线程是进程内部的执行流资源。
④操作系统以进程为单位,给我们分配资源,只不过(讲信号之前所有的)进程内部只有一个执行流。而今天包含了很多执行流。(所以我们以前学习的才是进程的特殊情况,现在学习的是进程的常规情况)
⑤struct tcb(Thread control block)
复用进程数据结构和管理算法,用struct task_struct---模拟线程。
所以Linux没有真正意义上的线程,而是用进程的内核数据结构模拟的线程(非常卓越的设计)。
(思考:为什么不能用继承和多态直接实现?)
所以CPU无法区分线程和进程(线程<=执行流<=进程)。
一般将Linux中的执行流称为轻量级进程。
3、重谈地址空间第四讲:
①从物理内存读到CPU中的地址是虚拟地址。
②以32位虚拟地址为例,谈谈虚拟地址是如何转换到物理地址的:32=10+10+12。
一级页表(页目录)存储二级列表地址(页目录表项);
二级页表存储物理内存中页框的起始地址(页表表象);
后12位正好是页框的大小,页框对应搜索时具体的偏移量。
页框的物理地址加上虚拟地址的最后12位(访问物理内存中在页框中的偏移量)=物理地址
(二级页表大部分情况都是不全的。但是创建一个进程依旧是一个很重的工作。)
③C语言中任何一个类型取地址,只有一个地址。(思考:内存对齐)
CPU读取指令本身就包含要读取几个字节。
C语言任何变量都只有一个地址,就是它开辟对象众多字节中的一个起始地址,只要找到这个对象的起始地址,CPU天然就能识别出需要识别几个字节(而高级语言在编译完后就没有类的概念了,归根结底也是内置类型的集合)。
起始地址+类型=起始地址+偏移量(x86CPU的特点:段地址+偏移量)
④一个页目录的二级页表可以残缺甚至没有,但是必须要有页目录。
cr2寄存器:存储越界或者缺页中断异常的虚拟地址。
⑤如何理解资源分配多个线程?
线程分配资源的本质就是分配地址空间范围。
4. Linux线程周边的概念:
①线程vs进程:
线程比进程要更轻量化(为什么:整个生命周期都轻量化)。
a. 创建和释放更加轻量化(生死问题)。
b. 切换更加轻量化(运行问题)。
线程在执行,本质是进程在调度,因为线程是进程的执行分支。
CPU内部还有一个硬件级别的缓存cache(进程运行时/缓存的热数据:被高频访问的数据)。
线程内切换不需要重新cache数据,而进程切换时,cache数据要由冷变热重新缓存。所以线程效率更高。
②线程需要独立的栈结构,独立上下文能够体现线程是被独立调度的,独立的栈结构能体现进程之间运行是不会出现执行流错乱的问题。
面试常问:线程哪部分是独占的?答:线程上下文和栈,errno信号屏蔽字,调度优先级。
线程哪部分是共享的?答:代码、数据、文件描述符表。
③刚启动的线程称为主线程,进程的多个线程共享同一地址空间。
除了主进程,所有其他进程的独立栈,都在共享区具体来讲是在pthread库中的tid指向的用户tcb中。
④内核中没有很明确的线程的概念,不会直接提供现成的系统调用,只会提供轻量级进程的系统调用。于是在用户和系统之间,linux程序员在应用层开发出了pthread线程库(将轻量级进程接口进行封装)。为用户提供直接线程的接口(几乎所有的linux平台都是默认自带这个库)(Linux中编写多线程代码需要使用第三方pthread库)。
二、线程控制(接口):
1、创建一个线程:
①输出型参数(输出thread id),②线程的属性(一般置为nullptr),③函数指针(让新线程执行传入的回调函数), ④创建线程成功,新线程回调线程函数的时候,需要参数(这个参数就是给线程函数传递的)。失败返回错误码。
2、等待一个终止的线程:
3、 终止线程:
4、取消线程:
5、提供轻量级进程系统调用(不过该接口已被pthread线程库封装了) :
6、线程分离:pthread_detach
7、ps -aL查看当前用户所有启动的轻量级进程。
LWP(Lightweight Process Id):轻量级进程pid(若PID==LWP,则为主线程)。
8、、任何一个线程被kill掉了,整个进程都被kill掉。在这里,kill信号的发送对象为进程(所以线程的健壮性很差)。
9、示例:(使用操作系统的原生接口。代码不具备可移植性。)
①Linux的多线程:
mythread:mythread.cc
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -f mythread
#include
#include
#include
#include
#include
using namespace std;
int g_val = 100;
//可以被多个执行流同时调度执行(show函数被重入了)
void show(const string &name)
{
cout << name << "say# " << "hello thread" << endl;
}
//new thread
void* threadRoutine(void *args)
{
const char *name = (const char*)args;
int cnt = 5;
while(true)
{
printf("%s, pid: %d, g_val: %d, &g_val: 0x%p\n", name, getpid(), g_val, &g_val);
//cout << "new thread, pid: " << getpid() << endl;
//show("[new thread]");
sleep(1);
cnt--;
if(cnt == 0) break;
}
// exit(11); // exit是用来终止进程的,不能终止线程。
pthread_exit((void*)100);
return (void*)1; //此处默认线程退出了
}
int main()
{
PTHREAD_CANCELED; // -1
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void*)"Thread 1"); // 不是系统调用
sleep(1);
pthread_cancel(tid);
// while(true)
// {
// printf("main thread pid: %d, g_val: %d, &g_val: 0x%p, create new thread tid: %p\n", getpid(), g_val, &g_val, tid);
// //show("[main thread]");
// sleep(1);
// g_val++;
// }
void *retval;
pthread_join(tid, &retval); // main thread等待的时候,默认是阻塞等待的。
//为什么此处join的时候不考虑异常呢?因为线程出异常,进程整体资源全部释放,无法返回退出码。
cout << "main thread quit ..., ret" << (long long int)retval << endl;
return 0;
}
#include
#include
#include
#include
#include
using namespace std;
class Request
{
public:
Request(int start, int end, const string &threadname)
: _start(start)
, _end(end)
, _threadname(threadname)
{}
public:
int _start;
int _end;
string _threadname;
};
class Response
{
public:
Response(int result, int exitcode)
: _result(result)
, _exitcode(exitcode)
{}
public:
int _result;
int _exitcode;
};
void *sumCount(void *args) // 线程的参数和返回值,不仅仅可以用来传递一般参数,也可以用来传递对象
{
Request *rq = static_cast(args); // Request *rq = (Request*)args
Response *rsp = new Response(0,0);
for(int i = rq->_start; i <= rq->_end; i++)
{
cout << rq->_threadname << "is running, calling..., " << i << endl;
rsp->_result += i;
usleep(10000);
}
delete rq;
return rsp;
}
int main()
{
pthread_t tid;
Request *rq = new Request(1, 100, "thread 1");
pthread_create(&tid, nullptr, sumCount, rq);
void *ret;
pthread_join(tid, &ret);
Response *rsp = static_cast(ret);
cout << "rsp->result: " << rsp->_result << ", exitcode: " << rsp->_exitcode << endl;
delete rsp;
return 0;
}
②C++的多线程:
#include
#include
#include
#include
// #include
#include
using namespace std;
// 目前,我们的原生线程库,pthread库
// C++11语言本身也已经支持多线程了(封装了 原生线程库)
void threadrun()
{
while(true)
{
cout << "I am a new thread for C++" << endl;
sleep(1);
}
}
int main()
{
thread t1(threadrun);
t1.join();
return 0;
}
三、用户级线程(Linux线程=用户级线程+内核的LWP):
①线程库要维护线程,不用维护线程的执行流。
②线程库注定了要维护多个线程属性集合,通过先描述再组织管理这些进程。
③除了主线程,所有其他线程的独立栈都在共享区,具体来讲是在pthread库中tid指向的用户tcb中,这样运行时就不会互相干扰了。每个线程的库级别的tcb的起始地址叫做线程的tid,线程库维护的tid其实是在库中地址空间的一个描述结构体的虚拟地址,而LWP是管理底层轻量级进程的执行流(用户级执行流:内核LWP=1:1)。
④pthread动态库是进程在运行时根据调用函数情况加载到内存里,通过页表映射到共享区(在执行多线程代码时库也要加载到内存里),通过pthread动态库中打包的函数接口调用系统调用到内核创建线程(并且pthread库里也有很多用以维护线程的函数)。
⑤每一个执行流的本质就是一条调用链。每一个线程有自己独立的的栈结构,都有对应的线程控制块,对线程的属性进行管理。该栈帧结构会保存任何一个执行流在运行过程中的所有临时变量。栈结构本质上是为了支持在应用层完成整个调用链所对应的临时变量的空间的开辟和释放。
⑥线程的栈上的数据也可以被其他线程看到并访问,全局变量也是能被所有的线程同时看到并访问的。通过编译选项可以实现线程级别的全局变量.
#include
#include
#include
#include
#include
#include
using namespace std;
#define NUM 10
// int *p = NULL;
// __thread int g_val = 100; // 线程的局部存储(编译选项只能定义内置类型,不能用来修饰自定义类)型)
// __thread unsigned int number = 0;
// __thread int pid = 0;
struct threadData
{
string threadname;
};
// __thread threadData td;
string toHex(pthread_t tid)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", tid);
return buffer;
}
void InitThreadData(threadData *td, int number)
{
td->threadname = "thread-" + to_string(number);
}
void *threadRoutine(void *args)
{
pthread_detach(pthread_self());
// int test_i = 0;
threadData *td = static_cast(args);
// if(td->threadname == "thread-2") p = &test_i;
string tid = toHex(pthread_self());
int pid = getpid(); // 通过局部存储减少系统调用
int i = 0;
while(i < 10)
{
// cout << "pid: " << getpid() << ", tid : "
// << toHex(pthread_self()) << ", threadname: " << td->threadname
// << ", test_i: " << test_i << ", &test_i: " << &test_i << endl;
// cout << "pid: " << getpid() << ", tid : "
// << toHex(pthread_self()) << ", threadname: " << td->threadname
// << ", g_val: " << g_val << ", &g_val: " << &g_val << endl;
cout << "tid: " << tid << ", pid: " << pid << endl;
sleep(1);
i++;
// test_i++;
// g_val++;
}
delete td;
return nullptr;
}
int main()
{
// 创建多线程
vector tids;
for(int i = 0; i < NUM; i++)
{
pthread_t tid;
threadData *td = new threadData;
InitThreadData(td, i);
pthread_create(&tid, nullptr, threadRoutine, td);
tids.push_back(tid);
// sleep(1);
}
sleep(1); // 确保复制成功
// for(auto i : tids)
// {
// pthread_detach(i);
// }
// cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl;
for(int i = 0; i < tids.size(); i++)
{
int n = pthread_join(tids[i], nullptr);
printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
}
return 0;
}
四、线程的同步与互斥:
1、抢票系统:
#include
#include
#include
#include
#include
#include
using namespace std;
#define NUM 4
int tickets = 1000; // 用多线程模拟一次抢票
class threadData
{
public:
threadData(int number)
{
threadname = "thread-" + to_string(number);
}
public:
string threadname;
};
void *getTicket(void *args)
{
threadData *td = static_cast(args);
// int cnt = 5;
// while(cnt)
// {
// printf("%s is running..., cnt: %d\n",td->threadname.c_str(), cnt--);
// sleep(1);
// }
const char *name = td->threadname.c_str();
while(true)
{
if(tickets > 0)
{
usleep(1000);
printf("who=%s,get a ticket: %d\n", name, tickets); // 共享数据——>数据不一致问题(肯定和多进程并发访问是有关系的)。
tickets--; // 对一个全局变量进行多进程并发++/--操作不是安全的。
}
else
break;
}
printf("%s ... quit\n", name);
return nullptr;
}
int main()
{
vector tids;
vector thread_datas;
for(int i = 1 ; i <= NUM; i++)
{
pthread_t tid;
threadData* td = new threadData(i);
thread_datas.push_back(td);
pthread_create(&tid, nullptr, getTicket, thread_datas[i-1]);
tids.push_back(tid);
}
for(auto thread : tids)
{
pthread_join(thread, nullptr);
}
for(auto td : thread_datas)
{
delete td;
}
return 0;
}
①tickets--,每一步都会对应一条汇编操作:先将tickets读到CPU的寄存器中,再在CPU内部进行--操作,最后将计算结果写回内存。
②寄存器不等于寄存器的内容,线程在执行的时候将共享数据加载到CPU寄存器的本质:把数据的内容变成了自己的硬件上下文(以拷贝的方式给自己单独拿了一份)。恢复上下文时会将前一线程所做的工作覆盖,导致了tickets的数据不一致问题(该操作不是原子的,不安全)。
③解决方案:对共享数据的任何访问,保证任何时候只有一个执行流访问--互斥--锁。
④加锁的本质:对被加锁的代码区域,将多线程串行访问(用时间换取安全)。
加锁的表现,线程对于临界区代码串行执行(加锁和解锁之间的区域叫做临界区)。
自旋锁(是否使用取决于其他线程执行临界区的时长):
#include
#include
#include
#include
#include
#include
using namespace std;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
#define NUM 4
int tickets = 1000; // 用多线程模拟一次抢票
class threadData
{
public:
threadData(int number/*, pthread_mutex_t *mutex*/)
{
threadname = "thread-" + to_string(number);
// lock = mutex;
}
public:
string threadname;
// pthread_mutex_t *lock;
};
void *getTicket(void *args)
{
threadData *td = static_cast(args);
// int cnt = 5;
// while(cnt)
// {
// printf("%s is running..., cnt: %d\n",td->threadname.c_str(), cnt--);
// sleep(1);
// }
const char *name = td->threadname.c_str();
while(true)
{
// 线程对于锁的竞争能力可能会不同
// pthread_mutex_lock(td->lock); // 加锁:申请锁成功才能往后执行,不成功则阻塞等待。
pthread_mutex_lock(&lock); // 加锁:申请锁成功才能往后执行,不成功则阻塞等待。
// 临界区:
if(tickets > 0)
{
usleep(1000);
printf("who=%s,get a ticket: %d\n", name, tickets); // 共享数据——>数据不一致问题(肯定和多进程并发访问是有关系的)。
tickets--; // 对一个全局变量进行多进程并发++/--操作不是安全的。
// pthread_mutex_unlock(td->lock); // 解锁
pthread_mutex_unlock(&lock); // 解锁
}
else
{
// pthread_mutex_unlock(td->lock); // 解锁
pthread_mutex_unlock(&lock); // 解锁
break;
}
usleep(13); // 现实中,抢完一张票后还要执行得到票之后的后续动作,此处用usleep模拟。
}
printf("%s ... quit\n", name);
return nullptr;
}
int main()
{
// pthread_mutex_t lock;
// pthread_mutex_init(&lock, nullptr);
vector tids;
vector thread_datas;
for(int i = 1 ; i <= NUM; i++)
{
pthread_t tid;
threadData* td = new threadData(i/*, &lock*/);
thread_datas.push_back(td);
pthread_create(&tid, nullptr, getTicket, thread_datas[i-1]);
tids.push_back(tid);
}
for(auto thread : tids)
{
pthread_join(thread, nullptr);
}
for(auto td : thread_datas)
{
delete td;
}
// pthread_mutex_destroy(&lock);
return 0;
}
⑤纯互斥环境:如果所分配不够合理,容易导致其他线程的饥饿问题。(不是说只要有互斥,必有饥饿。适合纯互斥的场景,就用互斥。)
⑥同步--按照一定的顺序性获取资源:外面来的,必须排队。出来的人,不能立马重新生成锁,必须排到队列的尾部。让所有的线程(人)获取锁(钥匙),按照一定的顺序。
⑦锁本身就是共享资源。所以申请锁和释放锁本身就被设计成为了原子性操作。在临界区中线程可以被切换,在线程被切出去的时候,是持有锁被切走的。对于其他线程来讲,一个线程要么没有锁,要么释放锁。当前,线程访问临界区的过程对于其他线程是原子的( 一条汇编语句就是原子的)。
⑧加锁的原则,临界区的代码越少越好。运行的时间越短,串行的比率降低,在临界区中线程也可以被调度,临界区的代码越短,线程被调度的概率也越低。
⑨锁交换的本质:把内存中的数据(共享),交换到CPU的寄存器中(线程),本质是把数据交换到线程的硬件上下文中(这是线程私有的)。也就是把一个共享的锁,让一个线程以一条汇编的方式,交换到自己的上下文中。
2、锁的封装(RAII风格的锁):
//LockGuard.hpp
#pragma once
#include
class Mutex
{
public:
Mutex(pthread_mutex_t *lock):_lock(lock)
{}
void Lock()
{
pthread_mutex_lock(_lock);
}
void Unlock()
{
pthread_mutex_unlock(_lock);
}
~Mutex()
{}
private:
pthread_mutex_t *_lock;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t *lock):_mutex(lock)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex _mutex;
};
//mythread.cc
#include
#include
#include
#include
#include
#include
#include "LockGuard.hpp"
using namespace std;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
#define NUM 4
int tickets = 1000; // 用多线程模拟一次抢票
class threadData
{
public:
threadData(int number/*, pthread_mutex_t *mutex*/)
{
threadname = "thread-" + to_string(number);
// lock = mutex;
}
public:
string threadname;
// pthread_mutex_t *lock;
};
void *getTicket(void *args)
{
threadData *td = static_cast(args);
const char *name = td->threadname.c_str();
while(true)
{
{
//临界区:
LockGuard lockguard(&lock); // 临时的LockGuard对象(RAII风格的锁)
if (tickets > 0)
{
usleep(1000);
printf("who=%s,get a ticket: %d\n", name, tickets); // 共享数据——>数据不一致问题(肯定和多进程并发访问是有关系的)。
tickets--;
}
else
{
// pthread_mutex_unlock(td->lock); // 解锁
pthread_mutex_unlock(&lock); // 解锁
break;
}
}
usleep(13);
}
printf("%s ... quit\n", name);
return nullptr;
}
int main()
{
vector tids;
vector thread_datas;
for(int i = 1 ; i <= NUM; i++)
{
pthread_t tid;
threadData* td = new threadData(i/*, &lock*/);
thread_datas.push_back(td);
pthread_create(&tid, nullptr, getTicket, thread_datas[i-1]);
tids.push_back(tid);
}
for(auto thread : tids)
{
pthread_join(thread, nullptr);
}
for(auto td : thread_datas)
{
delete td;
}
// pthread_mutex_destroy(&lock);
return 0;
}
3、死锁:
①4条件(必须同时满足):
互斥条件:一个资源每次只能被一个执行流使用(前提)。
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放(原则)。
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺(原则)。
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系(重要条件)。
②解决死锁问题:
破坏4个必要条件(只要有一个不满足就可以)。
加锁顺序一致。
避免所谓释放的场景。
资源一次性分配。
③避免死锁的算法:
死锁检测算法。
银行家算法。
4、
同步:同步问题是保证数据安全的情况下,让我们的线程访问资源具有一定的顺序性。
解决方案:条件变量(必须依赖于锁的使用)。
①接口:
条件变量函数初始化:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_cond attr_t, *restrict attr);
// 参数:
// cond:要初始化的条件变量
// attr:NULL
销毁:
int pthread_cond_destroy(pthread_cond_t *cond);
等待条件满足:
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
// 参数:
// cond:要在这个条件变量上等待
// mutex:互斥量,后面详细解释
唤醒等待:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
②test_cond:
//makefile
mycond:mycond.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f mycond
//mycond.cc
#include
#include
#include
int cnt = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *Count(void * args)
{
pthread_detach(pthread_self());
uint64_t number = (uint64_t)args;
std::cout << "pthread: " << number << " create success" << std::endl;
while(true)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex); // 1、pthread_cond_wait让线程等待的时候,会自动释放锁!
// 不管临界资源的状态情况
std::cout << "pthread: " << number << " , cnt: " << cnt++ << std::endl;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
for(uint64_t i = 0; i < 5; i++)
{
pthread_t tid;
pthread_create(&tid, nullptr, Count, (void*)i);
}
sleep(3);
std::cout << "main thread ctrl begin: " << std::endl;
while(true)
{
sleep(1);
pthread_cond_signal(&cond); // 唤醒在cond的等待队列中的一个线程,默认都是第一个
std::cout << "signal one thread..." << std::endl;
}
return 0;
}
五、CP问题---理论:
1、生产者的数据从哪里来?用户,网络等。
生产者生产的数据也是要花时间获取的。
①获取数据(访问非临界资源)。
②生产数据和队列(访问临界资源)。
2、消费也要做数据加工处理,也要花时间。
①消费数据(访问临界资源)
②加工处理数据(访问非临界资源)
生产消费访问临界资源和非临界资源时可能出现交叉,从而实现并发。所以生产消费模型是高效的。
3、基于BlockingQueue的生产者消费者模型
//makefile
blockqueue:main.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f mycond blockqueue
//BlockQueue.hpp
#pragma once
#include
#include
#include
template
class BlockQueue
{
static const int defalutnum = 20;
public:
BlockQueue(int maxcap = defalutnum):maxcap_(maxcap)
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&c_cond_, nullptr);
pthread_cond_init(&p_cond_, nullptr);
// low_water_ = maxcap_/3;
// high_water_ = (maxcap_*2)/3;
}
T pop()
{
pthread_mutex_lock(&mutex_);
while(q_.size() == 0) // 判断临界资源是否满足,也是在访问临界资源!判断资源是否就绪,是通过临界资源内部判断的。
{
pthread_cond_wait(&c_cond_, &mutex_); // 此时持有锁。调用的时候,自动释放锁,因唤醒而返回的时候,重新持有锁。
}
T out = q_.front(); // 先保证消费条件满足,才能消费。
q_.pop();
// if(q_.size() < low_water_) pthread_cond_signal(&p_cond_); // 只要消费,一定保证本次队列有空间,直接唤醒生产者生产
pthread_cond_signal(&p_cond_);
pthread_mutex_unlock(&mutex_);
return out;
}
void push(const T &in)
{
pthread_mutex_lock(&mutex_);
while(q_.size() == maxcap_) // 做到防止线程被伪唤醒的情况(不用if而用while)
{
// 伪唤醒情况
pthread_cond_wait(&p_cond_, &mutex_); // 1、调用的时候,自动释放锁
}
// 1、队列没满 2、被唤醒
q_.push(in);
// if(q_.size() > high_water_) pthread_cond_signal(&c_cond_); // 只要生产,一定保证本次队列有数据,直接唤醒消费者消费
pthread_cond_signal(&c_cond_);
pthread_mutex_unlock(&mutex_);
}
~BlockQueue()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&c_cond_);
pthread_cond_destroy(&p_cond_);
}
private:
std::queue q_; // 共享资源,q当作整体来使用的!q只有一份,通过加锁保证资源的安全性。但是资源也可以看作多份分批使用。
int maxcap_; // 极值
pthread_mutex_t mutex_;
pthread_cond_t c_cond_;
pthread_cond_t p_cond_;
// int low_water_;
// int high_water_;
};
//Task.hpp
#pragma once
#include
#include
std::string opers = "+-*/%";
enum{
DivZero = 1,
ModZero,
Unknown
};
class Task
{
public:
Task(int x, int y, char op):data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0)
{
}
void run()
{
switch(oper_)
{
case '+':
result_ = data1_ + data2_;
break;
case '-':
result_ = data1_ - data2_;
break;
case '*':
result_ = data1_ * data2_;
break;
case '/':
{
if(data2_ == 0) exitcode_ = DivZero;
else result_ = data1_ / data2_;
}
break;
case '%':
{
if(data2_ == 0) exitcode_ = DivZero;
else result_ = data1_ % data2_;
}
break;
default:
exitcode_ = Unknown;
break;
}
}
void operator()()
{
run();
}
std::string GetResult()
{
std::string r = std::to_string(data1_);
r += oper_;
r += std::to_string(data2_);
r += "=";
r += std::to_string(result_);
r += "[code: ";
r += std::to_string(exitcode_);
r += "]";
return r;
}
std:: string GetTask()
{
std::string r = std::to_string(data1_);
r += oper_;
r += std::to_string(data2_);
r += "=?";
return r;
}
~Task()
{
}
private:
int data1_;
int data2_;
char oper_;
int result_;
int exitcode_;
};
//main.cc
#include "BlockQueue.hpp"
#include "Task.hpp"
#include
#include
void * Consumer(void *args)
{
BlockQueue *bq = static_cast*>(args);
while(true)
{
// 消费
Task t = bq->pop();
// 计算
// t.run();
t();
std::cout << "处理任务:" << t.GetTask() << "运算结果是:" << t.GetResult() << "thread id: " << pthread_self() << std::endl;
}
}
void * Productor(void *args)
{
int len = opers.size();
BlockQueue *bq = static_cast*>(args);
int x = 10;
int y = 20;
while(true)
{
// 模拟生产者生产数据
int data1 = rand() % 10 + 1; // [1,10]
usleep(10);
int data2 = rand() % 10 + 1; // [1,10]
char op = opers[rand() % len];
Task t(data1, data2, op);
// 生产
bq->push(t);
std::cout << "生产了一个任务: " << t.GetTask() << "thread id: " << pthread_self() < *bq = new BlockQueue();
pthread_t c[3],p[5];
for(int i = 0; i < 3; i++)
{
pthread_create(c+i, nullptr, Consumer, bq);
}
for(int i = 0; i < 5; i++)
{
pthread_create(p+i, nullptr, Productor, bq);
}
for(int i = 0; i < 3; i++)
{
pthread_join(c[i], nullptr);
}
for(int i = 0; i < 3; i++)
{
pthread_join(p[i], nullptr);
}
delete bq;
return 0;
}
思考:为何在读者写者问题中,读者间不互斥?
六、信号量:
1、信号量的本质是一把计数器,那么这把计数器的本质就是用来描述资源数目,判断资源是否就绪,是否在临界区(申请信号量时,其实就间接的已经在做判断了)。
2、接口:
初始化信号量
销毁信号量
对信号量进行P操作
对信号量进行V操作
3、环形队列的生产消费模型sem_cp
条件:
①指向同一个位置的时候,不能同时访问。
②不能超过。
③不能套一个圈。
(不空和不满的时候指向不同的位置,空的时候只能由生产者来访问,买的时候只能由消费者来访问)。
// makefile
RingQueueTest:Main.cpp
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f RingqueueTest
// RingQueue.hpp
#pragma once
#include
#include
#include
#include
const static int defaultcap = 5;
template
class RingQueue{
private:
void P(sem_t &sem)
{
sem_wait(&sem);
}
void V(sem_t &sem)
{
sem_post(&sem);
}
void Lock(pthread_mutex_t &mutex)
{
pthread_mutex_lock(&mutex);
}
void Unlock(pthread_mutex_t &mutex)
{
pthread_mutex_unlock(&mutex);
}
public:
RingQueue(int cap = defaultcap)
:ringqueue_(cap), cap_(cap), c_step_(0), p_step_(0)
{
sem_init(&cdata_sem_, 0, 0);
sem_init(&pspace_sem_, 0, cap);
pthread_mutex_init(&c_mutex_, nullptr);
pthread_mutex_init(&p_mutex_, nullptr);
}
void Push(const T &in) // 生产
{
P(pspace_sem_);
Lock(p_mutex_); // 先申再锁,提高并发度
ringqueue_[p_step_] = in;
// 位置后移,维持环形特性
p_step_++;
p_step_%= cap_;
Unlock(p_mutex_);
V(cdata_sem_);
}
void Pop(T *out) // 消费
{
P(cdata_sem_); // 信号量的申请是原子的
Lock(c_mutex_); // 先申再锁,提高并发度
*out = ringqueue_[c_step_];
// 位置后移,维持环形特性
c_step_++;
c_step_%= cap_;
Unlock(c_mutex_);
V(pspace_sem_);
}
~RingQueue()
{
sem_destroy(&cdata_sem_);
sem_destroy(&pspace_sem_);
pthread_mutex_destroy(&c_mutex_);
pthread_mutex_destroy(&p_mutex_);
}
private:
std::vector ringqueue_;
int cap_;
int c_step_; // 消费者下标
int p_step_; // 生产者下标
sem_t cdata_sem_; // 消费者关注的数据资源
sem_t pspace_sem_; // 生产者关注的空间资源
pthread_mutex_t c_mutex_;
pthread_mutex_t p_mutex_;
};
// Task.hpp
#pragma once
#include
#include
std::string opers = "+-*/%";
enum{
DivZero = 1,
ModZero,
Unknown
};
class Task
{
public:
Task(int x, int y, char op):data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0)
{
}
void run()
{
switch(oper_)
{
case '+':
result_ = data1_ + data2_;
break;
case '-':
result_ = data1_ - data2_;
break;
case '*':
result_ = data1_ * data2_;
break;
case '/':
{
if(data2_ == 0) exitcode_ = DivZero;
else result_ = data1_ / data2_;
}
break;
case '%':
{
if(data2_ == 0) exitcode_ = DivZero;
else result_ = data1_ % data2_;
}
break;
default:
exitcode_ = Unknown;
break;
}
}
void operator()()
{
run();
}
std::string GetResult()
{
std::string r = std::to_string(data1_);
r += oper_;
r += std::to_string(data2_);
r += "=";
r += std::to_string(result_);
r += "[code: ";
r += std::to_string(exitcode_);
r += "]";
return r;
}
std:: string GetTask()
{
std::string r = std::to_string(data1_);
r += oper_;
r += std::to_string(data2_);
r += "=?";
return r;
}
~Task()
{
}
private:
int data1_;
int data2_;
char oper_;
int result_;
int exitcode_;
};
// Main.cpp
#include
#include
#include
#include "RingQueue.hpp"
#include "Task.hpp"
using namespace std;
struct ThreadData
{
RingQueue *rq;
std::string threadname;
};
void *Productor(void *args)
{
// sleep(3);
ThreadData *td = static_cast(args);
RingQueue *rq = td->rq;
std::string name = td->threadname;
int len = opers.size();
while(true)
{
// 1、获取数据
int data1 = rand() % 10 + 1;
usleep(10);
int data2 = rand() % 10;
char op = opers[rand() % len];
Task t(data1, data2, op);
// 2、生产数据
rq->Push(t);
cout << "Productor task done, task is : " << t.GetTask() << " who: " << name << endl;
sleep(1);
}
return nullptr;
}
void *Consumer(void *args)
{
ThreadData *td = static_cast(args);
RingQueue *rq = td->rq;
std::string name = td->threadname;
while(true)
{
// 1、消费数据
Task t;
rq->Pop(&t);
// 2、处理数据
t();
cout << "Consumer get task, task is : " << t.GetTask() << " who: " << name << " result: " << t.GetResult() << endl;
}
return nullptr;
}
int main()
{
srand(time(nullptr)^getpid());
RingQueue *rq = new RingQueue();
pthread_t c[5], p[3];
for(int i = 0; i < 3; i++)
{
ThreadData *td = new ThreadData();
td->rq = rq;
td->threadname = "Productor-" + std::to_string(i);
pthread_create(p + i, nullptr, Productor, td);
}
for(int i = 0; i < 5; i++)
{
ThreadData *td = new ThreadData();
td->rq = rq;
td->threadname = "Consumer-" + std::to_string(i);
pthread_create(c + i, nullptr, Consumer, td);
}
for(int i = 0; i < 3; i++)
{
pthread_join(p[i], nullptr);
}
for(int i = 0; i < 3; i++)
{
pthread_join(c[i], nullptr);
}
return 0;
}
七、线程池threadpool:
//makefile
ThreadPool:Main.cpp
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f ThreadPool
//ThreadPool.hpp
#pragma once
#include
#include
#include
#include
#include
#include
struct ThreadInfo
{
pthread_t tid;
std::string name;
};
static const int defaultnum = 5;
template
class ThreadPool
{
public:
void Lock()
{
pthread_mutex_lock(&mutex_);
}
void Unlock()
{
pthread_mutex_unlock(&mutex_);
}
void Wakeup()
{
pthread_cond_signal(&cond_);
}
void ThreadSleep()
{
pthread_cond_wait(&cond_, &mutex_);
}
bool IsQueueEmpty()
{
return tasks_.empty();
}
std::string GetThreadName(pthread_t tid)
{
for(const auto &ti : threads_)
{
if(ti.tid == tid) return ti.name;
}
return "None";
}
public:
ThreadPool(int num = defaultnum): threads_(num)
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
static void *HandlerTask(/*ThreadPool *this, */void *args) // 传this指针的话,参数数量配不上,只能static化
{
ThreadPool *tp = static_cast *>(args);
std::string name = tp->GetThreadName(pthread_self());
while(true)
{
tp->Lock();
if(tp->IsQueueEmpty())
{
tp->ThreadSleep();
}
T t = tp->Pop();
tp->Unlock();
t();
std::cout << name << " run, " << "result: " << t.GetResult() << std::endl;
}
}
void Start()
{
int num = threads_.size();
for(int i = 0; i < num; i++)
{
threads_[i].name = "thread-" + std::to_string(i+1);
pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this); // // 静态的成员方法不能直接访问类内的成员函数 2:33:00
}
}
void Push(const T &t)
{
Lock();
tasks_.push(t);
Wakeup();
Unlock();
}
T Pop()
{
T t = tasks_.front();
tasks_.pop();
return t;
}
~ThreadPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
private:
std::vector threads_;
std::queue tasks_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
};
//Main.cpp
#include
#include
#include "ThreadPool.hpp"
#include "Task.hpp"
int main()
{
ThreadPool *tp = new ThreadPool(5); // 2:33:00
tp->Start();
srand(time(nullptr) ^ getpid());
while(true)
{
// 1、构建任务
int x = rand() % 10 + 1;
usleep(10);
int y = rand() % 5;
char op = opers[rand()%opers.size()];
Task t(x, y, op);
tp->Push(t);
// 2、交给线程池处理
std::cout << "main thread make task: " << t.GetTask() << std::endl;
sleep(1);
}
}
八、线程的封装:
// thread.cc
#pragma once
#include
#include
#include
#include
typedef void (*callback_t)();
static int num = 1;
class Thread
{
public:
static void *Routine(void *args)
{
Thread* thread = static_cast(args);
thread->Entery();
return nullptr;
}
public:
Thread(callback_t cb):tid_(0), name_(""), start_timestamp_(0), isrunning_(false),cb_(cb)
{}
void Run()
{
name_ = "thread-" + std::to_string(num++);
start_timestamp_ = time(nullptr);
isrunning_ = true;
pthread_create(&tid_, nullptr, Routine, this);
}
void Join()
{
pthread_join(tid_, nullptr);
isrunning_ = false;
}
std::string Name()
{
return name_;
}
uint64_t StartTimestamp()
{
return start_timestamp_;
}
bool IsRunning()
{
return isrunning_;
}
void Entery()
{
cb_();
}
~Thread()
{}
private:
pthread_t tid_;
std::string name_;
uint64_t start_timestamp_;
bool isrunning_;
callback_t cb_;
};
八、线程安全的单例模式
// 懒汉模式
#pragma once
#include
#include
#include
#include
#include
#include
struct ThreadInfo
{
pthread_t tid;
std::string name;
};
static const int defalutnum = 5;
template
class ThreadPool
{
public:
void Lock()
{
pthread_mutex_lock(&mutex_);
}
void Unlock()
{
pthread_mutex_unlock(&mutex_);
}
void Wakeup()
{
pthread_cond_signal(&cond_);
}
void ThreadSleep()
{
pthread_cond_wait(&cond_, &mutex_);
}
bool IsQueueEmpty()
{
return tasks_.empty();
}
std::string GetThreadName(pthread_t tid)
{
for (const auto &ti : threads_)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
static void *HandlerTask(void *args)
{
ThreadPool *tp = static_cast *>(args);
std::string name = tp->GetThreadName(pthread_self());
while (true)
{
tp->Lock();
while (tp->IsQueueEmpty())
{
tp->ThreadSleep();
}
T t = tp->Pop();
tp->Unlock();
t();
std::cout << name << " run, "
<< "result: " << t.GetResult() << std::endl;
}
}
void Start()
{
int num = threads_.size();
for (int i = 0; i < num; i++)
{
threads_[i].name = "thread-" + std::to_string(i + 1);
pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
}
}
T Pop()
{
T t = tasks_.front();
tasks_.pop();
return t;
}
void Push(const T &t)
{
Lock();
tasks_.push(t);
Wakeup();
Unlock();
}
static ThreadPool *GetInstance()
{
if (nullptr == tp_) // 后续所有线程串行申请锁解锁都是没有意义的,通过双重判断提高效率(保证线程安全)
{
pthread_mutex_lock(&lock_);
if (nullptr == tp_)
{
std::cout << "log: singleton create done first!" << std::endl;
tp_ = new ThreadPool();
}
pthread_mutex_unlock(&lock_);
}
return tp_;
}
private:
ThreadPool(int num = defalutnum) : threads_(num)
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
ThreadPool(const ThreadPool &) = delete; // 构造函数
const ThreadPool &operator=(const ThreadPool &) = delete; // 赋值重载
private:
std::vector threads_;
std::queue tasks_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
static ThreadPool *tp_;
static pthread_mutex_t lock_;
};
template
ThreadPool *ThreadPool::tp_ = nullptr;
template
pthread_mutex_t ThreadPool::lock_ = PTHREAD_MUTEX_INITIALIZER;