Linux基础(15)多线程编程
Linux的内核中没有thread的概念,线程是第三方库libpthread实现的, 和vfork(轻量级进程,只有部分copy)有点像(进程的创建fork会完全copy主进程资源 ,而线程会共享资源,子线程创建新资源时其作用域只在当前子线程,而子线程非新新创建的资源会和创建前的主线程共享这些资源) , 线程和进程的创建在内核里都是系统调用copy_process ,但是他们的实现不同, 进程会调用fork()的copy_process(全拷贝) ,而线程调用的是thread的copy_process(第三方库实现的,只拷贝部分) , copy_process是一个虚函数
小知识: Linux 有个内存文件系统挂载在内存里(shell:cd /proc)用作调试使用, 里面有很多数,这些是进程或线程的ID, 这些文件里有他们的调试信息,比如 ps -ef 所展示的信息
shell:cd空格/work/pthread , 在这下面使用 pmap(进程映射工具)把进程信息保存(pmap [ThreadNum] > a.txt)
pmap -x [ThreadNum] >a.txt
pmap -x [ThreadNum] >b.txt
可以使用shell:diff a.txt b.txt 比对保存的两个进程或线程信息 有何区别
线程的概念 进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
(进程只是系统分配的资源而执行的是线程(在没有创建子线程时进程默认有一个线程执行,创建子线程后默认的线程作为主线程))
什么是堆 ,什么是栈 ,他们在哪 https://www.cnblogs.com/valor-xh/p/6535421.html
线程不代表资源,是程序的最小执行单元-----每一个线程都有一个栈 ,当线程结束时栈会被回收
进程代表资源,是程序调度的最小单元------创建的子线程共享主进程的一切资源, 而子进程新创建的资源不与主线程和其他线程共享
多线程是并发执行的,但也是有数量限制的,过了时间片要等待下一次的CPU调度 线程和进程除了资源的区别其他和进程一样
多线程的并行其实是伪并行,CPU的调度是有轮转的,比如A线程被CPU调度后,CPU寄存器等是被A所占用的,而A过了时间片后其状态和信息(寄存器里的值)会被保存在tss段, 而B线程会的状态和信息会被恢复到CPU
线程的管理
gcc -g -o xxx xxx.c -lpthread
pthread_self() 获得线程tid(由线程库维护的 ,其ID空间的各个进程独立的, ,因此每个进程的线程可能会相同)
syscall(SYS_gettid), 获取内核中的线程ID(内核中没有线程的概念只有轻量级进程 ,SYS_gettid则是获取内核中这个轻量级进程的ID 是唯一的)
syscall: http://blog.chinaunix.net/uid-28458801-id-4630215.html
线程的创建
int pthread_create(pthread_t *thread , const pthread_attr_t *attr , void(start_routine)(void*) , void *arg );
pthread_t *thread 线程的id 放一个指针,线程创建完后作为返回值返回给 传入的指针 返回指向线程的首地址
const pthread_attr_t *attr NULL 线程的属性
void(start_routine)(void*) 线程的执行函数
void *arg 传递给线程函数的参数
线程的退出与等待
void pthread_exit(void *retval); 自行退出当前线程 retval是一个返回值,可以NULL
其他线程调用 pthread_cancel
线程执行完后也是自动退出 , 创建线程的进程退出后线程也会退出
其中一个线程执行了exec类函数,因为会替换当前进程的所有的地址空间 线程也会退出
int pthread_join(pthread_t thread, void **retval); 等待指定线程退出后,会收到指定线程退出时返回的值 ,也可以NULL不接收
#include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> void *helloworld(char *argc); int main(int argc,int argv[]) { int error; int *temptr; pthread_t thread_id; pthread_create(&thread_id,NULL,(void *)*helloworld,"helloworld"); //创建子线程 printf("*p=%x,p=%x\n",*helloworld,helloworld); if(error=pthread_join(thread_id,(void **)&temptr)) //等待子线程退出,并接受返回的堆指针 { perror("pthread_join"); exit(EXIT_FAILURE); } printf("temp=%x,*temp=%c\n",temptr,*temptr); *temptr='d'; //使用子线程创建的堆 printf("%c\n",*temptr); free(temptr); //释放 return 0; } void *helloworld(char *argc) { int *p; p=(int *)malloc(10*sizeof(int)); //子线程创建了一个堆 printf("the message is %s\n",argc); printf("the child id is %u\n",pthread_self()); memset(p,'c',10); printf("p=%x\n",p); pthread_exit(p); //把堆的指针返回出去 //return 0; }
线程的取消
线程能否被取消要看两点:
线程是否具有可取消属性---默认可以被取消
线程如果设置为到可取消点才能被取消时,线程被取消是不会被立刻取消
int pthread_cancel(pthread_t thread);
线程的取消状态属性
int pthread_setcancelstate(int state, int *oldstate);
state : PTHREAD_CANCEL_ENABLE 可取消属性
PTHREAD_CANCEL_DISABLE---->不可取消
线程的取消类型属性 A线程收到了B线程的取消请求时请求时 ,要根据取消类型判断是立即取消飞行合适再取消(取决于系统)
int pthread_setcanceltype(int type, int *oldtype);
type: 立刻被取消 PTHREAD_CANCEL_ASYNCHRONOUS
只有到达一定取消点,才会取消 PTHREAD_CANCEL_DEFERRED
线程的私有数据
TSD私有数据,同名但是不同内存地址的私有数据结构, 比如fork()后子进程继承了父进程的一切包括全局变量的值,但是在子进程修改了这个全局变量却不影响父进程的全局变量的值, 父进程的这个全局变量也不影响子进程的值, 两者不在同一内存地址互不影响
https://blog.csdn.net/mengxingyuanlove/article/details/50802246
int pthread_key_create(pthread_key_t key, void (destructor)(void*)); 创建
int pthread_key_delete(pthread_key_t key); 删除
void *pthread_getspecific(pthread_key_t key); 获取
int pthread_setspecific(pthread_key_t key, const void *value); 设置
//this is the test code for pthread_key #include <stdio.h> #include <pthread.h> pthread_key_t key; //线程私有类型的变量 void echomsg(void *t) { printf("destructor excuted in thread %u,param=%u\n",pthread_self(),((int *)t)); } void * child1(void *arg) { int i=10; int tid=pthread_self(); printf("\nset key value %d in thread %u\n",i,tid); pthread_setspecific(key,&i); //要使用特定的设置函数才能设置当前线程私有的变量 printf("thread one sleep 2 until thread two finish\n"); sleep(2); printf("\nthread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); } void * child2(void *arg) { int temp=20; int tid=pthread_self(); printf("\nset key value %d in thread %u\n",temp,tid); pthread_setspecific(key,&temp); sleep(1); printf("thread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key)); } int main(void) { pthread_t tid1,tid2; pthread_key_create(&key,echomsg); //创建线程私有的变量,每个线程都可以继承这个变量,但是内存地址不同而互不影响 ,在线程结束后会调用echomsg() pthread_create(&tid1,NULL,(void *)child1,NULL); pthread_create(&tid2,NULL,(void *)child2,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_key_delete(key); //删除线程私有变量 return 0; }
线程占有的都是不共享的,其中包括:栈、寄存器、状态、程序计数器
线程间共享的有:堆,全局变量,静态变量;
线程的互斥的方式与更精细的互斥: 互斥锁,条件变量 , 读写锁
互斥锁的通信机制
pthread_mutex_t 互斥锁对象
int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁对象
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 初始化对象
int pthread_mutex_lock(pthread_mutex_t *mutex); 上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); 非阻塞函数,无论是否上锁都直接返回
int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁
无论读写共享内存都要先上锁,读完写完都要解锁
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <string.h> void *thread_function(void *arg); pthread_mutex_t work_mutex; //互斥锁 #define WORK_SIZE 1024 char work_area[WORK_SIZE]; //共享内存区域 int time_to_exit = 0; //是否退出的信号 int main(int argc,char *argv[]) { int res; pthread_t a_thread; void *thread_result; res = pthread_mutex_init(&work_mutex, NULL); //初始化并创建锁对象 if (res != 0) { perror("Mutex initialization failed"); exit(EXIT_FAILURE); } res = pthread_create(&a_thread, NULL, thread_function, NULL); //创建线程 if (res != 0) { perror("Thread creation failed"); exit(EXIT_FAILURE); } pthread_mutex_lock(&work_mutex); //上锁 printf("Input some text. Enter 'end' to finish\n"); while(!time_to_exit) //判断是否输入了end,如果为1则退出循环写 { fgets(work_area, WORK_SIZE, stdin); //get a string from stdin //往共享区域里写 pthread_mutex_unlock(&work_mutex); //unlock the mutex //解锁 while(1) { pthread_mutex_lock(&work_mutex); //上锁 if (work_area[0] != '\0') //如果不是\0说明有字符还没读 { pthread_mutex_unlock(&work_mutex); //解锁 sleep(1); //休眠1秒让子线程读取完整 } else { break; //如果是\0 说明没有数据读了,则循环再次输入 } } } pthread_mutex_unlock(&work_mutex); //到了这里说明用户输入了end,解锁 printf("\nWaiting for thread to finish...\n"); res = pthread_join(a_thread, &thread_result); //阻塞等待子线程完成操作 if (res != 0) { perror("Thread join failed"); exit(EXIT_FAILURE); } printf("Thread joined\n"); pthread_mutex_destroy(&work_mutex); //删除锁 exit(EXIT_SUCCESS); //退出进程 } void *thread_function(void *arg) { sleep(1); //休眠1秒让主线程先上锁 pthread_mutex_lock(&work_mutex); //如果目标共享区域已上锁则自动休眠知道解锁为止 while(strncmp("end", work_area, 3) != 0) //上锁后判断共享区域是否输入end,如果有end则不进入循环 { printf("You input %d characters\n", strlen(work_area) -1); printf("the characters is %s",work_area); //打印共享区域的内容 work_area[0] = '\0'; //读完内容后那内容置0 pthread_mutex_unlock(&work_mutex); //解锁 sleep(1); //休眠1秒再次让主线程上锁 pthread_mutex_lock(&work_mutex); //上锁 while (work_area[0] == '\0' ) //判断是否有数据,一直循环直到有数据来 { pthread_mutex_unlock(&work_mutex); //如果什么都没有解锁 sleep(1); //休眠1秒 pthread_mutex_lock(&work_mutex); //上锁 } } time_to_exit = 1; //发出用户输入end想结束对话的信号状态 work_area[0] = '\0'; pthread_mutex_unlock(&work_mutex); pthread_exit(0); }
条件变量通信机制
pthread_mutex_t work_mutex int i=3; int j=7;
___________________________________________________ pthread_A()
{
pthread_mutex_lock
i++;
j--;
pthread_unlock
}
______________________________________________________
pthread_B()
{
pthread_mutex_lock
if(i==j)
{
do_something();
}
pthread_unlock
}
_________________________________________________________
这样的互斥锁虽然安全但是有BUG,因为线程也是和进程一样受限于CPU的随机调度 ,如果 i==5==j 的时候因为CPU的随机调度被A线程再次抢占了就导致 i==6 4==j ,
B线程会因此不满足出发条件, 且pthread_B只会在 i==j 才会执行,但是会不断的消耗CPU去判断 i是否等于j ,引入条件变量就可以解决这两个问题了
条件变量必须配合互斥锁使用
__________________________________________________________
pthread_A()
{
pthread_mutex_lock
i++;
j--;
if(i==j)
{
pthread_cond_signal(); 满足条件后发出信号
}
pthread_unlock
}
______________________________________________________
pthread_B()
{
pthread_mutex_lock
pthread_cond_wait(); 当前函数会阻塞(自动解锁,被唤醒后自动上锁),直到A线程发出信号才会继续执行
do_something();
pthread_unlock
}
条件变量对象: pthread_cond_t condtion
int pthread_cond_destroy(pthread_cond_t *cond); 销毁条件变量的对象
int pthread_cond_init(pthread_cond_t * cond,const pthread_condattr_t * attr); 初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
条件变量等待: timedwait()超过设定的时间限制会立刻返回 wait()等待,直到发出满足条件的信号为止
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
wait()会自动解锁mutex_lock等待其他线程触发条件激活wait
wait的实现原理 https://blog.csdn.net/Z_Stand/article/details/104309019
条件变量通知:
int pthread_cond_broadcast(pthread_cond_t *cond); 广播的方式触发所有的条件变量
int pthread_cond_signal(pthread_cond_t *cond); 单个的触发条件变量
读写锁: 对于读数据比修改数据频繁的应用,用读写锁代替互斥锁可以提高效率。
如果当前线程读数据 则允许其他线程同时并发进行读操作 但不允许写操作
如果当前线程写数据 则其他线程的读写都不允许操作
RCU: 内核中RCU 做链表的操作
pthread_rwlock_t 读写锁对象
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 销毁读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 初始化读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 解除锁定
读锁定 如果某线程申请了读锁定 其他线程依旧可以申请读锁 不能申请写锁定
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 阻塞式申请读锁定
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 非阻塞式申请读锁定
写锁定 如果某线程申请了写锁定 则其他线程不能申请读锁定 也不能申请写锁定
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 申请写锁定
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 非阻塞式申请写锁定
线程信号发送
int pthread_kill(pthread_t thread, int sig); sig=0检测指定线程是否存在
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
how = 阻塞:
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<signal.h> void *sigone_program(void *arg); void *sigtwo_program(void *arg); void report(int); pthread_t thread_one,thread_two; int main(int argc,char *argv[]) { int i; void *status; if(pthread_create(&thread_one,NULL,sigone_program,NULL)!=0) //创建线程1 { fprintf(stderr,"pthread_create failure\n"); exit(EXIT_FAILURE); } if(pthread_create(&thread_two,NULL,sigtwo_program,NULL)!=0) //创建线程2 { fprintf(stderr,"pthread_create failure\n"); exit(EXIT_FAILURE); } sleep(1); printf("this is parent ,send SIGUSR1,SIGUSR2 to thread %u\n",thread_one); if(pthread_kill(thread_one,SIGUSR1)!=0) //给指定线程发出已经屏蔽的信号 { perror("pthread_kill"); exit(EXIT_FAILURE); } if(pthread_kill(thread_one,SIGUSR2)!=0) //给指定线程发出非屏蔽的信号 { perror("pthread_kill"); exit(EXIT_FAILURE); } printf("this is parent ,send SIGUSR1,SIGUSR2 to thread %u\n",thread_two); if(pthread_kill(thread_two,SIGUSR1)!=0) { perror("pthread_kill"); exit(EXIT_FAILURE); } if(pthread_kill(thread_two,SIGUSR2)!=0) { perror("pthread_kill"); exit(EXIT_FAILURE); } sleep(1); if(pthread_kill(thread_one,SIGKILL)!=0) { perror("pthread_kill"); exit(EXIT_FAILURE); } printf("the end\n"); pthread_join(thread_two,NULL); pthread_join(thread_one,NULL); return 0; } void *sigone_program(void *arg) //线程函数1:屏蔽出SIGUSR2所有信号 { int i; __sigset_t set; signal(SIGUSR1,report); sigfillset(&set); sigdelset(&set,SIGUSR2); pthread_sigmask(SIG_SETMASK,&set,NULL); for(i=0;i<5;i++) { printf("this is set mask %u thread\n",pthread_self()); pause(); } } void report(int sig) //打印当前线程的收到的信号和当前线程ID { printf("\nin signal ,the sig=%d\t,the thread id=%u\n",sig,pthread_self()); } void *sigtwo_program(void *arg) //线程函数2:绑定SIGUSR2的信号函数 { int i; signal(SIGUSR2,report); for(i=0;i<5;i++) { printf("this is no set mask %u thread\n",pthread_self()); pause(); } }
线程属性: 除非有特殊需求 , 不然,一般不会去设置线程属性的
typedef struct __pthread_attr_s 线程属性的对象 { int __detachstate; 设置可取消属性 int __schedpolicy; 调度策略 struct __sched_param __schedparam; int __inheritsched; int __scope; size_t __guardsize; int __stackaddr_set; void *__stackaddr; size_t __stacksize;表示线程栈的大小。 }pthread_attr_t;
在创建线程时有个 attr的参数,可以通过这个参数设置线程的属性 , 比如
__detachstate
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); //获取属性
__detachstate:PTHREAD_CREATE_DETACHED--->不能被等待
PTHREAD_CREATE_JOINABLE--->默认可以等待
struct __pthread_attr_s attr;
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthrea_create(tid,&attr,func,argv);
经过上面的设置后 线程不会被pthread_join()所等待了