程序员第二节:多线程
第二节:多线程的定义及使用
学习目录
前言
昨日烟雨淅沥,往事浮沉,如今悄然无声。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是多线程?
定义
多线程通俗来讲既是在一个进程中的多个执行路线,她是进程内部的一个控制序列,且所有进程中至少有一个执行线路。
多线程有哪些优点:
1:创建一个线程不需要分配单独的空间。
2:多线程之间由于是共享一块地址空间,所以线程之间方便通讯,可以做到数据共用。
3:线程之间的切换时间远远小于进程之间的切换。
4:将耗时操作置于某个线程,可以提高应用程序响应。
5:使cpu系统更加有效:当线程小于cpu总数时,不同的线程运行于不同的CPU上。
6:改善程序结构:复杂的进程分为多个线程,有利于程序的理解和修改
二、使用步骤
1.引入库
在LINUX下编写程序,需要链接使用库libpthread.a,链接方法如下。
由于pthrea的库不是linux的系统库,编译的时候需要加上 -lpthread:
#indclude <pthread.h>//库
host# vim hello.c//写一个线程程序
host# gcc hello.c -lpthread;//编译指令
host# ./a.out//执行程序
2.创建一个线程
#include <stdio.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
printf("this is a thread_fun\n");
return 0;
}
int main()
{
pthread_t rid;
int reselt;
//pthread_create(线程唯一表示符,线程属性,函数,函数的参数);成功返回0;
if((reselt=pthread_create(&rid,NULL,thread_fun,NULL))!=0)
return 0;
printf("%d\n",reselt);//线程创建成功这里reselt=0;
return 1;
}
我们来看一下运行结果
函数thread_fun并没有打印,但返回值为0,说明线程创建成功了,这是怎么回事呢?让我们改变一下代码:
#include <stdio.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
printf("this is a thread_fun\n");
return 0;
}
int main()
{
pthread_t rid;
int reselt;
if((reselt=pthread_create(&rid,NULL,thread_fun,NULL))!=0)
return 0;
printf("%d\n",reselt);//线程创建成功这里reselt=0;
while(1);//主线程不退出
return 1;
}
当我们给主函数(主线程)加上一个循环等待,让他不退出,我们发现子线程终于执行了打印
这说明,子线程会随着主线程的退出而退出。
3:线程传参
pthread_create()函数的最后一个参数为要传入的参数,类型是指针,子线程中用void *类型的指针接收,所以我们需要进行指针的强制转换。
#include <stdio.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
printf("this is a thread_fun:%d\n",*(int *)arg);
*(int *)arg=90;
return 0;
}
int main()
{
pthread_t tid;
int a=3;
int reselt=pthread_create(&tid,NULL,thread_fun,&a);
while(a==3);
printf("%d\n",a);
while(1);
return 1;
}
运行结果:this is a thread_fun=3;90;
可以看出,子线程可以接收并更改子线程的数据。
4:线程的正常退出方式
1:使用return;返回
2:使用pthread_exit();
void *thread_fun(void *arg)
{
printf("this is a thread_fun:%d\n",*(int *)arg);
pthread_exit((void *)0);//正常退出线程
}
5:线程的返回
函数原型:int pthread_join(pthread_t tid,void **pva)
作用:通俗来讲就是等待另一个线程的结束,比如主线程中使用了该该函数,那么直到被等待子线程退出主线程才会退出。
pva:返回值指针
成功返回0,失败返回错误代码。
当函数返回时,被等待线程的资源被收回。如果线程已经结束,该函数立即返回。并且thread指定的程序必须是joinable的。
#include <stdio.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
printf("this is a thread_fun:%d\n",*(int *)arg);
*(int *)arg=90;
return 0;
}
int main()
{
pthread_t tid;
void *userp;//返回值指针
int a=3;
int reselt=pthread_create(&tid,NULL,thread_fun,&a);
pthread_join(tid,uesrP);//代替了之前的while(1)后结果依然相同
printf("%d\n",a);
return 1;
}
可以看出,主线程子线程一同正常退出
6:线程的取消
函数原型:int pthread_cancel(pthread_t tid)
这个函数比较好理解,该语句通过传入参数:线程ID来取消该线程的运行
7:线程间的共享资源保护
一:互斥锁(互斥量)MUtex
通俗理解:如果共享空间内有一段连续的内存空间,现在A线程要对其进行操作,A线程在操作的过程中释放了连续内存中的某一段。于此同时,B线程也找到了该连续的内存空间,也要对其操作,但B线程并不知道线程A已经释放了其中一个空间,从而,线程B在操作到这块空间的时候,就会产生很严重的程序错误,所以此时产生了互斥量的概念,他本质上就像一把锁,保护共享资源。
操作步骤:
初始化 : pthread_mutex_init
上锁:pthread_mutex_lock
判断上锁:pthread_mutex_unlock
解锁:pthread_mutex_unlock
消除互斥锁:pthread_mutex_destory
//相关函数原型
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexatter_t *attr)//mutex:创建成功后的互斥量 attr创建属性(NULL)
int pthread_mutex_destroy(pthread_mutex_t *mutex)
int pthread_mutex_lock(pthread_mutex_t *mutex)//阻塞模式,等待
int pthread_mutex_try(pthread_mutex_t*mutex)//非阻塞模式,有锁直接返回错误
int pthread_mutex_unlock(pthread_mutex_t *mutex)//在操作完成后必须对互斥量进行解锁,否则会造成死锁
下面看一组简单的代码演示
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
int mutex_return=0;
void *thread_fun(void *arg)
{
mutex_return=pthread_mutex_lock(&mutex);//线程1加锁
printf("fun=%d/n",mutex_return);
printf("this is a thread_fun:%d\n",*(int *)arg);
*(int *)arg=90;//对共享内存进行处理
pthread_mutex_unlock(&mutex);
return 0;
}
void *thread_fun2(void *arg)
{
mutex_return=pthread_mutex_lock(&mutex);//线程2加锁
*(int *)arg=50;//对共享内存进行处理
printf("this is a fun2\n");
pthread_mutex_unlock(&mutex);
pthread_exit((void *)0);
}
int main()
{
pthread_t tid,tid2;
void *userP;
int a=3;
mutex_return=pthread_mutex_init(&mutex,NULL);
printf("mutex_return=%d\n",mutex_return);
int reselt=pthread_create(&tid,NULL,thread_fun,&a);
int reselt2=pthread_create(&tid2,NULL,thread_fun2,&a);
pthread_join(tid,userP);
pthread_join(tid2,userP);
printf("%d\n",a);
pthread_mutex_destroy(&mutex);//释放互斥量
return 1;
}
上面的程序为:在主程序中创建了两个线程,在线程中修改a的值,pthread_mutex_lock的阻塞特性,一个先上锁的线程会先执行,而后上锁的线程会处于等待状态,直到先上锁的线程解锁为止。
二:信号量semaphore
信号量和互斥量的区别
1:互斥量上锁后只能由上锁的线程解锁释放,信号量可以由任何线程释放
2:信号量的初始值可能为0或者1,而互斥量的信号量为1
3:互斥量只有一次,而信号量相当于一个计数器,可以进行累积操作。
信号量的操作
每一次调用wait将会使sem-1,直到sem<0,wait阻塞(p操作)
每一次调用post会使sem+1(V操作)
sem<0;则sem的绝对值表示系统中等待该类临界资源线程的个数,就是等待处理资源线程的个数。
相关函数
#include <semaphore.h>//头文件包含
int sem_init(sem_t*sem,int pshared,unsigned int value);//pshared:控制信号量的类型,0表示当前进程的局部信号量,否则在多个简称间共享 value :初始化值
int set_wait(sem_t*sem)//减 阻塞型
int set_trywait(sem_t*sem)//直接退出型
int sem_post(sem_t*sem)//加
int sem_getvalue(sem_t *sem,int *val)//得到信号量的值
int sem_destroy(sem_t*sem)//删除信号量
下面是代码演示
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
sem_t sem;
void *thread_fun(void *arg)
{
int x=*(int *)arg;
while(x)
{
printf("this is a good boy==%d\n",x);
x=*(int *)arg;
sem_wait(&sem);//对sem做减法直到<0阻塞
}
return 0;
}
void *thread_fun2(void *arg)
{
while(1)
{
scanf("%d",(int *)arg);
sem_post(&sem);//对sem做加法
if(*(int *)arg==100)
{
pthread_exit(NULL);
}
}
pthread_exit((void *)0);
}
int main()
{
pthread_t tid,tid2;
void *userP;
int a=3;
sem_init(&sem,1,3);
int reselt=pthread_create(&tid,NULL,thread_fun,&a);
int reselt2=pthread_create(&tid2,NULL,thread_fun2,&a);
printf("one %d\n",a);
pthread_join(tid,userP);
pthread_join(tid2,userP);
sem_destroy(&sem);
printf("%d\n",a);
return 1;
}
上面的程序为:在主程序中创建了两个线程,线程A内部是一个循环函数,每次循环都对sem值减1,(初始化在主线程中,value=sem=3)当打印四次sem<0的时候,线程A开始阻塞。等待被释放或者等待其他线程执行sem_post 操作,线程B是一个循环函数,每次循环需要输从键盘上输入一个数字,然后执行sem_post让sem加1,运行结果如下图。 4为线程B的键盘输入,长语句为线程A的打印。