线程基础
:什么是多线程
计算机有线程
计算机:8核16线程
迅雷:一次下载多个任务
8核16线程的CPU
跟 8核8线程的CPU有啥区别
8核---8个人---8进程
8线程---8个活---8个main
一个进程干了两个main函数的时
这个概念在哪里出现过呢?
32的FreeRTOS
至始至终有几个主函数?
1个main–1个进程
FreeRTOS的任务
相当于线程
说白点就是在一个主函数中
再开辟多个同时进行的任务(线程)
2:为什么要有线程
/******************************/
有了进程
为什么还非要开辟线程
DHT11 蜂鸣器 LED
3个进程
1个进程3个线程
进程的创建需不需要占用系统资源
空间、CPU复制资源
PID
线程是在一个进程中创建
相对来说,线程启动的更加快
创建的也快
线程的切换也是大大的快于进程的切换
进程的通信:
管道
信号量
消息邮箱
共享内存
线程间的通信:
FreeRTOS:
全局变量
不管哪个单片机
上的单片机微操作系统
通信都是全局变量
更简单
同步和互斥
*******************************************************
线程创建函数:
int pthread_create(pthread_t * thread,
const pthread_attr_t * attr,
void *(*start_routine)(void*), void *arg);
thread:线程的ID
attr:线程的属性默认直接给NULL
start_routine:函数指针
参数 void *
返回值 void *
arg : 传给线程函数的参数
*********************************************************
线程正常结束函数:
pthread_exit(void *value_ptr);
value_ptr可传值,pthread_join的第二个参数接收
********************************************************
等待线程结束函数:
int pthread_join(pthread_t thread, void **value_ptr);
thread:等待的线程ID
void **value_ptr:
线程正常退出时返回的参数,默认可NULL,也可搭配pthread_exit用
okk我们先创建两个线程,分别让他们循环输出:
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int main(void)
{
pthread_t pthread_id[2];
//char *buf;
pthread_create(&pthread_id[0],NULL,text1,NULL);
pthread_create(&pthread_id[1],NULL,text2,NULL);
pthread_join(pthread_id[0],NULL);//(void **)&buf
pthread_join(pthread_id[1],NULL);
//printf("%s\n",buf);
return 0;
}
void * text1(void * arg)
{
for(int i=0;i<300;i++)
{
/*if(i==15)
{
pthread_exit("hi");
}*/
printf("this is pthread1\n");
}
}
void * text2(void * arg)
{
for(int i=0;i<300;i++)
{
printf("this is pthread2\n");
}
}
编译运行:
gcc pthread_text.c -lpthread
./a.out
可以看到1,2是穿插一起的,这就是线程的并行,并不是一个线程运行完了再去运行另一个线程,Linux是分时系统。是1线程跑一会,2线程跑一会,没有规律的。
现在我们已将创建和等待结束函数测试完毕了,下面我们看一下pthread_exit函数:还是上面的代码,我让1线程循环15次后正常结束,将结束内容设置为hi,pthread_join第二个参数接收。
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int main(void)
{
pthread_t pthread_id[2];
char *buf;
pthread_create(&pthread_id[0],NULL,text1,NULL);
pthread_create(&pthread_id[1],NULL,text2,NULL);
pthread_join(pthread_id[0],(void **)&buf);//注意数据类型
pthread_join(pthread_id[1],NULL);
printf("%s\n",buf);
return 0;
}
void * text1(void * arg)
{
for(int i=0;i<300;i++)
{
if(i==15)
{
pthread_exit("hi");
}
printf("this is pthread1\n");
}
}
void * text2(void * arg)
{
for(int i=0;i<300;i++)
{
printf("this is pthread2\n");
}
}
运行结果你会发现,线程1打印15次后就结束了,主函数输出线程1结束内容hi
函数功能:取消一个正在运行的线程
函数原型:int pthread_cancel(pthread_t thread);
函数参数:
thread:你需要取消的线程ID
函数返回值:
0 成功
-1 失败
***********************************************************
这个函数就不演示了。。。。一看就明白咋用了把。
咳咳。。。。。下面会用到!
函数功能:设置一个清理函数
void pthread_cleanup_push(void (*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);
这两个函数必须成对出现
参数:
routine:注册清理的函数的指针
arg:传递给清理函数的参数
execute:决定这个清理函数是否被调用
0--不调用
非0-调用
注意:采用先入后出的栈结构管理,两个函数之间的程序段中的终止动作
(包括调用pthread_exit()和异常终止(其他进程使用pthread_cancel取
消当前进程) 不包含return)都将执行
上代码:
创建俩线程,每个线程循环15次打印,线程1在第8次时候调用pthread_cancel杀死这个进程。调用清理函数
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);//线程1
void * text2(void * arg);//线程2
void * clean(void * arg);//清理函数
int main(void)
{
pthread_t pthread_id[2];
pthread_create(&pthread_id[0],NULL,text1,NULL);
pthread_create(&pthread_id[1],NULL,text2,NULL);
pthread_join(pthread_id[0],NULL);
pthread_join(pthread_id[1],NULL);
return 0;
}
void * text1(void * arg)
{
pthread_cleanup_push(clean,NULL);//注册清理函数
for(int i=0;i<15;i++)
{
if(i==7)
{
pthread_cancel(pthread_self());
}
printf("this is pthread1\n");
}
pthread_cleanup_pop(1);//调用清理函数
}
void * text2(void * arg)
{
for(int i=0;i<15;i++)
{
printf("this is pthread2\n");
}
}
void * clean(void * arg)
{
printf("我是线程1的清理函数\n");
}
运行结果:
可以看到在第8次时候,调用pthread_cancel杀死了1线程,进入了清理函数。有人会问这个清理函数有什么用呢?加入你这个线程死于异常,会不会照成数据的异常,比如你在这个线程中用到了全局变量,那么这个全局变量因为线程的不正常退出,保存的内容是垃圾值。会不会对你程序照成严重的影响呢?你的全局变量肯定不可能是就这一个线程用吧,万一另一个线程需要这个异常线程全局变量中保存的结果呢。哪整个程序是不是就出现了大错误了,如果使用清理函数,我在清理函数里面将这个全局变量给初始化为0,在调用的地方加个判断,是不是可以避免很多问题????
okk…我们继续下面的讲解。
线程间的通信
线程中的通信,完全可以用进程中的IPC通信方法经行通信。
不是特别情况下一般不用。
一个全局变量解决所有问题。
我们讲一个还算常用的吧:线程中的信号通信
信号:
在多线程中你的信号无论在哪个线程中注册
只要该进程得到信号都会去处理
信号的处理函数:
pthread_kill(线程id,信号)
上代码:
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
void hello(int num)
{
printf("hello fun!!!\n");
}
void *fun(void *data)
{
signal(3,hello);
while(1)
{
printf("fun!!!\n");
sleep(1);
}
return NULL;
}
//main线程 子线程
int main()
{
pthread_t id;//unsigned long
pthread_create(&id,NULL,fun,NULL);
while(1)
{
sleep(1);//有时间去注册信号操作函数
pthread_kill(id,3);
}
pthread_join(id,NULL);
return 0;
}
运行结果:
线程的互斥锁
举个不太恰当的例子:
互斥锁
什么是互斥
只能有一方存在
要么你存在
要么我存在
要么你用
要么我用
什么是锁
大门锁
测试需要维修
维修人员把门锁了
进去维修了
这段时间你能进去上厕所?
只有当维修人员搞完了
解锁完毕
你才能去上厕所
你上厕所的时候希不希望别去跟你
一起在厕所上厕所
你一般会把门锁上
那么互斥锁有什么用呢?
互斥锁就是保证每次只有一个操作在运行。举个例子。
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int i=0;
int main(void)
{
pthread_t pthread_id[2];
//char *buf;
pthread_create(&pthread_id[0],NULL,text1,NULL);
pthread_create(&pthread_id[1],NULL,text2,NULL);
pthread_join(pthread_id[0],NULL);//(void **)&buf
pthread_join(pthread_id[1],NULL);
//printf("%s\n",buf);
return 0;
}
void * text1(void * arg)
{
while(i<1000)
{
i++;
printf("text1 i=%d\n",i);
}
}
void * text2(void * arg)
{
while(i<1000)
{
i++;
printf("text2 i=%d\n",i);
}
}
我定义了一个全局变量i,在1、2两个线程中做++,假如在没有互斥锁的条件下,这两个线程就没法保证一次循环的完整。
比如:
text1
i++;
printf("text1 i=%d\n",i);
text2:
i++;
printf("text1 i=%d\n",i);
可能text1运行一半又跑去运行text2去了,可能会出现一些问题?
上面的代码你执行一下会发现:
这个2是怎么来的呢?16去哪里了呢。是不是程序就出现了bug。
我们如何保证每一次每个线程都完整运行完才可以进行跳转呢。
这就是需要互斥锁了。
互斥锁的创建:
动态创建:
pthread_mutex_init();
参数1:参数1:描述锁的变量 pthread_mutex_t mutex;
参数2:PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
函数传入值 Mutexattr
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁
NULL默认快速互斥锁
静态创建:
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//静态创建快速
互斥锁
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);//成功就加锁
成功不成功就等待
int pthread_mutex_trylock(pthread_mutex_t *mutex);成功就加
锁,不成功就返回错误----非阻塞
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
销毁锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
重点提示:
小心死锁:在同一个线程中,第一次lock,没问题,然后没有unlock,又
lock一次,会阻塞,且一直阻塞下去
下面我们用锁来经行代码保护:
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int i=0;
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
int main(void)
{
pthread_t pthread_id[2];
//char *buf;
pthread_create(&pthread_id[0],NULL,text1,NULL);
pthread_create(&pthread_id[1],NULL,text2,NULL);
pthread_join(pthread_id[0],NULL);//(void **)&buf
pthread_join(pthread_id[1],NULL);
//printf("%s\n",buf);
return 0;
}
void * text1(void * arg)
{
while(i<1000)
{
pthread_mutex_lock(&lock1);
i++;
printf("text1 i=%d\n",i);
pthread_mutex_unlock(&lock2);
}
}
void * text2(void * arg)
{
while(i<1000)
{
pthread_mutex_lock(&lock2);
i++;
printf("text2 i=%d\n",i);
pthread_mutex_unlock(&lock1);
}
}
可以看到数据没有异常吧。简单理解就是在线程中:
上锁
代码
解锁
之间的代码必须执行完,这个线程才会被挂起,cpu转去干别的事情。
线程的互斥锁之条件变量
首先我们来看一下条件变量的含义
当一个条件没有满足会阻塞程序,满足条件够可以解除阻塞
创建方式:
静态方式:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态方式:int pthread_cond_init(pthread_cond_t *restrict
cond,const pthread_condattr_t *restrict attr);
条件阻塞:int pthread_cond_wait(pthread_cond_t *restrict
cond, pthread_mutex_t *restrict mutex);
条件解除阻塞: int pthread_cond_signal(pthread_cond_t *cond);
销毁:int pthread_cond_destroy(pthread_cond_t *cond);
示例:
创建两个线程,创建一个全局变量i,当i<10时在1线程中阻塞当前线程,2线程做++,当在2线程中i>10时解除阻塞。
代码:
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
#include "signal.h"
#include "time.h"
void * text1(void * arg);//线程1
void * text2(void * arg);//线程2
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lunk;
int i=0;
int main(void)
{
pthread_t pthread_id[2];
pthread_create(&pthread_id[0],NULL,text1,NULL);
pthread_create(&pthread_id[1],NULL,text2,NULL);
pthread_join(pthread_id[0],NULL);
pthread_join(pthread_id[1],NULL);
return 0;
}
void * text1(void * arg)
{
for(i=0;i<15;i++)
{
printf(" i=%d\n",i);
if(i<10)
{//阻塞
printf("i<10阻塞\n");
pthread_cond_wait(&cond,&lunk);
}
printf("i>10阻塞解除\n");
}
}
void * text2(void * arg)
{
while(1)
{ i++;
sleep(1);
if(i>10)
{
pthread_cond_signal(&cond);
break;
}
}
}
运行结果: