文章目录
线程概述
1. 与进程类似,线程是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。
2. 进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。
3. 线程是轻量级的进程(LWP:Light Weight Process),在linux环境下线程的本质仍是进程
4. 查看指定进程的LWP号:ps-Lf pid
线程和进程的区别
- 进程间信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,,在进程间进行信息交换。
- 调用fork()来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符之类的多进程属性,这意味着fork()调用在时间开销上依然不菲。
- 线程间能够方便、快速的共享信息。只需要将数据复制到共享变量(全局或堆)中即可。
- 创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制内存,也无需复制页表。
线程间共享和非共享的资源
共享资源
- 进程ID和父进程ID
- 进程组ID和会话ID
- 用户ID和用户组ID
- 文件描述符表
- 信号处置
- 文件系统的相关信息:文件权限掩码(umask)、当前工作目录
- 虚拟地址空间(除栈、text段)
非共享资源
- 线程ID
- 信号掩码
- 线程特有数据
- error变量
- 实时调度策略和优先级
- 栈,本地变量和函数的调用链接信息
创建线程(pthread_create)
pthread_create()
#include<pthread.h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,
void *(*start_routine)(void*),void *arg)
功能:创建一个线程,一般来说,main函数所在的线程被称为主线程(main线程),其余创建的线程被称为子线程。
参数:-thread : 传出参数,线程创建成功后,子线程ID被写到该变量中
-attr: 设置线程属性,一般设置为NULL;
-start_routine: 函数指针,线程要执行的函数
-argue:给第三个参数使用,主要是传参
返回值:
成功:0
失败:返回错误号
;```
```cpp
void * callback(void *arg){//回调函数,主线程中调用的
printf("child thread....\n");
printf("Arg value %d",*(int *)arg);//先强制转成int* 然后再解引用
return NULL;
}
int main(){
pthread_t tid;
int num=10;//可以在子线程中获取到该变量
int ret = pthread_create(&tid,NULL,callback,(void*)&num);
if(ret != 0){
char *errstr = strerror(ret);
printf("error : %s\n",errstr);
}
for(int i=0; i<5; i++){
printf("%d\n",i);
}
pthread_exit(NULL);
return 0;
}
终止线程(pthread_exit)
pthread_exit()
#include<pthread.h>
void pthread_exit(void *retval);
功能:终止一个线程,在哪个线程中调用,表示终止哪个线程
参数:
retval:传递一个指针,作为返回值,可以在pthread_join()中获取到
pthread_t pthread_self(void);
功能:获取当前线程的ID
int pthread_equal(pthread_t t1,pthread_t t2);
功能:比较两个线程ID是否相等
不同操作系统 pthrea_t类型实现不一样,有的是无符号长整型,有的是结构体类型
#include<stdio.h>
#include<string.h>
void *callback1(void *arg){
printf("child thread.....id is :%ld\n",pthread_self());
return NULL;
}
int main(){
pthread_t pid;//保存子线程的ID
int ret = pthread_create(&pid,NULL,callback1,NULL);
for(int i=0; i<5; i++){
printf("%d\n",i);
}
printf("main thread.....id is :%ld\n",pthread_self());
pthread_exit(NULL);//退出主线程,当主线程退出时,不会影响其他正常运行的线程
return 0;
}
连接已终止的线程(pthread_join)
pthread_join(pthread_t thread,void **retval);
功能:
-和一个已经终止的线程进行连接
回收子线程资源
这个函数是阻塞函数,调用一次只能回收一个子线程
一般在主线程中使用
参数:
-thread:需要回收的子线程ID
-retval:接收子线程退出时的返回值
返回值:
成功返回0
失败返回错误号
*/
#include<pthread.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int val=10;//要定义成全局变量,不可以定义在函数体内,函数执行结束,栈区资源就释放了
void *callback1(void *arg){
//sleep(3);//休眠3秒
printf("child thread.....id is :%ld\n",pthread_self());
pthread_exit((void *)&val);
//return NULL;
}
int main(){
pthread_t tid;//保存子线程的ID
int ret = pthread_create(&tid,NULL,callback1,NULL);
for(int i=0; i<5; i++){
printf("%d\n",i);
}
printf("main thread.....id is :%ld\n",pthread_self());
//主线程调用pthread_join()回收子线程的资源
//3秒后,会打印出这句话
//int res = pthread_join(tid,NULL);
int *thread_retval;
int res = pthread_join(tid,(void **)&thread_retval);
printf("exit val is:%d\n",*thread_retval);
printf("回收子线程资源成功\n");
pthread_exit(NULL);//退出主线程,当主线程退出时,不会影响其他正常运行的线程
return 0;
}
线程分离(pthread_detach)
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
分离一个线程,被分离的线程终止的时候会自动释放资源返回给系统
1:不能多次分离,会产生不可预期的行为
2:不能去连接一个已经分离的线程,会报错
参数:
需要分离的线程的ID
返回值:
0:成功
非0:失败 错误号
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void * callback(void *arg){
printf("child thread id: %ld",pthread_Self());
return NULL;
}
int main(){
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
printf("tid:%ld,main thread:%ld\n",tid,pthread_self());
//设置线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
pthread_detach(tid);
//此时就不可再调用pthread_join
pthread_exit(NULL);
return 0;
}
线程取消(pthread_cancel)
*
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
取消线程,让线程终止
并不是立马终止,当子线程执行到一个取消点,线程才会终止
*/
#include<pthread.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void *callback(void *arg){
printf("child thread is :%ld\n",pthread_self());
for(int i=0; i<5; i++){
printf("child: %d\n",i);
}
}
int main(){
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
pthread_cancel(tid);//取消线程
for(int i=0; i<5; i++){
printf("%d\n",i);
}
pthread_exit(NULL);
return 0;
}
线程同步
初始版本 不加锁
*
使用多线程去卖票,
3个窗口,总共100张票
*/
#include<pthread.h>
#include<stdio.h>
int tickets = 100;//设为全局变量
void *selltickets(void *arg){
while(tickets > 0){
printf("%ld 正在卖第%d张门票\n",pthread_self(),tickets);
tickets--;
}
return NULL;
}
int main(){
pthread_t t1,t2,t3;
pthread_create(&t1,NULL,selltickets,NULL);
pthread_create(&t2,NULL,selltickets,NULL);
pthread_create(&t3,NULL,selltickets,NULL);
//回收子线程资源,阻塞
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
//设置线程分离
// pthread_detach(t1);
// pthread_detach(t2);
// pthread_detach(t3);
pthread_exit(NULL);//退出主线程
return 0;
}
mutex
- 线程的主要优势在于,能够通过全局变量来共享信息。不过这种便捷的共享是有代价的,必须保证确保多个线程不会同时修改同一变量或者某个线程不会读取正在由其他线程修改的变量
- 临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应中断该片段的执行
pthread_mutex_t;
int pthread_mutex_init(pthread_mutex_t*restrict mutex,const pthread_mutextatttr_T attr)
初始化互斥量
参数:
-mutex:需要初始化的互斥量变量
-attr:互斥量相关的属性,NULL
restrict: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);
*/
pthread_mutex_t m1;
int tickets = 100;//设为全局变量
void *selltickets(void *arg){
while(1){
pthread_mutex_lock(&m1);
if(tickets >0){
printf("%ld 正在卖第%d张门票\n",pthread_self(),tickets);
tickets--;
}else{
pthread_mutex_unlock(&m1);
break;
}
pthread_mutex_unlock(&m1);
}
return NULL;
}
int main(){
pthread_t t1,t2,t3;
pthread_mutex_init(&m1,NULL);
pthread_create(&t1,NULL,selltickets,NULL);
pthread_create(&t2,NULL,selltickets,NULL);
pthread_create(&t3,NULL,selltickets,NULL);
//回收子线程资源,阻塞
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_exit(NULL);//退出主线程
pthread_mutex_destroy(&m1);
return 0;
}
死锁
- 有时,一个线程需要同时访问两个或者更多不同的共享资源,而每个资源又都由不同的互斥量管理,当超过一个线程增加锁同一组互斥量时,就有可能发生死锁。
- 两个或者两个以上的线程在执行的过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,此时成为死锁
死锁的几种场景:
忘记释放锁
重复加锁
多线程多锁,抢占资源
读写锁
- 当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住,但考虑一种情况,当前持有互斥锁的线程只是要读取共享资源,而同时其他几个线程也想读取共享资源,但由于互斥的排他性,所有线程都无法获得锁,也就无法访问共享资源了,但实际上多个线程同时访问共享资源并不会导致问题。
- 在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出但只允许一个写入的需求,线程提供了读写锁来实现
- 读写锁特点
-
如果有其他线程读取数据,则允许其他线程执行读操作,但不允许写操作
-
如果有其他线程写数据,则其他线程都不允许读、写操作
-
写是独占的,写的优先级高
读写锁类型:pthread_rwlock_t
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rw)
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<stdio.h>
#include<pthread.h>
#include<unistd.h>
int num = 1;//定义全局变量
pthread_mutex_t mutex;
pthread_rwlock_t rwlock;//创建读写锁
void *writenum(void *arg){//写的函数
while(1){
pthread_rwlock_wrlock(&rwlock);//加读锁
num++;
printf("num++ ,tid: %ld,num:%d\n",pthread_self(),num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
void *readnum(void *arg){
while(1){
pthread_rwlock_rdlock(&rwlock);//加写锁
printf("==read ,tid: %ld,num:%d\n",pthread_self(),num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
int main(){
//pthread_mutex_init(&mutex,NULL);
pthread_rwlock_init(&rwlock,NULL);//初始化读写锁
pthread_t wtids[3];//存放3个写线程号
pthread_t rtids[5];//存放5个读线程号
for(int i=0; i<3; i++){
pthread_create(&wtids[i],NULL,writenum,NULL);//创建写的线程
}
for(int i=0; i<5; i++){
pthread_create(&rtids[i],NULL,readnum,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_rwlock_destroy(&rwlock);//释放读写锁
return 0;
}
生产者消费者模型
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
struct Node{
int num;
struct Node*next;
};
//头结点
struct Node*head = NULL;
void *produce(void *arg){
//不断创建新的节点,添加到链表中
while(1){
//头插法
struct Node *newnode=(struct Node*)malloc(sizeof(struct Node));
newnode->next = head;
head = newnode;
newnode->num = rand()%1000;
printf("Add num,num: %d,tid: %ld\n",newnode->num,pthread_self());
usleep(100);
}
return NULL;
}
void *consume(void *arg){
while(1){
//保存头结点
struct Node *tmp = head;
head =head->next;
printf("del node num: %d,tid:%ld\n",tmp->num,pthread_self());
free(tmp);
usleep(100);
}
return NULL;
}
int main(){
//创建5个生产者线程,个,5消费者线程
pthread_t ptids[5];//5个生产者线程
pthread_t ctids[5];//5个消费者线程
//创建线程
for(int i=0; i<5; i++){
pthread_create(&ptids[i], NULL,produce,NULL);
pthread_create(&ctids[i], NULL,consume,NULL);
}
//线程分离
for(int i=0; i<5; i++){
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1){
sleep(10);
}
pthread_exit(NULL);
return 0;
}
条件变量
*
条件变量的类型:pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond ,pthread_mutex_t *restrict mutex);
---调用该函数,线程会阻塞
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex
,const struct timespec *restrict abstime);
---等待多长时间,调用这个函数,线程会阻塞,直到指定时间结束
int pthread_signal_cond_signal(pthread_cond_t*cond);
int pthread_cond_broadcast(pthread_cond_t*cond);
*/
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
//创建互斥量
pthread_mutex_t mutex;
pthread_cond_t cond;//创建条件变量
struct Node{
int num;
struct Node*next;
};
//头结点
struct Node*head = NULL;
void *produce(void *arg){
//不断创建新的节点,添加到链表中
while(1){
//头插法
pthread_mutex_lock(&mutex);
struct Node *newnode=(struct Node*)malloc(sizeof(struct Node));
newnode->next = head;
head = newnode;
newnode->num = rand()%1000;
printf("Add num,num: %d,tid: %ld\n",newnode->num,pthread_self());
//只要生产了一个,就通知消费者去消费
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
usleep(100);
}
return NULL;
}
void *consume(void *arg){
while(1){
//保存头结点
pthread_mutex_lock(&mutex);
struct Node *tmp = head;
if(head != NULL){//有数据
head =head->next;
printf("del node num: %d,tid:%ld\n",tmp->num,pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
usleep(100);
}else{//没有数据
//当这个函数调用阻塞的时候,会对互斥锁解锁,不阻塞的时候 向下指向会加锁
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}
int main(){
//创建5个生产者线程,个,5消费者线程
pthread_t ptids[5];//5个生产者线程
pthread_t ctids[5];//5个消费者线程
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
//创建线程
for(int i=0; i<5; i++){
pthread_create(&ptids[i], NULL,produce,NULL);
pthread_create(&ctids[i], NULL,consume,NULL);
}
//线程分离
for(int i=0; i<5; i++){
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1){
sleep(10);
}
pthread_exit(NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
信号量
信号量类型:sem_t
int 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_post(sem_t *sem);
-对信号量解锁,信号量的值加1;
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem,const struct timespec*abs_timeout);
int sem_getvalue(sem_t *sem,int *sval);
*/
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
//创建互斥量
pthread_mutex_t mutex;
sem_t psem;
sem_t csem;
struct Node{
int num;
struct Node*next;
};
//头结点
struct Node*head = NULL;
void *produce(void *arg){
//不断创建新的节点,添加到链表中
while(1){
//头插法
sem_wait(&psem);//调用一次减一
pthread_mutex_lock(&mutex);
struct Node *newnode=(struct Node*)malloc(sizeof(struct Node));
newnode->next = head;
head = newnode;
newnode->num = rand()%1000;
printf("Add num,num: %d,tid: %ld\n",newnode->num,pthread_self());
//只要生产了一个,就通知消费者去消费
pthread_mutex_unlock(&mutex);
sem_post(&csem);//消费者信号量加一
}
return NULL;
}
void *consume(void *arg){
while(1){
//保存头结点
sem_wait(&csem);
pthread_mutex_lock(&mutex);
struct Node *tmp = head;
head =head->next;
printf("del node num: %d,tid:%ld\n",tmp->num,pthread_self());
free(tmp);
pthread_mutex_unlock(&mutex);
sem_post(&psem);
}
return NULL;
}
int main(){
//创建5个生产者线程,个,5消费者线程
pthread_t ptids[5];//5个生产者线程
pthread_t ctids[5];//5个消费者线程
sem_init(&psem,0,8);//生产者信号量
sem_init(&csem,0,0);
pthread_mutex_init(&mutex,NULL);
//创建线程
for(int i=0; i<5; i++){
pthread_create(&ptids[i], NULL,produce,NULL);
pthread_create(&ctids[i], NULL,consume,NULL);
}
//线程分离
for(int i=0; i<5; i++){
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1){
sleep(10);
}
pthread_exit(NULL);
pthread_mutex_destroy(&mutex);
return 0;
}