1.Linux线程实现
2.Linux线程的创建和终止
3.Linu线程的互斥和同步
Linux或unix系统多任务,线程处理大并发的客户端请求
进程是资源管理的最小单位,线程是程序执行的最小单位
针对在进程中的每一个操作,都是在后台去启动一个一个线程来执行处理,线程是负责进程中某个具体操作,根据需要,可以在进程中启动一个或多个线程。每个线程共享其附属进程的所以资源,包括打开的文件,内存页面,信号标识及动态分配的内存等。
因为线程比进程比起来很小,所以相对来说,线程花费更少的CPU资源
在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持多处理器,并且减小进程上下文切换的开销
进程根据系统调度要互相切换,时间片给进程,
每个进程都有自己的数据段,代码段,堆栈段,线程通常叫做轻量级进程,
1.linux搭建的服务器,多个客户端需要访问服务器,需要发送请求,服务器响应完后,发回页面,服务器对客户端的请求采用线程处理大并发客户端请求
线程与进程关系:
线程是属于进程的,线程运行在进程空间内,同一进程所产生的进程共享同一用户内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除, 进程管理着资源(比如CPU,内存,文件等),而将线程分配到某个CPU上执行,如果没有可以去创建线程,一个进程只有一个主控线程,创建的线程称为子线程,线程的执行也要根据优先级进行任务调度
默认情况,一个进程启动只有一个线程(主线程)
通过主控线程可以创建很多线程,这些线程都共享进程所有的资源
现在的操作系统都是多任务,多用户,分时操作系统,可以创建多个进程,根据抢占式调度,根据优先级,轮流占据CPU,各个进程从就绪态切换为运行态,CPU会给这个进程分配一个时间片,进程必须在这个时间片中运行完毕,如果没有运行完毕,暂时退出,阻塞,CPU被其他进程占有,CPU再发分配时间片给其他进程,被阻塞的进程等待被CPU重新发分配时间片才会继续运行
如果只有一个进程
CPU分配给进程的时间片,在进程运行里面,只有一个线程在获取CPU,去执行,多个线程,也要根据优先级实现系统调度,
有多个线程,CPU给进程的时间片时间大于此进程里面任何单个线程被CPU分配的时间片。
线程分类
线程按照其调度可分为用户级线程和内核级线程
用户级线程:主要解决的是上下文切换的问题
内核级线程:由内核调度机制实现
用户级线程需要绑定内核级线程,这样才能实现用户级线程的调度执行。
默认情况下,用户级线程和内核线程是一对一,也可以多对一,这样实时性就会比较差
用户级线程分配的时间片以内核级线程为准
Linux线程实现
在linux中,一般采用pthread线程库(继承于unix操作系统),实现线程的访问与控制,由POSIX
提出,具有良好的可移植性
Liunx线程程序编译需要在gcc上链接库pthread gcc -lpthread
进程标识是pid
线程标识
1.每个进程内部的不同线程都有自己唯一的标识(ID)
2.线程标识只在它所述的进程环境中有效
3.线程标识是pthread_t数据类型,线程标识的类型
线程创建
在主控线程中调用系统调用
第一个是传递线程标识符,第二个是线程属性,不需要为NULL,
终止方式
gcc -p.c -lpthread //加-lpthread链接pthread库
//龟兔赛跑线程
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <math.h>//定义线程运行函数,乌龟和兔子的线程轮流交替
void* th_fn(void *arg)
{int distance = (int)arg; //强制类型转换int i;for(i = 1; i <= distance; i++){printf("%lx run %d\n", pthread_self(), i);//drand48()用于产生0~1之间的随机数int time = (int)(drand48() * 100000);usleep(time); //微妙级别延时}return (void*)0;
}int main(void)
{int err;pthread_t rabbit, turtle; //兔子和乌龟的线程标识符 //创建rabbit线程,第一个是线程标识符,第二个是线程属性,//第三个是线程运行函数,第四个是传给线程函数的参数//创建成功返回0,错误返回错误编号if((err = pthread_create(&rabbit, NULL, th_fn, (void*)50)) != 0){perror("pthread_create error");}//创建turtle线程if((err = pthread_create(&turtle, NULL, th_fn, (void*)50)) != 0){perror("pthread_create error");}//先运行子线程//主控线程调用pthread_join(),自己会阻塞,//直到rabbit和turtle都结束方可运行//pthread_join()用于阻塞线程,谁调用,谁阻塞pthread_join(rabbit, NULL);pthread_join(turtle, NULL);//sleep(5); //睡眠主控线程,阻塞主控子线程cleaprintf("control thread id:%lx\n", pthread_self());printf("finished\n");return 0; //整个进程结束
}
线程创建第二个案例,传入线程函数里的是一个结构体
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>typedef struct{char name[20];int time;int start;int end;
}RaceArg;void* th_fn(void* arg)
{RaceArg *r = (RaceArg*)arg;int i = r->start;for(; i <= r->end; i++){printf("%s(%lx) running %d\n",r->name, pthread_self(), i);usleep(r->time);}return (void*)0;
}int main(void)
{int err;pthread_t rabbit, turtle;RaceArg r_a = {"rabbit", (int)(drand48()*10000000), 20, 50};RaceArg t_a = {"turtle", (int)(drand48()*10000000), 10, 60};if((err = pthread_create(&rabbit, NULL, th_fn, (void*)&r_a)) != 0){printf("Pthread_create error");}if((err = pthread_create(&turtle, NULL, th_fn, (void*)&t_a)) != 0){printf("Pthread_create error");}pthread_join(rabbit, NULL);pthread_join(turtle, NULL);return 0;
}
进程和线程的内存信息
主控线程使用同一个线程调用函数被pthread_create()了两次创建了两个子线程,生成了两个子线程的栈空间,在各自的栈中存放了各自的线程调用函数所需的局部变量
然而定义的全局变量存放在数据段,进程中的各个线程对这个全局变量是共享的,可以对其进行修改,所有线程都能对其访问的资源叫做共享资源,引发线程不安全,要做到线程安全,互斥和同步来解决 多线程编程 尽量引用局部变量,这样线程就是安全的
线程终止
1.主动终止
线程主动终止pthread_exit();
exit();//进程主动终止
2.被动终止
pthread_cancel();//线程可以被同一进程的其他线程取消,tid为被终止的线程标识符
pthread_join();调用这个函数的线程会阻塞,直到等到等待的线程结束,会回收标识符ID线程的资源,类似进程里用的系统调用函数wait()
pthread_join(pthread_t th, (void**) thread_return) //第二个参数接收th线程的返回值
下面的代码主要是注意指针的转换
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>typedef struct
{int d1;int d2;
}Arg;void* th_fn(void *arg)
{Arg *r = (Arg*)arg;return (void*)(r->d1 + r->d2);
}int main(void)
{int err;pthread_t th;Arg r = {20, 50};if((err = pthread_create(&th, NULL, th_fn, (void*)&r)) != 0) //传r的地址{perror("pthread_create error");}/* int *result;pthread_join(th, (void**)&result); //传result的地址printf("result is %d\n", (int)result);*/int result;pthread_join(th, (void*)&result); //获取线程函数的返回值printf("result is %d\n", result);return 0;
}
线程运行函数关键usleep();
这样可以让不同线程不至于让一个线程霸占完
//定义线程运行函数,乌龟和兔子的线程轮流交替
void* th_fn(void *arg)
{int distance = (int)arg; //强制类型转换int i;for(i = 1; i <= distance; i++){printf("%lx run %d\n", pthread_self(), i);//drand48()用于产生0~1之间的随机数int time = (int)(drand48() * 100000);usleep(time); //微妙级别延时,使多个线程交替执行}return (void*)distance;
}
线程清理和控制函数
线程清理函数
pthread_cleanup_push()
pthread_cleanup_pop()
excute == 1执行线程处理函数,excute==0不执行线程处理函数
#include <stdint.h>
intptr_t类型
intptr_t
是 C 语言中的一种整数类型,定义在 <stdint.h>
头文件中。intptr_t
是一个有符号整数类型,其大小足以容纳指针的值。它的主要用途是在需要将指针转换为整数或将整数转换为指针的情况下,确保这种转换是安全的和可移植的。
关键特性
-
大小:
intptr_t
的大小与指针的大小相同,这意味着它可以无损地存储指针的值。- 在 32 位系统上,
intptr_t
通常是 32 位整数。 - 在 64 位系统上,
intptr_t
通常是 64 位整数。
-
有符号性:
intptr_t
是一个有符号整数类型,这意味着它可以表示负数。
-
用途:
- 将指针转换为整数,以便在需要整数的地方使用。
- 将整数转换为指针,以便在需要指针的地方使用。
示例代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> //包含intptr_t类型//定义线程清理函数
void clean_fn(void* arg)
{char *s = (char*)arg;printf("clean_func: %s\n", s);
}//线程运行函数
//线程清理函数在线程结束之前调用
void* th_fn(void *arg)
{intptr_t excute = (intptr_t)arg;//第二个参数传递给线程清理函数pthread_cleanup_push(clean_fn, "first clean func"); //线程处理函数压栈pthread_cleanup_push(clean_fn, "second clean func");printf("thread running %lx\n", pthread_self());//线程控制函数,excute为1执行pthread_cleanup_push(th_fn, arg);pthread_cleanup_pop(excute); //控制是否执行线程处理函数,0不执行,1执行pthread_cleanup_pop(excute);return (void*)0;
}int main(void)
{int err;pthread_t th1, th2;//子线程1,传递if((err = pthread_create(&th1, NULL, th_fn, (void*)(intptr_t)1)) != 0){perror("pthread create error");}pthread_join(th1, NULL);printf("th1(%lx) finished\n", th1);//子线程2if((err = pthread_create(&th2, NULL, th_fn, (void*)1)) != 0){perror("pthread create error");}pthread_join(th2, NULL);printf("th2(%lx) finished\n", th2);return 0;
}