概述
进程与线程
 进程 : 系统分配资源的基本单位 , 可以简单理解为一个正在进行的程序  
 
 线程 : 操作系统调度的最小单位 , 就是一段代码的执行顺序 
 
 注意: 
 
 1, 一个进程必须要有一个线程 , 该线程被称为主线程  
  2, 一个进程可以有多个线程 , 除主线程外的其他线程都是子线程  
  3, 进程被销毁时 , 其中的线程也将被销毁 .  
  4, 线程是轻量级的进程( LWP : light weight process ),在  Linux  环境下线程的本  
  质仍是进程。  
  5, 进程所有线程都共享该进程的资源。  
 
线程的特点
 类  Unix  系统中,早期是没有 “ 线程 ” 概念的, 80  年代才引入,借助进程机制实现出了线程的概念。  
 
 因此在这类系统中,进程和线程关系密切:  
 
 1)  线程是轻量级进程 (light-weightprocess) ,也有  PCB ,创建线程使用的底层函数和进程一样,都是 clone  
 
 2)  从内核里看进程和线程是一样的,都有各自不同的  PCB.  
 
 3)  进程可以蜕变成线程  
 
 4)  在 linux  下,线程最是小的执行单位;进程是最小的分配资源单位  
 
 实际上,无论是创建进程的  fork ,还是创建线程的  pthreadcreate ,底层实现都是调用同一个内核函数 clone  。  
 
         Ø 如果复制对方的地址空间,那么就产出一个 “ 进程 ” ;  
 
         Ø 如果共享对方的地址空间,就产生一个 “ 线程 ” 。  
 
 Linux 内核是不区分进程和线程的 , 只在用户层面上进行区分。所以,线程所有操作函数pthread* 是库函数,而非系统调用  
 
线程共享与非共享的资源
 共享的  
 
 1)  文件描述符表  
  2)  每种信号的处理方式  
  3)  当前工作目录  
  4)  用户 ID 和组 ID  
  5)  内存地址空间  (.text/.data/.bss/heap/ 共享库 ) 
  非共享的 
  1)  线程  id  
  2)  处理器现场和栈指针 ( 内核栈 )  
  3)  独立的栈空间 ( 用户空间栈 )  
  4) errno  变量  
  5)  信号屏蔽字  
  6)  调度优先级 
 现成的优缺点
 优点 
 
 提高程序并发性  
  开销小  
  数据通信、共享数据方便 
  缺点 
  库函数,不稳定  
  调试、编写困难、 gdb  不支持  
  对信号支持不好 优点相对突出,缺点均不是硬伤。 Linux  下由于实现方法导致进程、线程差别不是很大。  
 查看的指定的线程号(LWP)
ps - Lf pid
pid : 进程号
注意:
 由于线程库原本不是系统本身的 , 所以在链接时需要手动链接库文件 ,编译源文件时输入gcc *.c -l pthread 
 
线程相关函数
获取当前线程号
简述
 线程号只在它所属的进程环境中有效。  
 
 线程号则用  pthread_t  数据类型来表示, Linux  使用无符号长整数表示。 
 
 函数: 
 
 所需头文件  
          #include <pthread.h>  
  函数  
          pthread_t pthread_self(void);  
  功能:  
          获取线程号。  
  参数:  
          无  
  返回值:  
          调用线程的线程 ID  。  
  示例: 
 #include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int main()
{pthread_t pthid = pthread_self();printf("pthid=%ld\n",pthid);return 0;
}创建线程
函数
 所需头文件  
 
         #include <pthread.h>  
 
 函数  
 
         int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  
 
         void *(*start_routine)(void *), void *arg );  
 
 参数:  
 
         thread:线程标识符地址。  
 
         attr:线程属性结构体地址,通常设置为  NULL 。  
 
         start_routine:线程函数的入口地址。  
 
         arg:传给线程函数的参数。  
 
 返回值:  
 
         成功:0  
 
         失败:非 0 
 
示例: 
 
#include <stdio.h>
#include <pthread.h>
// 注意线程调用的函数返回值为任意指针类型
void *myfunc01()
{printf("线程%ld正在执行\n", pthread_self());return NULL;
}
void *myfunc02(void *arg)
{printf("线程%ld正在执行,参数为:%s\n", pthread_self(), (char *)arg);return NULL;
}
int main(int argc, char const *argv[])
{pthread_t p1, p2, p3;pthread_create(&p1, NULL, myfunc01, NULL);pthread_create(&p2, NULL, myfunc02, "Thread2");pthread_create(&p3, NULL, myfunc02, "Thread3");getchar();return 0;
}线程的回收
 作用  
 
 等待线程结束(此函数会阻塞),并回收线程资源,类似进程的  wait()  函数。如果线程已经结束,那么该函数会立即返回。  
  函数  
          #include <pthread.h>  
          int pthread_join(pthread_t thread, void **retval);  
  参数:  
          thread:被等待的线程号。  
          retval:用来存储线程退出状态的指针的地址 , 即回收的线程调用的函数的返回值  
  返回值:  
          成功:0  
          失败:非 0  
  示例: 
 #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *myfunc01(void *argv)
{int *time = (int *)argv;printf("线程A%ld将于%d秒后销毁\n", pthread_self(), *time);sleep(*time);return "线程A";
}
void *myfunc02(void *argv)
{int *time = (int *)argv;printf("线程B%ld将于%d秒后销毁\n", pthread_self(), *time);sleep(*time);return "线程B";
}
int main(int argc, char const *argv[])
{int n01 = 3, n02 = 5;pthread_t p1, p2;pthread_create(&p1, NULL, myfunc01, &n01);pthread_create(&p2, NULL, myfunc02, &n02);void *argv01, *argv02;pthread_join(p1, &argv01);pthread_join(p2, &argv02);printf("%s被回收\n", (char *)argv01);printf("%s被回收\n", (char *)argv02);return 0;
}线程的分离
 作用  
 
 使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的  
  是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系  
  统会自动回收它的资源。所以,此函数不会阻塞。  
 函数 
 头文件 
 #include <pthread.h> 
 函数 
 void pthread_detach(patread_t thread); 
 参数: 
 thread:线程号 
 示例: 
 #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun1(void *arg)
{printf("线程1开始执行\n");sleep(3);return NULL;
}
void *fun2(void *arg)
{printf("线程2开始执行\n");sleep(1);return NULL;
}
int main(int argc, char const *argv[])
{pthread_t tid1;pthread_t tid2;pthread_create(&tid1,NULL,fun1,NULL);pthread_create(&tid2,NULL,fun2,NULL);pthread_detach(tid1);pthread_detach(tid2);getchar();return 0;
}案例1:多线程遍历字符串
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *fun(void *arg)
{printf("len = %ld\n",strlen(arg));char *txt = (char *)arg;char buf[32] = {0};strcpy(buf,txt);printf("线程%ld开始执行\n",pthread_self);for(int i = 0;i < strlen(buf);i++){printf("%c\n",buf[i]);sleep(1);}printf("线程%ld执行结束\n");
}
int main(int argc, char const *argv[])
{char buf[32] = {0};while(1){// memset(buf,0,32);printf("请输入要打印的字符\n");fgets(buf,32,stdin);buf[strlen(buf)-1] = 0;if(strcmp(buf,"886")==0){break;}    pthread_t t;pthread_create(&t,NULL,fun,buf);}return 0;
}线程的退出
 作用 
 
 退出当前线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后  
  所占用的资源并不会释放。  
  函数 
  所需头文件  
          #include <pthread.h>  
  函数  
          void pthread_exit(void *retval);  
  参数:  
          retval:存储线程退出状态的指针。  
  返回值:  
          无 
  示例  
 #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{for(int i = 0;i < 10;i++){printf("%d\n",i);sleep(1);if(i == 3){pthread_exit(NULL);}}return NULL;
}
int main(int argc, char const *argv[])
{pthread_t tid;pthread_create(&tid,NULL,fun,NULL);pthread_detach(tid);return 0;
}
线程的取消
 作用  
 
 退出指定线程  
  注意 :  
  线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点 ( 检查点)  
  检测点:类似与游戏的存档,不是实时的,需要到特定的地方才会存档  
  函数 
          #include <pthread.h> 
           int pthread_cancel(pthread_t thread); 
 参数: 
 thread :目标线程id 
 返回值: 
 成功:0 
 失败:出错编号 
 示例: 
 #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{for(int i = 0;i < 10;i++){printf("%d\n",i);sleep(1);}return NULL;
}
int main(int argc, char const *argv[])
{pthread_t tid;pthread_create(&tid,NULL,fun,NULL);sleep(3);pthread_detach(tid);pthread_join(tid,NULL);return 0;
}
线程的属性(了解)
概述
 Linux  下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采  
 
 用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。  
 
 如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈  
 
 的大小来降低内存的使用,增加最大线程个数。  
 
线程树形结构体
 typedef struct  
 
 {  
 
 int  etachstate ;  // 线程的分离状态  
 
 int  schedpolicy ;  // 线程调度策略  
 
 struct  sched_param schedparam ;  // 线程的调度参数  
 
 int  inheritsched ;  // 线程的继承性  
 
 int  scope ;  // 线程的作用域  
 
 size_t  guardsize ;  // 线程栈末尾的警戒缓冲区大小  
 
 int  stackaddr_set ;  // 线程的栈设置  
 
 void * stackaddr ;  // 线程栈最低地址 , 即线程栈的地址 , 默认是从所  
 
 属的进程的栈空间划分  
 
 size_t  stacksize ;  // 线程栈的大小  
 
 }  pthread_attr_t ; 
 
 注意: 
 
 1.  线程分离状态  
  2.  线程栈大小(默认平均分配)  
  3.  线程栈警戒缓冲区大小(位于栈末尾)  
  4.  线程栈最低地址  
  以上属性的属性值不能直接设置,须使用相关函数进行操作,初始化的函数为  
  pthread_attr_init ,这个函数必须在 pthread_create  函数之前调用。之后须用  
  pthread_attr_destroy  函数来释放资源。 
 
线程属性相关函数
初始化
 作用 :  
 
         初始化线程属性  
  函数  
          int pthread_attr_init(pthread_attr_t *attr);  
  返回值:  
          成功:0 ;  
          失败:错误号 
 销毁
 作用 
 
 销毁线程属性  
  函数  
 int pthread_attr_destroy(pthread_t *attr); 
 返回值: 
 成功:0 
 失败:错误号 
 分离状态
 作用 : 设置分离状态  
 
 函数  
 int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate); 
 参数: 
 attr:已初始化的线程属性 
 detachstate: 
 分离状态 PTHREAD_CREATE_DETACHED (分离线程) 
          PTHREAD_CREATE_JOINABLE (非分离线程) 
   作用 : 获取分离状态 
  int pthread_attr_getdetachstate(pthread_attr_t *attr, int  
  *detachstat);  
  参数 :  
          attr:已初始化的线程属性  
          detachstate:  
  分离状态 PTHREAD_CREATE_DETACHED (分离线程)  
  PTHREAD_CREATE_JOINABLE (非分离线程)  
 示例: 
 #include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void *arg)
{int i = 0;while(1){printf("%lu正在运行i=%d\n",pthread_self(),i++);sleep(1);}
}
int main(int argc, char const *argv[])
{pthread_attr_t attr;pthread_attr_init (&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置分离属性pthread_t tid;pthread_create(&tid,NULL,fun,NULL);printf("5秒后将结束线程\n");sleep(5);pthread_cancel(tid);sleep(1);printf("销毁线程\n");pthread_attr_destroy(&attr);return 0;
}
线程栈相关
概述:
 // 设置栈的地址  
 
 int pthread_attr_setstack(pthreadattrt *attr, void *stackaddr, sizet stacksize);  
 
 参数:  
 
         attr:指向一个线程属性的指针  
 
         stackaddr:设置的栈的地址  
 
         stacksize:设置的栈的大小  
 
 返回值
成功:0 ;失败:错误号
 
成功:0 ;失败:错误号
 // 得到栈的地址  
 
 int pthread_attr_getstack(pthreadattrt *attr, void **stackaddr, sizet*stacksize);  
 
 参数:  
 
         attr:指向一个线程属性的指针  
 
         stackaddr:返回获取的栈地址  
 
         stacksize:返回获取的栈大小  
 
 成功: 0 ;失败:错误号  
 
 // 设置线程所使用的栈空间大小  
 
 int pthread_attr_setstacksize(pthreadattrt *attr, sizet stacksize);  
 
 参数:  
 
         attr:指向一个线程属性的指针  
 
         stacksize:设置的栈大小  
 
 成功: 0 ;失败:错误号  
 
 // 得到线程所使用的栈空间大小  
 
 int pthread_attr_getstacksize(pthreadattrt*attr, sizet *stacksize);  
 
 参数:  
 
         attr:指向一个线程属性的指针  
 
         stacksize:获取线程的栈大小  
 
 成功: 0 ;失败:错误号 
 
示例: 
 
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <
void *fun(void *arg)
{int i = 0;while(1){printf("%lu线程正在执行i=%d\n",pthread_self(),i++);sleep(1);}return NULL;
}
int main(int argc, char const *argv[])
{pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);void *stackaddr = calloc(1,128);if(stackaddr == NULL){perror("null");return 0;}pthread_attr_setstack(&attr,&stackaddr,128);pthread_t tid;printf("线程在5秒后结束\n");pthread_create(&tid,&attr,fun,NULL);sleep(5);pthread_cancel(tid);sleep(1);pthread_attr_destroy(&attr);free(stackaddr);return 0;
}
注意事项
 1)  主线程退出其他线程不退出,主线程应调用  pthread_exit  
 
 2)  避免僵尸线程  
 
         a) pthread_join  
 
         b) pthread_detach  
 
         c) pthread_create 指定分离属性  
 
 被  join  的线程可能在  join  函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值; 3) malloc  和  mmap  申请的内存可以被其他线程释放  
 
 4)  应避免在多线程模型中调用  fork ,除非马上  exec ,子进程中只有调用  fork  的线程存在,其他线程 t  在子进程中均  pthread_exit  
 
 5)信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制 
 
案例
 火车票售票问题  
 
 定义一个记录火车票的剩余数量  
 
 4 个窗口同时销售该火车票  
 
代码: 
 
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
int ticket = 100;
void *sale(void *name)
{char buf[32] = {0};strcpy(buf,(char*) name);while(ticket > 0){ticket--;sleep(0.5);printf("%s售卖了一张船票,还剩%d张船票\n",buf,ticket);}
}
int main(int argc, char const *argv[])
{pthread_t tid1,tid2,tid3,tid4;pthread_create(&tid1,NULL,sale,"一号窗口");pthread_create(&tid2,NULL,sale,"二号窗口");pthread_create(&tid3,NULL,sale,"三号窗口");pthread_create(&tid3,NULL,sale,"四号窗口");pthread_detach(tid1);pthread_detach(tid2);pthread_detach(tid3);pthread_detach(tid4);getchar();return 0;
}作业
 1, 整理笔记  
 
 2, 完成以下情况  
 
         一个线程打印1~52  
 
         一个线程打印a~z  
 
 3, 使用代码模拟龟兔赛跑  
 
         乌龟每秒1 米  
 
         兔子每秒5 米  
 
         赛程100 米  
 
         兔子在临近终点睡觉了, 导致乌龟赢了  
 
 4, 完成售票案例 , 查看并分析结果 , 说明原因 
 
 代码参考上面案例 
 
 原因: 
 
 
 
是因为卖了第一张票后,其他三个窗口在抢cpu执行权,在第一张票卖出去,提示的信息还未执行的时候,其他三个线程有一个线程有一个将cpu执行权抢了过去,这是票数实际是99,同理以此类推,其他线程将cpu执行权抢了过去,所以将卖了四张票后才第一次打印提示信息,这时实际票数已经是96张,所以,这时提示的信息中将之前的四次卖票纪律一次性打出来了,所以都是剩余96张。总之,这是个异步,在栈中,每个线程都将有各自独立的ticket,都是100张。 
 
解决办法: 
 
是要解决其中异步的问题,所以要解决这一问题,就是要实现同步,让四个线程只对一个ticket操作就可以。 
 
简单的可以用将ticket设置为全局变量