正文开始!
进程的多个线程共享同一地址空间,因此Text Segment,Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享一下进程资源和环境:
void* startRoutine(void* args)
{while(true){cout<<"线程的正在运行..."<pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");cout<<"new thread id: "<cout<<"main thread 正在运行..."<
tid的值为什么这么大呢?—>线程的ID
转为16进制打印后
static void printTid(const char* name,const pthread_t& tid)
{printf("%s 正在运行,id : 0x%x\n",name,tid);
}
void* startRoutine(void* args)
{const char* name=static_cast(args);while(true){printTid(name,pthread_self());sleep(1);}
}
int main()
{pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");while(true){printTid("main thread",pthread_self());sleep(1);}pthread_join(tid,nullptr);return 0;
}
pthread_t的tid其实就是一个地址!
线程的全部实现,并没有全部体现在OS内,而是OS提供执行流,具体的线程结构由库来进行管理。
库可以创建多个线程—>库也要管理线程!---->先描述在组织。
所以库里面就要有struct thread_info{pthread_t tid; void* stack;//私有栈…};
所以pthread_t是我们对应的用户级现成的控制结构体的起始地址!
所以主线程的独立栈结构用的就是地址空间中的栈区
新线程用的栈结构,用的就是库中提供的栈结构!
所以在Linux中,用户级线程库和内核的LWP是1:1的
就类似与我们之间讲过的fork()函数创建子进程,父进程需要waitpid()等待子进程退出。不等待就会造成内存泄漏等问题。
void* startRoutine(void* args)
{const char* name=static_cast(args);int cnt=10;while(cnt--){printTid(name,pthread_self());sleep(1);}cout<<"线程退出啦..."<pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");sleep(5);cout<<"主线程运行结束,正在等待回收线程"<
void* startRoutine(void* args)
{const char* name=static_cast(args);int cnt=5;while(true){printTid(name,pthread_self());sleep(1);if(!(cnt--)){int* p=nullptr;*p=10;//野指针问题}}cout<<"线程退出啦..."<pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");while(true){sleep(1);}pthread_join(tid,nullptr);return 0;
}
整个线程整体异常退出,线程异常=进程异常!
线程会影响其他线程的运行—main thread----健壮性较低,鲁棒性。
是一个输出型参数,获取新线程退出时候的退出码!
进程退出:
void* startRoutine(void* args)
{const char* name=static_cast(args);int cnt=5;while(true){printTid(name,pthread_self());sleep(1);if(!(cnt--)){break;}}cout<<"线程退出啦..."<pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");//void **retval是一个输出型参数void* ret=nullptr;pthread_join(tid,&ret);cout<<"main thread join success,*ret: "<<(long long)ret<
2. 调用pthread_exit()
void* startRoutine(void* args)
{const char* name=static_cast(args);int cnt=5;while(true){printTid(name,pthread_self());sleep(1);if(!(cnt--)){break;}}cout<<"线程退出啦..."<pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");//void **retval是一个输出型参数void* ret=nullptr;pthread_join(tid,&ret);cout<<"main thread join success,*ret: "<<(long long)ret<
3. 调用pthread_cancel()
void* startRoutine(void* args)
{const char* name=static_cast(args);while(true){printTid(name,pthread_self());sleep(1);}cout<<"线程退出啦..."<pthread_t tid;int n=pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");sleep(3);//代表main线程对应的工作pthread_cancel(tid);cout<<"new thread been canceled"<
给线程发送取消请求,如果线程是被取消的,退出结果是:-1。
int global_value=100;
void* startRoutine(void* args)
{while(true){cout<<"thread "<pthread_t t1;pthread_t t2;pthread_t t3;pthread_create(&t1,nullptr,startRoutine,(void*)"thread 1");pthread_create(&t1,nullptr,startRoutine,(void*)"thread 2");pthread_create(&t1,nullptr,startRoutine,(void*)"thread 3");pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);return 0;
}
每个线程访问的是同一个变量!
如何让每个线程具有只属于自己的全局变量呢?
__thread int global_value=100;
每个线程就有了属于自己的变量了!—>局部存储!
获取线程的tid
关于pthread_join的返回值
int global_value = 100;
void *startRoutine(void *args)
{pthread_detach(pthread_self());std::cout<<"线程分离..."<cout << "thread " << pthread_self()<< " global_value: " << global_value<< " &global_value: " << &global_value<< " LWP: " << syscall(SYS_gettid)<< endl;sleep(1);}
}int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(&tid1, nullptr, startRoutine, (void *)"thread 1");pthread_create(&tid2, nullptr, startRoutine, (void *)"thread 2");pthread_create(&tid3, nullptr, startRoutine, (void *)"thread 3");sleep(2);int n = pthread_join(tid1, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid2, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid3, nullptr);cout << n << " : " << strerror(n) << endl;return 0;
}
所以我们在执行pthread_detach()分离线程以后就不能使用pthread_join()等待线程退出了!!
但是我们倾向于让主线程分离其他线程!
int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(&tid1, nullptr, startRoutine, (void *)"thread 1");pthread_create(&tid2, nullptr, startRoutine, (void *)"thread 2");pthread_create(&tid3, nullptr, startRoutine, (void *)"thread 3");sleep(2);pthread_detach(tid1);pthread_detach(tid2);pthread_detach(tid3);return 0;
}
线程可以立即分离和延后分离----线程活着----意味着我们不在关心这个线程的死活了—可以理解为线程退出的第四种方式!(延后分离)
新线程分离,但是主线程先退出(进程退出!)—一般我们分离线程,对应的main thread一般不要退出(常驻内存的进程)
pthread_exit()是退出该线程!
任何一个线程调用exit(),都表示整个进程退出!!!
//临界资源
// int 票数计数器
int tickets = 1000; //临界资源,可能会因为共同访问,可能会造成数据不一致问题
void *getTickets(void *args)
{const char *name = static_cast(args);while (true){if (tickets > 0){usleep(10000);cout << name << "抢到了票,票的编号: " << tickets << endl;tickets--;usleep(123);//模拟其他业务逻辑的执行}else{//票抢到几张,就算没有了呢?--->0cout << name << " 已经放弃抢票了,因为没有了..." << endl;break;}}return nullptr;
}int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_t tid4;pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");int n = pthread_join(tid1, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid2, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid3, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid4, nullptr);cout << n << " : " << strerror(n) << endl;return 0;
}
正常情况下,票抢到0张的时候是不是就结束了呢?请看以下结果
这种情况存在偶然性,但是真正抢票的时候是不能发生的!!!
因为tickets–:是由一条语句完成的吗???—>并不是,在底层汇编语句的实现,要至少被翻译称为三条语句!!!
tickets–:
在上面三条语句的任何地方,线程都有可能被切换走!!!
CPU的寄存器是被所有的执行流共享的,但是寄存器里面的数据是属于当前执行流的上下文数据的!(线程被切换的时候,需要保存上下文;线程被换回的时候,需要恢复上下文)
**为了避免以上情况的发生,我们应该保证访问临界区的操作是原子的!**所以访问临界区的操作我们可以给他加锁!
//临界资源
// int 票数计数器
int tickets = 1000; //临界资源,可能会因为共同访问,可能会造成数据不一致问题
pthread_mutex_t mutex;
void *getTickets(void *args)
{const char *name = static_cast(args);while (true){//临界区,只要对临界区加锁,而且加锁的粒度越细越好//加锁的本质是让线程执行临界区代码串行化//加锁是一套规范法,通过临界区对临界资源进行访问的时候,要加就都要加pthread_mutex_lock(&mutex);if (tickets > 0){usleep(10000);cout << name << "抢到了票,票的编号: " << tickets << endl;tickets--;pthread_mutex_unlock(&mutex);usleep(123);//模拟其他业务逻辑的执行}else{//票抢到几张,就算没有了呢?--->0cout << name << " 已经放弃抢票了,因为没有了..." << endl;pthread_mutex_unlock(&mutex);break;}}return nullptr;
}int main()
{pthread_mutex_init(&mutex, nullptr);pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_t tid4;pthread_create(&tid1, nullptr, getTickets, (void *)"thread 1");pthread_create(&tid2, nullptr, getTickets, (void *)"thread 2");pthread_create(&tid3, nullptr, getTickets, (void *)"thread 3");pthread_create(&tid4, nullptr, getTickets, (void *)"thread 4");int n = pthread_join(tid1, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid2, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid3, nullptr);cout << n << " : " << strerror(n) << endl;n = pthread_join(tid4, nullptr);cout << n << " : " << strerror(n) << endl;pthread_mutex_destroy(&mutex);return 0;
}
锁保护的是临界区,换言之任何线程执行我们临界区代码,访问临界资源。都必须先申请锁,前提是都必须看到锁
这把锁,本身不就也是临界资源吗??锁的设计者早就想到了
pthread_mutex_lock:竞争和申请锁的过程,就是原子的!!
如果锁是main创建的并且初始化的,线程想要获得锁和线程名可以使用结构体传参!
结构体传参
int tickets = 1000;
#define NAMESIZE 100
typedef struct threadData
{char _name[NAMESIZE];pthread_mutex_t *_mutex;
} threadData;void *startRoutine(void *args)
{threadData *td = static_cast(args);const char *name = td->_name;pthread_mutex_t *mutex = td->_mutex;while (true){pthread_mutex_lock(mutex); //如果申请不到,就阻塞进程if (tickets > 0){usleep(1000);cout << name << " get a ticket: " << tickets << endl;tickets--;pthread_mutex_unlock(mutex);//你还有其他的事情做usleep(500);}else{pthread_mutex_unlock(mutex);break;}}
}
int main()
{static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_t t1;pthread_t t2;pthread_t t3;pthread_t t4;threadData* td=new threadData();strcpy(td->_name,"thread 1");td->_mutex=&mutex;pthread_create(&t1, nullptr, startRoutine, (void *)td);pthread_create(&t2, nullptr, startRoutine, (void *)&mutex);pthread_create(&t3, nullptr, startRoutine, (void *)&mutex);pthread_create(&t4, nullptr, startRoutine, (void *)&mutex);pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);delete(td);return 0;
}
也可以正常的实现抢票的逻辑!!!
难道在加锁的临界区里面,就没有线程切换了吗?是不是加锁==不会被切换?
当然是完全可以!因为线程执行的加锁解锁等对应的也是代码,线程在任意代码处都可以被切换的,但是线程加锁是原子的—>这个锁,要么你拿到了,要么没有拿到
在我被切走的时候,绝对不会有线程进入临界区!!!!---->因为线程进入临界区需要先申请锁,但是锁现在被我抱着跑了(即便我没有被调度)—>新线程申请不到,只能被挂起了!!!
所以一旦一个线程持有了锁,该线程根本就不担心任何切换问题!!!
对于其他线程而言,该线程访问临界区,只有没有进入和访问完毕两种状态!!!这样才对其他线程有意义!!!
所以这就要求我们尽量不要在临界区里面做一些耗时的事情!!
线程b在线程a执行swap或者exchange交换前切入线程后就可以获得锁,之后线程a在切换也只是吧线程中mutex的0在来回切换,执行if判断也就只能挂起等待了!
本质:将数据从内存读入存储器,本质是将数据从共享变成线程私有!
1就如同令牌一般
解锁的过程就是把1在写入内存中的变量mutex即可!
log.hpp
/** @Author: hulu 2367598978@qq.com* @Date: 2022-11-17 16:46:55* @LastEditors: hulu 2367598978@qq.com* @LastEditTime: 2022-11-17 16:49:18* @FilePath: /2022_11_16/lock.hpp* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#pragma once#include
#include class Mutex
{
public:Mutex(){pthread_mutex_init(&_lock, nullptr);}void lock(){pthread_mutex_lock(&_lock);}void unlock(){pthread_mutex_unlock(&_lock);}~Mutex(){pthread_mutex_destroy(&_lock);}private:pthread_mutex_t _lock;
};class LockGuard
{
public:LockGuard(Mutex* mutex): _mutex(mutex){_mutex->lock();std::cout<<"加锁成功..."<_mutex->unlock();std::cout<<"解锁成功..."<
main.cc
Mutex mutex;
int tickets = 1000;
//函数本质就是一个代码块,
bool getTickets()
{bool ret=false;//函数的局部变量,在栈上保存,线程具有独立的栈结构,每个线程各自一份LockGuard lock_guard(&mutex);//局部对象的声明生命周期是随代码块的if (tickets > 0){usleep(1000);cout << "thread " <const char* name=static_cast(args);while(true){if(!getTickets()){break;}cout<pthread_t t1;pthread_t t2;pthread_t t3;pthread_t t4;pthread_create(&t1, nullptr, startRoutine, (void *)"thread 1");pthread_create(&t2, nullptr, startRoutine, (void *)"thread 2");pthread_create(&t3, nullptr, startRoutine, (void *)"thread 3");pthread_create(&t4, nullptr, startRoutine, (void *)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
}
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于一种永久等待的状态
#include
#include
#include
#include
#include"lock.hpp"
using namespace std;pthread_mutex_t mutexA=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB=PTHREAD_MUTEX_INITIALIZER;void* startRoutine1(void* args)
{while(true){pthread_mutex_lock(&mutexA);sleep(1);pthread_mutex_lock(&mutexB);cout<<"我是线程1,我的tid: "<while(true){pthread_mutex_lock(&mutexB);sleep(1);pthread_mutex_lock(&mutexA);cout<<"我是线程2,我的tid: "<pthread_t t1,t2;pthread_create(&t1,nullptr,startRoutine1,nullptr);pthread_create(&t1,nullptr,startRoutine2,nullptr);pthread_join(t1,nullptr);pthread_join(t2,nullptr);return 0;
}
上面打的东西乱是缺少访问控制。
此时线程还未退出,但是卡在这里不执行了。这就是死锁问题!!!
(本章完!)
下一篇:第05章_存储引擎