当前位置: 首页 > news >正文

linux并发服务器 —— 多线程并发(六)

线程概述

同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域;

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位;

Linux环境下,线程的本质就是进程;

ps -Lf pid:查指定进程LWP号(线程号)

线程和进程的区别

1. 进程间的信息难以共享,除只读代码段,父子进程并未共享内存;

        线程共享信息方便快速(进程、父进程、进程组、会话ID,文件描述符表,当前工作目录,文件权限掩码,虚拟地址空间(除栈、.text));但超线程ID、信号掩码、error变量、调度策略和优先级、栈、本地变量不共享;

2. fork创建进程代价较高

        创建线程比创建进程快一个数量级以上

线程操作

/*#include <pthread.h>一般情况下,main所在线程为主线程/main线程,其余都成为子线程pthread_t pthread_self(void);功能:获取当前线程IDint pthread equal(pthread_t tl,pthread_t t2);功能:比较两个线程号是否相等不同操作系统,pthread_t类型实现不一样,有可能是结构体int pthread_create(pthread t *thread, const pthread attr t *attr,void *(*start_routine) (void *), void *arg);功能:创建一个子线程(调度的基本单位)参数:thread - 传出参数:线程创建成功,子线程ID会写入该变量attr - 设置线程的属性,默认值 - NULLstart_rountine - 函数指针,子线程需要处理的逻辑代码arg - 给start_rountine使用,传参返回值:成功 - 0失败 - 错误号,与errno不同;获取错误号信息:char* strerror(int errnum);void pthread_exit(void *retval);功能:终止一个当前调用线程参数:retval - 传递一个指针,作为一个返回值,可以在pthread_join中获取返回值: 没有任何返回值int pthread_join(pthread_t thread,void **retval);功能:和一个已经终止的线程进行连接回收子线程的资源这个函数是阻塞函数,调用一次只能回收一个子线程一般在主线程中去使用参数:thread - 需要回收的子线程IDretval - 接收子线程退出的返回值返回值:成功 - 0失败 - !0int pthread_detach(pthread_t thread);功能:分离一个线程,将线程标记分离,线程终止时自动释放资源给系统1. 不能多次分离,不可预料2. 不能去连接一个已经分离的线程,会报错(join)参数:需要分离的线程ID返回值:成功 - 0失败 - errorint pthread_cancel(pthread_t thread);功能:取消线程(让线程终止),中途暂停!但并不是立马终止,而是当一个子线程执行到一个取消点,线程才会终止取消点:系统规定好的一些系统调用,可以粗略认为是用户去到内核区的切换这个位置
*/

创建线程

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void *arg){cout<<"子线程...."<<*((int*)arg)<<endl;return NULL;
}int main(){pthread_t tid;int num = 10;int ret = pthread_create(&tid , NULL , callback , (void*)&num);if(ret != 0){char* str = strerror(ret);cout<<"error: "<<str<<endl;}for(int i = 0 ; i<5 ; i++){cout<<i<<endl;}sleep(1);return 0;
}

终止线程

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void *arg){char buf[1024];sprintf(buf , "子线程....%ld" , pthread_self());cout<<buf<<endl;return NULL;
}int main(){// 创建子线程pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<str<<endl;}for(int i = 0 ; i<100 ; i++){cout<<i<<endl;}cout<<"子线程...."<<tid<<endl;cout<<"主线程...."<<pthread_self()<<endl;pthread_exit(NULL);// 主线程退出不会影响正常运行的线程return 0; // 进程退出 所有子线程立刻终止
}

链接已终止的线程

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;int val = 10;void* callback(void *arg){char buf[1024];sprintf(buf , "子线程....%ld" , pthread_self());cout<<buf<<endl;// sleep(3);// return NULL; // pthread_exit(NULL);// int val = 10; // 局部变量pthread_exit((void*)&val);
}int main(){// 创建子线程pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<str<<endl;}for(int i = 0 ; i<5 ; i++){cout<<i<<endl;}cout<<"子线程...."<<tid<<endl;cout<<"主线程...."<<pthread_self()<<endl;int* ptr;if(pthread_join(tid , (void **)&ptr) != 0){char* str = strerror(ret);cout<<str<<endl;}cout<<"回收子线程成功: "<<*(int *)ptr<<endl;pthread_exit(NULL);// 主线程退出不会影响正常运行的线程return 0; // 进程退出 所有子线程立刻终止
}

线程分离

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void* arg){cout<<"我的ID: "<<pthread_self()<<endl;return NULL;
}int main(){// 创建pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<"error1: "<<str<<endl;}cout<<"父线程:"<<pthread_self()<<"子线程:"<<tid<<endl;//子线程分离ret = pthread_detach(tid);if(ret != 0){char* str = strerror(ret);cout<<"error2: "<<str<<endl;}//对分离子线程进行连接ret = pthread_join(tid,NULL);if(ret != 0){char* str = strerror(ret);cout<<"error3: "<<str<<endl;}pthread_exit(NULL);return 0;
}

线程取消

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void* arg){cout<<"我的ID: "<<pthread_self()<<endl;for(int i = 0 ; i<5 ; i++){cout<<"子线程:"<<i<<endl;}return NULL;
}int main(){// 创建pthread_t tid;int ret = pthread_create(&tid , NULL , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<"error1: "<<str<<endl;}// 取消线程pthread_cancel(tid);for(int i = 0 ; i<10 ; i++){cout<<i<<endl;}cout<<"父线程:"<<pthread_self()<<"子线程:"<<tid<<endl;pthread_exit(NULL);return 0;
}

线程属性

/*
int pthread_attr_init(pthread_attr_t *attr);初始化线程属性变量int pthread_attr_destroy(pthread_attr_t *attr);释放线程属性资源int pthread_attr_getdetachstate(const pthread_attq_t *attr, int* detachstate);获取线程分离的状态属性int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);设置线程分离的状态属性*/#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string.h>
#include <unistd.h>
using namespace std;void* callback(void* arg){cout<<"我的ID: "<<pthread_self()<<endl;return NULL;
}int main(){// 创建线程属性变量pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_DETACHED);// 获取线程栈的大小size_t size;pthread_attr_getstacksize(&attr , &size);cout<<"子线程占空间大小:"<<size<<endl;// 创建pthread_t tid;int ret = pthread_create(&tid , &attr , callback , NULL);if(ret != 0){char* str = strerror(ret);cout<<"error1: "<<str<<endl;}cout<<"父线程:"<<pthread_self()<<"子线程:"<<tid<<endl;pthread_attr_destroy(&attr);ret = pthread_join(tid,NULL);if(ret != 0){char* str = strerror(ret);cout<<"error3: "<<str<<endl;}pthread_exit(NULL);return 0;
}

线程同步

必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量;

临界区是指访问某一共享资源的代码片段,这段代码的执行应该为原子操作(不能分割);

互斥锁

使用互斥锁来确保仅有一个线程可以访问某项共享资源,保证原子访问;

互斥锁由两种状态:锁定/未锁定,试图对锁定的互斥锁再加锁会导致线程阻塞/报错,取决于加锁使用的方法;

线程加锁成为互斥锁的所有者,只有所有者才能解锁;

/*
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutexconst pthread_mutexattr_t *restrict attr);功能:初始化互斥锁参数:mutex - 需要初始化的互斥锁attr - 互斥锁相关属性 NULLrestric - C语言修饰符,被修饰的指针不能由另外的指针进行操作
int pthread_mutex_destroy(pthread_mutex_t *mutex);释放互斥量的资源
int pthread_mutex_lock(pthread_mutex_t *mutex);加锁 , 如果有线程已经加锁,只能阻塞等待
int pthread_mutex_trylock(pthread_mutex_t *mutex);尝试加锁,加锁失败不会阻塞,会直接返回
int pthread_mutex_unlock(pthread_mutex_t *mutex);释放锁
*/

死锁

多个进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象;

导致死锁的三个主要原因:

1. 加锁忘记释放

2. 重复枷锁

3. 线程之间对于锁循环等待

读写锁

读写锁允许多个读出,但只允许一个写入:

1. 如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作;

2. 有其他线程写数据,则其他线程不允许读/写;

3. 写是独占的,写的优先级高;

/*读写锁的类型 pthread_rwlock_tint pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
*/
// 案例:创建8个线程,操作同一个全局变量;
//      3个线程不定时写一个全局变量,其余5个线程不定时读全局变量
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <unistd.h>
using namespace std;int num = 1;
// pthread_mutex_t mutex;
pthread_rwlock_t rwlock;void* wnum(void* arg){while(1){// pthread_mutex_lock(&mutex);pthread_rwlock_wrlock(&rwlock);num++;printf("++write , tid: %ld , num : %d\n" , pthread_self() , num);// pthread_mutex_unlock(&mutex);pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}void* rnum(void* arg){while(1){// pthread_mutex_lock(&mutex);pthread_rwlock_rdlock(&rwlock);printf("read , tid: %ld , num : %d\n" , pthread_self() , num);// pthread_mutex_unlock(&mutex);pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}int main(){// pthread_mutex_init(&mutex , NULL);pthread_rwlock_init(&rwlock , NULL);// 创建3个写线程 5个读线程pthread_t wtids[3] , rtids[5];for(int i = 0 ; i<3 ; i++){pthread_create(&wtids[i] , NULL , wnum , NULL);}for(int i = 0 ; i<5 ; i++){pthread_create(&rtids[i] , NULL , rnum , NULL);}// 设置线程分离for(int i = 0 ; i<3 ; i++){pthread_detach(wtids[i]);}for(int i = 0 ; i<5 ; i++){pthread_detach(rtids[i]);}pthread_exit(NULL);// pthread_mutex_destroy(&mutex);pthread_rwlock_destroy(&rwlock);return 0;
}

生产者消费者模式

多生产者 - 容器 - 多消费者

阻塞 - 通知机制,需要条件变量和信号量来进行实现;

条件变量 - 通过条件变量来唤醒阻塞进程

信号量 - 一定程度上表示资源的多少

/*信号量的类型 sem_tint sem_init(sem_t *sem, int pshared,unsigned int value);初始化信号量参数:sem - 信号量变量的地址pshared - 0用在线程,非0用在进程value - 信号量中的值int sem_destroy(sem_t *sem);释放资源int sem_wait(sem_t *sem);加锁 对信号量的值减1,如果值为0则阻塞int sem_trywait(sem_t *sem);尝试int sem_timedwait(sem_t *sem, const struct timespec *abs timeout);等待多少时间int sem_post(sem_t *sem);解锁 对信号量的值加1int sem_getvalue(sem_t *sem, int *sval);获取值
*/
// 生产者消费者模型 粗略版本
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <semaphore.h>
using namespace std;// 创建一个互斥锁
pthread_mutex_t mutex;
// 创建两个信号量
sem_t p , c;class Node{
public:int num;Node* next;
};Node* head = NULL;void* pro(void* arg){// 不断创建节点添加到链表while(1){sem_wait(&p);pthread_mutex_lock(&mutex);Node* newNode = new Node();newNode->next = head;head = newNode;newNode->num = rand()%100;printf("add node , num: %d , tid: %ld\n" , newNode->num , pthread_self());   pthread_mutex_unlock(&mutex);sem_post(&c);usleep(1000);}return NULL;
}void* cus(void* arg){while(1){sem_wait(&c);pthread_mutex_lock(&mutex);Node* cur = head;head = head->next;printf("del node : %d , tid : %ld\n" , cur->num , pthread_self());delete(cur);cur = NULL;pthread_mutex_unlock(&mutex);sem_post(&p);usleep(1000);}return NULL;
}int main(){pthread_mutex_init(&mutex , NULL);sem_init(&p , 0 , 5);sem_init(&c , 0 , 0);// 5个生产者,5个消费者pthread_t ptids[5] , ctids[5];for(int i = 0 ; i<5; i++){pthread_create(&ptids[i] , NULL , pro , NULL);pthread_create(&ctids[i] , NULL , cus , NULL);}for(int i = 0 ; i<5 ; i++){pthread_detach(ptids[i]);pthread_detach(ctids[i]);}while(1){sleep(10);}pthread_mutex_destroy(&mutex);pthread_exit(NULL);return 0;
}

http://www.lryc.cn/news/149821.html

相关文章:

  • Nginx 部署 配置
  • 数据结构:时间复杂度和空间复杂度计算
  • 云原生Kubernetes:二进制部署K8S单Master架构(一)
  • ICCV 2023 | 利用双重聚合的Transformer进行图像超分辨率
  • 经纬恒润预期功能安全(SOTIF)解决方案为自动驾驶安全保驾护航
  • java从入门到起飞(七)——面向对象
  • 题集-三路划分和三数取中(快排优化)
  • 设计模式-迭代器
  • Hive学习(12)Hive常用日期函数
  • PowerQuery动态加载M公式
  • 2分钟搭建FastGPT训练企业知识库AI助理(Docker部署)
  • TDengine函数大全-字符串函数
  • part-02 C++知识总结(类型转换)
  • stable diffusion实践操作-图生图
  • Jtti:Ubuntu18.04如何修改远程ssh端口号
  • 微软表示Visual Studio的IDE即日起开启“退休”倒计时
  • 好马配好鞍:Linux Kernel 4.12 正式发布
  • element——switch接口成功后赋值打开开关
  • WPF Border设置渐变色
  • SAP_ABAP_OLE_EXCEL批导案例
  • MySQL以及版本介绍
  • stm32 iap sd卡升级
  • D358周赛复盘:哈希表模拟⭐⭐+链表乘法翻倍运算(先反转)⭐⭐⭐
  • java八股文面试[数据库]——索引的基本原理、设计原则
  • 2023年京东方便食品行业数据分析(京东数据报告)
  • 无涯教程-Android - Style Demo Example函数
  • 【算法训练-字符串 二】最长回文子串
  • 结合OB Cloud区别于MySQL的4大特性,规划降本方案
  • 题目有点太简单了,不知道怎么选了
  • Bug:mac上运行go run main.go 报错,fork/exec /var/fold/T/go-build269/b001/ex