Linux系统编程之线程深度详解(有实例)

线程基础

:什么是多线程

计算机有线程
    计算机: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 

Linux系统编程之线程深度详解(有实例)
可以看到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
Linux系统编程之线程深度详解(有实例)

函数功能:取消一个正在运行的线程
函数原型: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");
}

运行结果:
Linux系统编程之线程深度详解(有实例)
可以看到在第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;
}

运行结果:
Linux系统编程之线程深度详解(有实例)

线程的互斥锁

举个不太恰当的例子:

互斥锁
    什么是互斥
        只能有一方存在
        要么你存在
        要么我存在
        要么你用
        要么我用
    什么是锁
        大门锁
        测试需要维修
        维修人员把门锁了
            进去维修了
        这段时间你能进去上厕所?
        只有当维修人员搞完了
            解锁完毕
        你才能去上厕所
            你上厕所的时候希不希望别去跟你
            一起在厕所上厕所
        你一般会把门锁上

那么互斥锁有什么用呢?
互斥锁就是保证每次只有一个操作在运行。举个例子。

#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去了,可能会出现一些问题?
上面的代码你执行一下会发现:
Linux系统编程之线程深度详解(有实例)
这个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);
    }
  
}

Linux系统编程之线程深度详解(有实例)
可以看到数据没有异常吧。简单理解就是在线程中:

上锁
     代码
解锁

之间的代码必须执行完,这个线程才会被挂起,cpu转去干别的事情。

线程的互斥锁之条件变量

    晚上前更

线程的互斥锁之条件变量

   晚上前更

码累了,晚上前更剩下俩!

上一篇:shell脚本编程技巧几例


下一篇:css布局之三栏布局