【总结】Linux内核剖析-------线程及其同步
线程概念巩固
在Linux中,多线程的本质仍是进程,它与进程的区别:
进程:独⽴地址空间,拥有PCB
线程:也有PCB,但没有独⽴的地址空间(共享)
注:进程控制块(PCB Process Control Block)
线程的特点:
1,线程是轻量级进程,有PCB,创建线程使⽤的底层函数和进程⼀样,都是clone
2,从内核看进程和线程是⼀样的,都有各⾃不同的PCB
3,进程可以蜕变成线程
4,在LINUX中,线程是最⼩的执⾏单位,进程是最⼩的分配资源单位
查看指定线程的LWP号命令:
1ps -Lf pid
线程优点:
提⾼程序并发性
开销⼩
数据通信,共享数据⽅便
线程缺点:
库函数 ,不稳定
调试,编写困难,GDB
对信号⽀持不好
线程属性,可以在⼀开始就设置好分离态,具体在下⾯的代码有说明!
线程同步,主要有互斥锁mutex,读写锁,条件变量,信号量
C语⾔中使⽤多线程的函数
对象操作Linux Pthread API Windows SDK 库对应 API
线程创建pthread_create CreateThread
退出pthread_exit ThreadExit
等待pthread_join WaitForSingleObject
互斥锁创建pthread_mutex_init CreateMutex
销毁pthread_mutex_destroy CloseHandle
加锁pthread_mutex_lock WaitForSingleObject 解锁pthread_mutex_unlock ReleaseMutex
创建pthread_cond_init CreateEvent
条件销毁pthread_cond_destroy CloseHandle
触发pthread_cond_signal SetEvent
⼴播pthread_cond_broadcast SetEvent / ResetEvent 等待pthread_cond_wait / pthread_cond_timedwait SingleObjectAndWait
对象操作Linux Pthread API Windows SDK 库对应 API
多线程开发在 Linux 平台上已经有成熟的 Pthread 库⽀持。其涉及的多线程开发的最基本概念主要包含四点:线程,互斥锁,条件变量、读写锁。其中,线程操作⼜分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,⼴播和等待。其他的⼀些线程扩展概念,如信号灯等,都可以通过上⾯的三个基本元素的基本操作封装出来。
创建线程
int pthread_create(pthread_t * tid, const pthread_attr_t * attr, void * ( * func) (void * ), void * arg);
其返回值是⼀个整数,若创建进程成功返回0,否则,返回其他错误代码,也是正整数。
创建线程需要的参数:
线程变量名:pthread_t *类型,是标⽰线程的id,⼀般是⽆符号整形,这⾥也可以是引⽤类型,⽬的是⽤于返回创建线程的ID
线程的属性指针:制定线程的属性,⽐如线程优先*级,初始栈⼤⼩等,通常情况使⽤的都是指针。
创建线程的程序代码:⼀般是函数指针,进程创建后执⾏该函数指针指向的函数。
程序代码的参数:若线程执⾏的函数包含由若⼲个参数,需要将这些参数封装成结构体,并传递给它指针。
创建线程的函数的形式如下
结束线程
结束进程的函数定义如下:
void pthread_exit (void *status);
参数是指针类型,⽤于存储线程结束后返回状态。
线程等待
在 Linux 平台下,当处理线程结束时需要注意的⼀个问题就是如何让⼀个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独⽴的,⼀个线程的终⽌不会去通知或影响其他的线程。但是已经终⽌的线程的资源并不会随着线程的终⽌⽽得到释放,我们需要调⽤ pthread_join() 来获得另⼀个线程的终⽌状态并且释放该线程所占的资源。 Pthread_join() 函数的定义:
int pthread_join (pthread_t tid, void ** status);
第⼀个参数表⽰要等待的进程的id;
第⼆参数表⽰要等待的进程的返回状态,是个⼆级指针。
调⽤该函数的线程将挂起,等待 th 所表⽰的线程的结束。 thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表⽰的线程必须是 joinable 的,即处于⾮ detached(游离)状态;并且只可以有唯⼀的⼀个线程对 th 调⽤ pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调⽤将返回错误。
如果你压根⼉不关⼼⼀个线程的结束状态,那么也可以将⼀个线程设置为 detached 状态,从⽽来让操作系统在该线程结束时来回收它所占的资源。将⼀个线程设置为 detached 状态可以通过两种⽅式来实现。⼀种是调⽤ pthread_detach() 函数,可以将线程 th 设置为detached 状态。
pthread_detach 函数定义:
int pthread_detach(pthread_t th);
另⼀种⽅法是在创建线程时就将它设置为 detached 状态,⾸先初始化⼀个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传⼊线程创建函数 pthread_create(),这样所创建出来的线程就直接处于 detached 状态。⽅法如下。
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);
总之为了在使⽤ Pthread 时避免线程的资源在线程结束时不能得到正确释放,从⽽避免产⽣潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调⽤ pthread_join() 函数来对其进⾏资源回收。
线程创建后怎么执⾏,新线程和⽼线程谁先执⾏这些不是程序来决定,⽽是由操作系统进⾏调度的,但是在编程的时候我们常常需要多个线程配合⼯作,⽐如在结束某个线程之前,需要等待另外⼀个线程的处理结果(返回状态等信息),这时候就需要使⽤线程等待函数
函数pthread_join⽤来等待⼀个线程的结束。函数原型为:
extern int pthread_join __P ((pthread_t __th, void **__thread_return));
第⼀个参数为被等待的线程标识符,第⼆个参数为⼀个⽤户定义的指针,它可以⽤来存储被等待线程的返回值。这个函数是⼀个线程阻塞的函数,调⽤它的线程将⼀直等待到被等待的线程结束为⽌,当函数返回时,被等待线程的资源被收回。⼀个线程的结束有两种途径,⼀种是象我们上⾯的例⼦⼀样,函数结束了,调⽤它的线程也就结束了;
另⼀种⽅式是通过函数pthread_exit来实现。它的函数原型为:
linux内核设计与实现 pdf extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
唯⼀的参数是函数的返回代码,只要pthread_exit中的参数retval不是NULL,这个值将被传递给 thread_return。最后要说明的是,⼀个线程不能被多个线程等待,否则第⼀个接收到信号的线程成功返回,其余调⽤pthread_join的线程则返回错误代码ESRCH。
总结:
pthread_join⽤于等待⼀个线程的结束,也就是主线程中要是加了这段代码,就会在加代码的位置卡主,直到这个线程执⾏完毕才往下⾛。
pthread_exit⽤于强制退出⼀个线程(⾮执⾏完毕退出),⼀般⽤于线程内部。
结合⽤法:
⼀般都是pthread_exit在线程内退出,然后返回⼀个值。这个时候就跳到主线程的pthread_join了(因为⼀直在等你结束),这个返回值会直接送到pthread_join,实现了主与分线程的通信。
注意事项:
exit()是进程退出,如果在线程函数中调⽤exit,那该线程的进程也就挂了。会导致该线程所在进程的其他线程也挂掉,⽐较严重
这个线程退出的返回值的格式是void*,⽆论是什么格式都要强转成void*才能返回出来主线程(pthread_exit((void*)tmp);),⽽这个时候pthread_join就去接这个值,我们传进去⼀个void*的地址也就是&(void*),传地址进去接值是接⼝类函数常⽤的做法,有同样效果的做法是引⽤&,但是这个
做法⼀来值容易被误改,⼆来不规范,所以定义⼀个类型然后把地址传进去修改value。回到题⽬,这⾥返回的void*是⼀个指针类型,必须强转成对应的指针才能⽤。
举个例⼦,如果是char* = “mimida”;传出来的tmp,必须(char*)tmp⼀下。
⽽如果是int* a = new int(3888);这种类型返回的tmp,必须*(int*)tmp⼀下才能⽤。
最重要的⼀点,你定义的类型和最后出来的类型⼀定要⼀致,不然很容易出现问题。也就是你定义了int*,最后强转出来的⼀定是*(int*)。
别void* a = (void*)10;这种诡异的格式(我就中过招),⼀开始是什么就转成什么!(这个规则同时也适⽤于线程数据⾥的set和get)
实例:
其他关于进程的函数
1. 返回当前线程ID
pthread_t pthread_self (void);
⽤于返回当前进程的ID
2. 制定线程变成分裂状态
int pthread_detach (pthread_t tid);
参数是指定线程的ID,指定的ID的线程变成分离状态;若指定线程是分离状态,则 如果线程退出,那么它所有的资源都将释放,如果线程不是分离状态,线程必须保留它的线程ID、退出状态,直到其他线程对他调⽤的pthread_join()函数
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void print_message_func(void *ptr);
int main()
{
int tmp1,tmp2;
void *retival;
pthread_t thread1,thread2;
char *message1 = "thread1";
char *message2 = "thread2";
int ret_thread1,ret_thread2;
ret_thread1 = pthread_create(&thread1,NULL,(void *)&print_message_func,(void *)message1);
if(ret_thread1 == 0)
printf("create thread 1 true\n");
else
printf("create thread 1 false\n");
tmp1 = pthread_join(thread1,&retival);
printf("thread 1 return value (tmp1) is %d\n",tmp1);
if(tmp1 != 0)
printf("cannot join with thread 1\n");
ret_thread2 = pthread_create(&thread2,NULL,(void *)&print_message_func,(void *)message2);
if(ret_thread2 == 0)
printf("create thread 2 true\n");
else
printf("create thread 2 false\n");
tmp2 = pthread_join(thread2,&retival);
printf("thread 2 return value (tmp2) is %d\n",tmp2);
if(tmp2 != 0)
printf("cannot join with thread 2\n");
}
void print_message_func(void *ptr)
{
for(int i=0;i<5;++i)
{
printf("%s:%d\n",(char*)ptr,i);
}
}
线程属性
线程属性,在创建时分离代码:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论