1.基本知识:进程是操作系统对运行的二进制程序的抽象,包括:加载的二进制程序,虚拟内存,内核资源等等。线程是进程内执行的单元,包括:虚拟处理器,堆栈,程序状态等等。线程是操作系统调度器可以调度的最小执行单元。一个进程包含一个或者多个线程,状态可以分为“多个线程”,“单个线程”。虚拟内存是和进程相关,与线程无关,因此每个进程都有独立的内存空间,进程中所有线程共享这份空间。虚拟处理器是和线程有关的,与进程无关。多线程时,每个线程感觉自己独占一个处理器。
注意上述:栈指针和栈空间是不共用的,并且线程可以共享全局变量。
2.函数介绍:LINUX系统中,与线程线管的函数在<pthread.h>中
1).创造线程:
#include <pthread.h>
int pthread_create(pthread_t* thread,const pthread_attr_t* arr,void*(*start)(void*),void* arg);
第一个参数是线程ID变量,线程创建成功后,将返回创建的线程ID,储存在第一个变量中。
第二个参数是给创造的线程设置属性,但大多数时候是传递的NULL,以此来设置默认属性。
第三个参数是线程要运行的函数,注意接受和返回都是void*类型。
第四个参数是传递给运行函数的形参,注意只有一个并且是void*类型。
调用失败返回-1.
这里注意,新的线程会共享父进程资源,而不是进行拷贝。最重要的内存共享是进程地址空间。
2).线程ID
这里不同于进程ID(PID),PID是由LINUX内核分配,但是线程ID(TID)是由pthread库分配的。
#include <pthread.h>
pthread_t pthread_self(void);
线程可以调用此函数在运行时来获得自己的TID。
const pthread_t me=pthread_self();
当然TID还可以进行比较:
#include <pthread.h>
int pthread_equal(pthread_t t1,pthread_t t2);
调用时,若两个线程ID相同,返回非零值,不同返回0.
3).线程终止:
下面三种情况是杀死有问题的线程:
下面三种情况是可以杀死所有线程:
线程自杀:最简单的情况是在启动时候就结束。
#include <pthread.h>
void pthread_exit(void* retval);
调用该函数时,调用线程会结束
一般参数填NULL,此函数等价于exit()
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void* fff(void* arg){
int i=*((int*)arg);
if(i==3){
//exit(1);//exit函数是终止进程的,这里就相当于只能创建三个线程,而不是退出其中某一个线程。
// return NULL; return 是返回给调用者,这里的调用者是线程,这个不是退出
pthread_exit(NULL);//这里就是将当前该线程退出
}
printf("-- i am %dth thread : pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}//注意 如果传入的是变量i的地址且主线程循环没有sleep()函数 这样在主线程创造线程后,直接进入下一轮循环,这时i的值已经变化
//但是创造的线程进行参数回调要慢一点,当其回调成功时,i值已经发生变化,不是原来的i了
//解决方法就是传值,不是传地址,或者加sleep函数进行控制。
//创造的5个进程都在同一个线程里。
int main(){
int i;
int ret;
pthread_t tid;
// pthread_exit(NULL);这里直接退出主线程
for(int i=0;i<5;i++){
int a=i;
pthread_create(&tid,NULL,fff,(void*)&a);
sleep(1);
}
sleep(i);
printf("main: i am mian ,pid=%d,tid=%lu\n",getpid(),pthread_self());
return 0;
}
如上例子
终止其他线程:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
成功调用后,函数会给由线程ID表示的线程发送取消请求,
线程是否可以取消取决于取消状态或者取消类型,成功时返回0(这只能表示成功执行取消请求)。
实际的取消操作是异步的,出错时返回ESRCH。
注意:调用上面的函数之前,必须要确保thread线程已经被创建,不然没用,并且杀死线程后,用join进行回收。
上面说到,进程的取消状态和取消类型。取消状态有两种:“允许”和“不允许”。对于新线程,默认状态是允许,如果线程不允许取消,请求会入队列,直到允许取消。可以通过函数进行改变取消状态。
#include <pthread.h>
int pthread_setcancelstate(int state,int* oldstate);
此函数调用成功时,调用线程的取消状态会被设置为state,老的状态会被储存在oldstate中,
state的状态可以是PTHREAD_CANCEL_ENABLE,PTHREAD_CANCEL_DISABLE,
表示支持和不支持
#include <pthread.h>
int pthread_setcanceltype(int type,int* oldtype);
调用成功时,调用线程的取消类型变为type,取消类型有异步或者延迟。
PTHREAD_CANCEL_ASYNCHRONOUS为异步取消,PTHREAD_CANCEL_DEFERRED表示延迟取消。
上述两个函数调用失败都会返回EINVAL.
上述两个函数的调用是在线程函数里调用。而不是在调用的线程函数外。
至于调用函数pthread_cancl() 可以专门封装一个线程去发送要关闭线程的请求。
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void* fff(void* arg){
int i=*((int*)arg);
while(1){
sleep(1);
printf("-- i am %dth thread : pid=%d,tid=%lu\n",i,getpid(),pthread_self());
}
return NULL;
}
int main(){
int i=0;
int ret;
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fff,(void*)&(++i));
sleep(4);
pthread_cancel(tid1);
pthread_join(tid1,NULL);
printf("main: i am mian ,pid=%d,tid=%lu\n",getpid(),pthread_self());
return 0;
}
上面就是利用pthread_cancel杀死正在循环的子线程。
4).线程同步机制.join和detach.
join线程支持一个线程阻塞,等待另一个线程终止。
#include <pthread.h>
int pthread_join(pthread_t thread,void** ret);
第一个是要加入的线程的TID
第二个是线程结束后的返回值,可以保存在ret中,若函数没有返回值,可以填写NULL
#include <pthread.h>
int pthread_detach(pthread_t thread);
此函数调用后,线程与原来主进程分离,不在回合
上述两个应该在进程中的每一个线程上进行调用,这样当线程终止时,也会释放系统资源。
5).PTHREAD 互斥:
互斥使用pthread_mutex_t表示。
对于初始化一个互斥,可以使用宏,也可以使用函数。
pthread_mutex_t mutex;
mutex=PTHREAD_MUTEX_INITIALIZER;//宏初始化
pthread_mutex_init(pthread_mutex_t* mutex,NULL);
对互斥量加锁
#include <pthread.h>
int pthraed_mutex_lock(pthread_mutex_t* mutex);
对互斥量解锁
#include <pthread.h>
pthread_mutex_unlock(pthread_mutex_t* mutex);
具体用法见网络编程部分,对于LINUX来说,其锁的涉及稍微简单,可以参考C++标准库中的锁的分类,shared_mutex,unqie_lock<>,share_lock<>,mutex等等。请参考C++并发编程此书。