一 线程
线程的概念:线程是进程的进一步抽象。进程包括两个集合。一个资源集合,一个线程的集合,进程是资源分配的最小单位,线程是系统调度的最小单位。每个进程中必然包含一个线程,那么这个线程被称作主线程。线程的本质是一个在运行的线程函数。线程也属于并发,也会拥有自己的资源:如:Pc程序计数器,时间片,堆,栈,线程号等。线程本身并不会去申请资源,而是共享进程的资源。线程也被称为轻量级的进程,线程间共享全局变量,使得通信时会很方便。
线程不属于标准C库函数,也不属于系统调用,而是属于第三方库,libpthread.so
安装库:sudo apt-get install manpages-posix manpages-posix-dev
1.1 线程基本使用
线程的接口函数:
1.pthread_create
头文件:#include 原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 功能:创建一个线程 参数: thread:线程号 attr:线程的属性和设置堆栈的大小 分离属性:线程结束后,自己回归空间 非分离属性:线程结束后,由创建者手动去归还空间 一般填写NULL,一般在后面函数中设置 start_routine:函数指针,指向了一个返回值是void*,参数是void*的函数 arg:给线程函数传参使用,一般用于线程间通信使用,如果不传参,填写NULL 返回值: 如果成功返回 0 如果错误返回错误码 strerror();
注意:
1.在线程运行过程中,进程结束时资源释放后,线程没有办法执行了。
2.在多线程中,线程中尽量不调用影响整个进程的函数接口,线程本身安全星星不高,一个线程崩溃会造成进程的崩溃。
3.线程中通信的方式2种:1 全局变量 2.参数arg
注意点:如果一个线程对其进行改变,所有线程访问的值都是改变后的值。
#include <stdio.h> #include <pthread.h> #include <unistd.h> //int a = 100; void *MyFun(void * arg) { int *p = (int *)arg; //int a = *(int *)arg; //线程传参 printf("我是第一个线程\n"); printf("子线程获取A的值为%d\n",*p); //a = 200; *p = 200; } int main(int argc, char const *argv[]) { int a = 100; pthread_t tid = 0; if(pthread_create(&tid,NULL,MyFun,&a) != 0) { printf("线程创建失败!\n"); return -1; } printf("线程号为%ld\n",tid); printf("主线程获取A的值为%d\n",a); sleep(1); printf("主线程获取A的值为%d\n",a); return 0; }
2.pthread_exit
头文件:#include 原型:void pthread_exit(void *retval); 功能:结束一个线程 参数:retval:线程结束时的状态,如果不传递填写NULL 返回值:无
3.pthread_join
头文件:#include 原型:int pthread_join(pthread_t thread, void **retval); 功能:阻塞等待回收指定线程 参数:thread:目标线程号 retval:线程结束时,exit返回的状态 返回值:成功返回0 失败返回错误码
#include <stdio.h> #include <pthread.h> #include <unistd.h> //int a = 100; void *MyFun(void * arg) { int *p = (int *)arg; //int a = *(int *)arg; //线程传参 printf("我是第一个线程\n"); sleep(3); printf("子线程获取A的值为%d\n",*p); //a = 200; *p = 200; pthread_exit(NULL); } int main(int argc, char const *argv[]) { int a = 100; pthread_t tid = 0; if(pthread_create(&tid,NULL,MyFun,&a) != 0) { printf("线程创建失败!\n"); return -1; } printf("线程号为%ld\n",tid); printf("主线程获取A的值为%d\n",a); if(0 != pthread_join(tid,NULL)) { printf("回收资源失败!\n"); return -1; } //printf("主线程获取A的值为%d\n",a); printf("回收资源成功!\n"); return 0; }
4.pthread_detach
头文件:#include 原型:int pthread_detach(pthread_t thread); 功能:设置线程分离属性 参数:thread:目标线程号 返回值:成功返回0 失败返回错误码 注意:设置完分离属性后,线程自动归回资源,无需join回收,join会失败
#include <stdio.h> #include <pthread.h> #include <unistd.h> //int a = 100; void *MyFun(void * arg) { int *p = (int *)arg; //int a = *(int *)arg; //线程传参 printf("我是第一个线程\n"); sleep(3); printf("子线程获取A的值为%d\n",*p); //a = 200; *p = 200; pthread_exit(NULL); } int main(int argc, char const *argv[]) { int a = 100; pthread_t tid = 0; if(pthread_create(&tid,NULL,MyFun,&a) != 0) { printf("线程创建失败!\n"); return -1; } if(0 != pthread_detach(tid)) { printf("设置分离属性失败\n"); return -1; } printf("线程号为%ld\n",tid); printf("主线程获取A的值为%d\n",a); if(0 != pthread_join(tid,NULL)) { printf("回收资源失败!\n"); return -1; } //printf("主线程获取A的值为%d\n",a); printf("回收资源成功!\n"); return 0; }
5. pthread_self
原型:pthread_t pthread_self(void) 功能:获取自己的线程号 参数:无 返回值:成功会返回一个线程ID号 6.pthread_cancel 原型:int pthread_cancel(pthread_t thread); 功能:申请结束一个线程 参数:目标结束的线程号 返回值:成功返回0,失败返回非0
#include <stdio.h> #include <pthread.h> #include <stdio.h> #include <pthread.h> #include <unistd.h> //int a = 100; void *MyFun(void * arg) { int *p = (int *)arg; int n = 3; //int a = *(int *)arg; //线程传参 sleep(3); printf("我是第一个线程,我的线程ID是%ld\n",pthread_self()); printf("子线程获取A的值为%d\n",*p); //a = 200; *p = 200; pthread_exit(NULL); } int main(int argc, char const *argv[]) { int a = 100; pthread_t tid = 0; if(pthread_create(&tid,NULL,MyFun,&a) != 0) { printf("线程创建失败!\n"); return -1; } if(0 != pthread_detach(tid)) { printf("设置分离属性失败\n"); return -1; } pthread_cancel(tid); //发送一个申请取消线程的信号 printf("线程号为%ld\n",tid); printf("主线程获取A的值为%d\n",a); //printf("主线程获取A的值为%d\n",a); printf("回收资源成功!\n"); sleep(5); return 0; }
练习3
创建两个线程,一个线程对全局变量count循环相加,每一秒加一次,另一个线程循环打印count的值,每一秒打印一次。
#include <pthread.h> #include <unistd.h> #include <stdio.h> int count = 0; int flag = 0; void * myfun1(void *arg) { while(1) { if(flag == 0) { count++; //sleep(1); flag = 1; } } } int main(int argc, char const *argv[]) { pthread_t tid = 0; if(0 != pthread_create(&tid,NULL,myfun1,NULL)) { perror("pthread_create"); return -1; } while(1) { if(flag == 1) { printf("count = %d\n",count); flag = 0; } } return 0; }
1.2 线程同步和互斥
1.2.1同步概念
什么是同步:线程间也存在着竞态的关系,对有些任务是不友好的,所以说线程提供了可以实现让多个线程同步(按一定顺序去执行)。
1.3 无名信号量:又名信号灯
本质是一个全局特殊变量,这个值不允许小于0,
1.3.1 无名信号灯的接口
sem_init 头文件:#include 原型:int sem_init(sem_t *sem, int pshared, unsigned int value); 功能:初始化信号灯 参数:sem:信号灯变量的指针 pshared:如果是0,代表线程间使用 如果是非0,代表亲缘间进程使用 value:信号的初值 返回值: 成功返回0 失败返回 -1. sem_wait 头文件:#include 原型:int sem_wait(sem_t *sem); 功能:申请资源 ----又名p操作, 也就是信号灯-1操作 参数:sem:目标信号灯 返回值: 成功返回0 失败返回 -1.
3. sem_post
头文件:#include 原型:int sem_post(sem_t *sem); 功能:释放资源----又名V操作 也就是信号灯 +1操作 V操作可以多次执行,但是我i们一般把信号量控制在0和1 参数:sem:目标信号灯 返回值: 成功返回0 失败返回 -1. sem_destory 头文件:#include 原型:int sem_destroy(sem_t *sem); 功能:销毁一个信号灯 参数:sem:目标信号灯 返回值: 成功返回0 失败返回 -1.
#include <pthread.h> #include <unistd.h> #include <stdio.h> #include <semaphore.h> int count = 0; //定义无名信号灯 sem_t sem1; //给线程1使用,初值应该为0 sem_t sem2; //给线程2使用,初值应该为1 void * myfun1(void *arg) { while(1) { //申请资源向信号sem1 sem_wait(&sem1); count++; sem_post(&sem2); } } int main(int argc, char const *argv[]) { //初始化无名信号灯,分别初始化值为 1 0 sem_init(&sem1,0,1); sem_init(&sem2,0,0); //开始创建线程 pthread_t tid = 0; if(0 != pthread_create(&tid,NULL,myfun1,NULL)) { perror("pthread_create"); return -1; } while(1) { sem_wait(&sem2); printf("count = %d\n",count); sleep(1); sem_post(&sem1); } return 0; }