linux线程篇详细且附上代码

目录

一、线程的概念:

查看指定线程的LWP号:

(1)线程的创建:

(2)单个线程退出 --pthread_exit

(3)退出进程的方法:

1、exit(0):退出整个进程

3、阻塞等待线程退出,获取线程退出状态--pthread_join

4、线程分离--pthread_detach:

5、杀死(取消)线程--pthread_cancel :

7、线程的分离属性

二、线程同步:

三、互斥量(互斥锁):

1.互斥锁类型:

2.互斥锁的特点:多个线程访问共享数据的时候是串行的

3.使用互斥锁缺点 :效率低

4.互斥锁使用的步骤 :

5.互斥锁相关函数:

6、原子操作:

7、死锁:

四、读写锁 :

1.读写锁是几把锁?

2.读写锁的类型

3、读写锁的特性:

4、.读写锁场景练习

5、.主要操作函数

6、读写锁demo:

五、条件变量 :

如何才能同步线程?

1.条件变量是锁吗?

2.条件变量的两个动作

3.条件变量的类型

4.主要函数:

5、练习 :使用条件变量实现生产者,消费者模型

六、信号量

1.头文件:#include

4.练习:使用信号量实现生产者,消费者模型


一、线程的概念:

 

主线程和子线程共享:
.text
.bss
.data
动态加载区
环境变量
命令行参数
-通信:全局变量,堆
不共享
一共五个线程,栈区被平均分成五块
Linux 下: 线程就是进程 - 轻量级进程
对于内核来货,线程就是进程
多进程和多线程的区别:
多进程: 始终共享的资源 代码文件描述符、内存映射区 --mmap
多线程:始终共享的资源:堆、全局变量,节省资源

查看指定线程的LWP号:

线程号和线程 ID 是有区别的
线程号是给内核看的
查看方式
找到程序的进程 ID
ps -Lf pid

(1)线程的创建:

1.创建线程‐‐pthread_create
int pthread_create( pthread_t *thread), //线程ID = 无符号长整型
const pthread_attr_t *attr, //线程属性NULL
void *(*start_routine)(void *), //线程处理函数
void *arg); //线程处理函数
参数:
pthread:传出参数,线程创建成功之后,会被设置一个合适的值
attr:认传NULL
start_routine:子线程的处理函数
arg: 回调函数的参数
返回值:
成功:0
错误:错误//perror不能使用该函数打印错误信息
主线程先退出,子线程会被强制结束
验证线程直接共享全局变量
Demo:
vi pthread_create.c
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>

void* child_handle(void *arg)
{
	printf("child pthread id : %ld\n",pthread_self());
	return 0;
}
int main()
{
	pthread_t  phid;
	int ret;
	
	ret=pthread_create(&phid,NULL,child_handle,NULL);
	if( ret ! = 0)
	{
		printf("error number is %d\n",ret);
		printf("%s \n",strerror(ret));  //strerror函数返回错误信息是一个字符串
	}
	printf("parent pthread id : %ld\n",pthread_self());
	for(int i=0 ; i<5;i++)
	{
		printf("i = %d\n",i);
	}
	sleep(2);
	return 0;


}

gcc pthread_create.c  -o pthcreate -lpthread    (链接一个库)

(2)单个线程退出 --pthread_exit

函数原型: void pthread‐exit(void *retval)
retval指针:必须指向全局,

(3)退出进程的方法

1、exit(0):退出整个进程

2、pthread_exit(NULL);   //退出单个进程 

3、通过自定义函数退出

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
void* my_exit()
{
	return NULL;
}
void* child_handle(void *arg)
{
	printf("child pthread id : %ld\n",pthread_self());
	for(int i =0; i<5; i++)
	{
		printf("child pthread i =%d\n", i);
		if( i = 2)
		{ 
			//exit child pthread
			//exit(0);   //退出整个进程
			pthread_exit(NULL);   //退出单个进程 
			//my_exit();//通过函数退出
		}

	}

	return 0;
}
int main()
{
	pthread_t  phid;
	int ret;
	
	ret=pthread_create(&phid,NULL,child_handle,NULL);
	if( ret ! = 0)
	{
		printf("error number is %d\n",ret);
		printf("%s \n",strerror(ret));  
	}
	printf("parent pthread id : %ld\n",pthread_self());
	pthread_exit(NULL);
	for(int i=0 ; i<5;i++)
	{
		printf("parent pthread i= %d\n", i);
	}
	pthread_exit(NULL);
	sleep(2);
	return 0;


}

3、阻塞等待线程退出获取线程退出状态--pthread_join

函数原型: 
int pthread_join(pthread_t pthread, void **retval) 
参数:   pthread:要回收的子线程的ID 
        retval:读取线程退出的携带信息 
传出参数
    void* ptr; 
    pthread_join(pthid,&ptr);
    指向的内存和pthread_exit参数指向地址一致

4、线程分离--pthread_detach:

函数原型:int pthread_datach(pthread_t thread);
调用函数之后不需要 pthread_join
子线程会自动回收自己的PCB

5、杀死(取消)线程--pthread_cancel

函数原型: int pthread_cancel(pthread_t pthread);
使用注意事项:
在要杀死的子线程对应的处理的函数的内部,必须做过一次系统调用
write read printf
pthread_testcancel();设置取消点
6、比较两个线程 ID 是否相等(预留函数 --pthread_equal
函数原型:
int pthread_equal(pthread_t t1,pthread_t t2);

7、线程的分离属性

通过属性设置线程的分离
1.线程属性类型: pthread_attr_t attr;
2.线程属性操作函数
对线程属性变量的初始化
int pthread_attr_init(pthread_attr_t* attr);
设置线程分离属性
int pthread_attr_setdetachstate(
pthread_attr_t* attr,int detachstate
);
参数:
attr : 线程属性
detachstate
PTHREAD_CREATE_DETACHED(分离)
PTHREAD_CREATE_JOINABLE(非分离)
释放线程资源函数
int pthread_attr_destroy(pthread_attr_t* attr)
设置线程分离demo:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>

void* child_handle(void *arg)
{
	printf("child pthread id : %ld\n",pthread_self());
	return 0;
}
int main()
{
	pthread_t  phid;
	int ret;
	//init attr
	 pthread_attr_t   attr;
	pthread_attr_init(&attr);
	//set attr
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	ret=pthread_create(&phid,NULL,child_handle,NULL);
	if( ret ! = 0)
	{
		printf("error number is %d\n",ret);
		printf("%s \n",strerror(ret));  //strerror函数返回错误信息是一个字符串
	}
	printf("parent pthread id : %ld\n",pthread_self());
	for(int i=0 ; i<5;i++)
	{
		printf("i = %d\n",i);
	}
	sleep(2);
	pthread_attr_destroy(&attr);//释放线程资源
	return 0;

}

二、线程同步:

为什么要有线程同步?

数据混乱:
操作了共享资源
cpu调度问题
解决
线程同步
什么是同步?
协同步调,按照先后顺序操作执行

三、互斥量(互斥锁):

如果我们想要使用互斥锁同步线程所以线程都需要加锁

1.互斥锁类型

创建一把锁: pthread_mutex_t mutex;

2.互斥锁的特点:多个线程访问共享数据的时候是串行的

3.使用互斥锁缺点 效率低

4.互斥锁使用的步骤

创建互斥锁: pthread_mutex_t mutex;
初始化:pthread_mutex_init(&mutex,NULL); -- mutex = 1
找到线程共同操作的共享数据
加锁:操作共享资源之前加锁,pthread_mutex_lock(&mutex); //阻塞 --mutex = 0
pthread_mutex_trylock(&mutex); // 如果锁上锁直接返回,不阻塞
XXXXXX共享数据操作 //临界区 ,越小越好
解锁: pthread_mutex_unlock(&mutex); // -- mutex = 1
阻塞在锁上的线程会被唤醒
销毁:pthread_mutex_destory(&mutex)

5.互斥锁相关函数

初始化互斥锁
pthread_mutex_init(
pthread_mutex_t* restrict mutex,
const pthread_mutexattr_t* restrict attr,
);
销毁互斥锁:
pthread_mutex_destory(pthread_mutex_t* mutex );
加锁
pthread_mutex_lock(pthread_mutex* mutex)
mutex:
没有被锁上,当前线程会将这把锁锁上
被锁上了:当前线程阻塞,锁被打开之后,线程解除阻塞
尝试加锁,失败返回,不阻塞
pthread_mutex_trylock(pthread_mutex_t* mutex);
没有锁上:当前线程会被这把锁加锁
如果锁上了:不会阻塞,返回
返回0:加锁 成功。没锁上:返回错误
if( pthread_mutex_trylock(& mutex)==0) 
{ 
    //尝试加锁,并且成功了 
    //访问共享资源
     XXXXXXXX 
}
else 
{ 
    //错误处理 
   //或者等待,再次尝试加锁
 }
解锁:
pthread_mutex_unlock(pthread_mutex_t* mutex)
使用互斥锁:
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#define MAX 10000
int number=0;
//create mutex
pthread_mutex_t  mutex;
//线程A没有将number写到内存中
void* funA(void* arg)
{
	for(int i =0; i<MAX; i++)
	{
		//lock,加锁
		pthread_mutex_lock(&mutex);
		int ret = number;
		ret ++;
		number = ret ;
		printf(" funA id is %ld ,number  is %d\n",pthread_self(),number);
		//解锁
		pthread_mutex_unlock(&mutex);
		usleep(10);
	}

}

void* funB(void* arg)
{
       for(int i =0; i<MAX; i++)
	{
		//lock,加锁
		pthread_mutex_lock(&mutex);
		int ret = number;
		ret ++;
		number = ret ;
		printf(" funB id is %ld ,number  is %d\n",pthread_self(),number);
		//解锁
		pthread_mutex_unlock(&mutex);
		usleep(10);
	}
}
//结果执行到20000
int main()
{
	//init mutex
	pthread_mutex_init(&mutex,NULL);
	pthread_t   pth1,pth2;
	pthread_create(&pth1,NULL,funA,NULL);
	pthread_create(&pth2,NULL,funB,NULL);
	
	pthread_join(pth1,NULL);
	pthread_join(pth2,NULL);
	pthread_mutex_destory(&mutex);
	return 0;
}

6、原子操作:

cpu处理一个指令,进程/线程 在处理完这个指令之前是不会失去cpu
就像原子被认为是不可分割颗粒一样
// 示例代码 1
void* procucer(void * arg)
{
while(1)
{
// 创建一个链表的节点
Node * newNode = (Node*)malloc(sizeof(Node));
//init
newNode‐>data = rang()%100;
newNode ‐>next = head;
head = newNode;
printf("+++product:%d\n",newNode‐>data);
sleep(rang()%3);
}
reutn NULL;
}
// 示例代码 2
void* procucer(void * arg)
{
while(1)
{
// 创建一个链表的节点
Node * newNode = (Node*)malloc(sizeof(Node));
//init
newNode‐>data = rang()%100;
pthread_mutex_lock(&mutex);
newNode ‐>next = head;
head = newNode;
printf("+++product:%d\n",newNode‐>data);
pthread_mutex_unlock(&mutex);
sleep(rang()%3);
}
reutn NULL;
}

7、死锁:

造成死锁的原因:
1. 自己锁自己
for(int i = 0;i<MAX;i++)
{
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
int crt = number;
crt++;
number = crt;
printf("thread A id = %ld,number = %d\n",pthread_self(),number);
pthread_mutex_unlock(&mutex);
usleep(10);
}
//操作做完之后,一定要解锁
2、
线程 1 对共享资源 A 加锁成功 -A
线程 2 对共享资源 B 加锁成功 -B
线程 1 访问共享资源 B ,对 B 锁加锁 - 线程 1 阻塞在 B 锁上
线程 2 访问共享资源 A ,对 A 锁加锁 - 线程 2 阻塞在 A 锁上

 

如何解决
- 让线程按照一定的顺序去访问共享资源 - 在访问其他锁的时候,需要先将自己的锁解开

四、读写锁

1.读写锁是几把锁?

一把锁
pthread_rwlock_t lock;

2.读写锁的类型

读锁-对内存做读操作
写锁-对内存做写操作

3、读写锁的特性:

线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功
读共享-并行处理
线程A加写锁成功,又来了三个线程,做读操作,三个线程阻塞
写独占
线程A加读锁成功,又来了B线程加写锁阻塞,又来了C线程加读锁阻塞
读写不可以同时进行
写的优先级高

4、.读写锁场景练习

线程A加写锁成功,线程B请求读锁
线程B阻塞
线程A持有读锁,线程B请求写锁
线程B阻塞
线程A拥有读写,线程B请求读锁
线程B加锁
线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
线程B阻塞,线程C阻塞
线程B加锁,线程C阻塞
线程C加锁
线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
线程B阻塞,线程C阻塞
线程C加锁, 线程B阻塞
线程B加锁

5、.主要操作函数

初始化读写锁
pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,
const pthread_rwlockattr_t* restrict attr );
销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t* rwlock):
加读锁
pthread_rwlock_rdlock(pthread_rwlock_t* rdlock);
阻塞:之前对这把锁加的是写锁的操作
尝试加读锁
pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
加锁成功:返回0
失败:返回错误
加写锁
pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
阻塞:上一次加写锁还没解锁
阻塞:上一次加读锁还没解锁
尝试加写锁
pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
解锁
pthread_rwlock_unlock(pthread_rwlock_t* rwlock)

6、读写锁demo:


/*
练习读写锁:
三个线程不定时写同一个全局变量
五个线程不定时期读同一全局资源 
*/

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
int number=1000;
//creat rwlock
pthread_rwlock_t  lock;

void* write_fun(void* arg)
{
	while(1)
	{
		//加锁
		pthread_rwlock_wrlock(&lock);
		number++;
		printf("write :%ld ,%d\n",pthread_self(),number);
		//解锁
		pthread_rwlock_unlock(&lock);
		usleep(500);    //500ms
	}
}

void* read_fun(void* arg)
{
	while(1)
	{
		pthread_rwlock_rdlock(&lock);
		
		printf("read :%ld ,%d\n",pthread_self(),number);
		pthread_rwlock_unlock(&lock);
		usleep(500);  
	}
}

int main()
{
	pthread_rwlock_init(&lock ,NULL);
	pthread_t  p[8];
	//创建3个写的线程
	for (int i =0;i<3;i++)
	{
		pthread_create(&p[i],NULL,write_fun,NULL);
	}
	//创建5个读的线程
	for (int i =3;i<8;i++)
	{
		pthread_create(&p[i],NULL,read_fun,NULL);
	}
	//回收线程
	for (int i= 0; i<8; i++)
	{
		pthread_join(p[i],NULL);
	}	
	//销毁读写锁
	pthread_rwlock_destroy(&lock);	
	return 0;
}

五、条件变量

条件变量作用:阻塞线程(不是什么时候都能阻塞线程)
链表头节点
Node*head = NULL;
while(head == NULL)
{
// 我们想让代码在这个位置阻塞
// 等待链表中有了节点之后再继续向下运行
// 使用到了后面要讲的条件变量 阻塞线程
}
// 链表不为空的处理代码
xxxx

如何才能同步线程?

使用条件变量+互斥锁

1.条件变量是锁吗?

不是锁 ,但是条件变量能够阻塞线程
使用条件变量 + 互斥量
互斥量:保护一块共享数据
条件变量:引起阻塞
生产者和消费者模型

2.条件变量的两个动作

条件不满足,阻塞线程
当条件满足,通知阻塞的线程开始工作

3.条件变量的类型

pthread_cond_t cond ;

4.主要函数

初始化一个条件变量
pthread_cond_init(pthread_cond_t * restrict cond,
const pthread_condattr_t * restrict attr
);
销毁一个条件变量
pthread_cond_destroy(pthread_cond_t * cond);
阻塞等待一个条件变量
pthread_cond_wait(
pthread_cond_t *restrict cond,
pthread_mutex_t * restrict mutex
)
阻塞线程
将已经上锁的mutex瞬间解锁
函数解除阻塞,对互斥锁加锁限时等待一个条件变量
pthread_cond_timedwait(
pthread_cond_t * restrict cond,
pthread_mutex_t * restrict mutex,
const struct timespec * restrict abstime
)
唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t* cond);
唤醒全部阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t * cond)

5、练习 使用条件变量实现生产者,消费者模型

 

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
typedef struct node
{
	int data;
	struct node* next;
}Node_t;

//create head node
Node_t* head = NULL;
//create mutex
pthread_mutex_t  mutex;
//create cond
pthread_cond_t cond;

//生产者
void *produce(void *arg)
{
	while(1)
	{
		//create node
		Node_t *pnew =(Node_t*)malloc(sizeof(Node_t));
		//init node
		pnew->data=rand()%1000;  //0~999
		//lock
		pthread_mutex_lock(&mutex);
		pnew->next=head;
		head=pnew;
		printf(" produce(生产者) : %ld ,data is%d\n",pthread_self(),pnew->data);
		//unlock
		pthread_mutex_unlock(&mutex);
		pthread_cond_signal(&cond);
		sleep(rand()%3);

	}
	return NULL;
}
//消费者
void *customer(void *arg)
{
	while(1)
	{
		//lock
		pthread_mutex_lock(&mutex);
		if(head == NULL)
		{
			//continue;
			//阻塞等待
	         		pthread_cond_wait( &cond,&mutex );
		}
		//delete head node
		Node_t *pdel=head;
		head =head->next;
		printf("customer (消费者): %ld, data is %d\n",pthread_self(),pdel->data);
		free(pdel);
		//unlock
		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}
int main()
{
	pthread_mutex_init(&mutex,NULL);
	pthread_t  p1,p2;
	pthread_create(&p1,NULL,produce,NULL);
	
	pthread_create(&p2,NULL,customer,NULL);
	pthread_join(&p1,NULL);
	pthread_join(&p2,NULL);
	return 0;	
}

六、信号量

1.头文件:#include<semaphore.h>

2、信号量类型:

sem_t     sem;
加强版的互斥锁
3. 主要函数
初始化信号量
sem_init(sem_t *sem,int pshared,unsigned int value);
0-线程同步1-进程同步
value-最多有几个线程操作共享数据
销毁信号量
sem_destroy(sem_t *sem);
加锁
sem_wait(sem_t *sem);
调用一次相当于对sem做了一次 -- 操作
如果sem值为0,线程会阻塞
尝试加锁
sem_trywait(sem_t *sem);
sem == 0;加锁失败,不阻塞,直接发牛
限时尝试加锁
sem_timewait(sem_t *sem,xxxx);
解锁++
sem_post(sem_t *sem);
sem做了++ 操作

4.练习:使用信号量实现生产者,消费者模型

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<semaphore.h>
#include<unistd.h>

sem_t producer_sem;
sem_t  customer_sem;
typedef struct node
{
	int data;
	struct node* next;
}Node;

Node* head =NULL;
void *producer(void *arg)
{
	while(1)
	{
		sem_wait(&producer_sem);  // --- 0
		Node* node=(Node*)malloc(sizeof(Node));
		node->data=rand()%1000;
		node->next=head;
		head=node;
		printf("producer(生产者):  %ld ,  %d\n",pthread_self(),node->data);
		sem_ppost(&customer_sem);    //customer++
		sleep(rand()%5);
	}
	return NULL;
}
void *customer(void *arg)
{
	while(1)
	{
		sem_wait(&customer_sem);
		Node* del =head;
		head=head->next;
		printf("customer(消费者):  %ld ,  %d\n",pthread_self(),node->data);
		free(del);
		sem_ppost(&producer_sem);   //producer++
		sleep(rand()%5);
	}
	
	return NULL;
}
int main()
{
	pthread_t  thid[2];
	sem_init(&producer_sem,0,4);  

	sem_init(&customer_sem,0,0);  
	pthread_create(&thid[0],NULL,produce,NULL);
	
	pthread_create(&thid[1],NULL,customer,NULL);
	for(int  i =0;i <2; i++)
	{
		pthread_join(thid[i],NULL);
	}
	sem_destroy(&producer_sem);
	sem_destroy(&customer_sem);
	return 0;
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...