Linux_线程

线程:线程是进程内部的一条执行序列(执行流)    
                  
(执行序列: 一组有序指令+数据的集合( 以函数为单位))
           每一个进程至少有一条线程,称之为主线程,从代码角度看,即为main()的函数体
           在主线程中可以通过线程库创建其它线程(函数线程)。
           主线程和函数线程会同时向下运行,一个进程中的线程都是并发执行的
           主线程仅仅代表进程执行的第一条线程而已。
           当主线程通过线程库创建出函数线程后,所有线程没有任何区别。
           主线程默认结束,则整个进程结束。

线程的实现方式:用户级、内核级、混合模式
用户级:函数的创建、销毁、管理都在用户空间完成、内核只会识别为,一个进程,一条线程。
              优点:灵活性,操作系统不知道线程的存在,在任何平台上都可以运行:
                         线程切换快,线程的切换在用户空间完成,不用陷入内核,
                         不用修改操作系统,容易实现。
              缺点:编程复杂,用户比须自己管理线程,包括线程调度
                         如果一个线程阻塞,整个进程都会阻塞
                         不能使用对称多处理器
内核级别:线程的创建、销毁、管理由操作系统内核完成,内核线程使得用户编程简单,但是每次切换都得陷入内核所以效率低
混合模式:即一部分以用户线程创建、一部分以内核创建,是一个多对多的关系,结合着用户级和内核级的优点

线程数据共享:栈区数据(.stack)全局数据(.data、.bss) 堆区数据(.heap) 文件描述符(fd)

进程和线程的区别
1、进程是系统资源分配的最小单位,线程是CPU调度的最小单位
2、一个进程可以包含多条线程, 一个线程只能属于一个进程
3、创建线程的代价比创建进程的代价小的多
4、进程的切换效率比线程的切换效率低
5、进程都是独立的个体,同一个进程中的所有线程只有自己的栈区,其他空间共享
6、进程间通讯必须借助于其他的手段,线程则可以直接通过共享的空间通讯
7、同一个进程中的线程之间存在安全问题, 而不同进程之间不存在

线程库的使用:线程库包含在pthread.h头文件中

线程创建:int pthread_create(pthread_t  *id,pthread_attr_t  *attr,void * (pthread_fun)(void *), void *arg);
                  pthread_create会创建一个新的函数线程,线程从pthread_fun函数入口地址开始执行
                  到pthread_fun函数结束。arg参数为给pthread_fun函数传递的参数,atr为线程属性
                  pthread_create函数创建成功返回0,失败返回错误码
结束线程:int  pthread_exit(void  *reval);   该函数会结束进程,进程结束则所有的线程会结束
等待线程结束:int  pthread_join(phtread_t   tid,  void  **val);
取消线程:int    pthread_cancel(pthread_t  tid);

创建函数线程时传参的两种方式:
值传递:最多传递四个字节的数据(指针),将传递的值直接强转为void*,arg记录的是传递的值
地址传递:为防止函数线程对其进行修改,需要进行判断,将要传递的地址转化为void*, arg记录的是传递的地址

同一个进程中的线程都是并发执行的,当线程需要访问临界资源时,必须同步执行。
控制线程同步的方式:信号量、互斥锁(自旋锁    读写锁)、条件变量

信号量:与进程间的信号量作用相似,当线程访问一些有限的共享资源时,就必须做到线程间同步访问
头文件:#include <semaphore.h>
初始化:int   sem_init(sem_t  *sem,   int  shared,   int  val);  将信号量sem的初始值设置为val
              sem一般被定义在线程共享的全局数据区,shared控制信号量是否可以在多个线程间共享(Linux不支持)
P操作: int   sem_wait(sem_t  *sem);  对sem进行-1操作,如果结果小于0,此函数阻塞,直到其它线程执行v操作
V操作: int   sem_post(sem_t   *sem); 对sem进行+1操作
销毁:   int   sem_destroy(sem_t   *sem);销毁sem

互斥锁:互斥锁是只能在线程之间使用的一种控制临界资源访问的机制,如果一个线程要访问临界资源,则必须先加锁,用完之后解锁,这样在一个线程访问临界资源的过程中,其它线程加锁就会阻塞,不能访问临界资源的临界区,直到访问临界资源的线程访问结束并解锁
头文件:#include <pthread.h>
初始化:int   pthread_mutex_init(pthread_mutex_t   *mutex,  pthread_mutexattr_t  *mutexattr);
              互斥锁mutex一般被定义在线程共享的全局数据区,初始化互斥锁mutex、attr锁的属性
加锁:int   pthread_mutex_lock(pthread_mutex_t   *mutex);
           int   phtead_mutex_trylock(pthread_mutex_t   *mutex);
解锁:int   pthread_mutex_unlock(pthread_mutex_t   *mutex);
销毁:int  phtread_mutex_destroy(pthread_mutex_t   *mutex);

使用任何锁都需要消耗系统资源(内存资源和CPU时间)
这种资源消耗可以分为两类:建立锁所需要的资源、当线程被阻塞时所需要的资

条件变量:条件变量是线程可用的另一种同步机制。条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件。
初始化:int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
销毁:int pthread_cond_destroy(pthread_cond_t *cond);
等待:int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
           int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t mytex,const struct timespec *abstime);  
           传递给pthread_cond_wait的互斥量对条件进行保护。调用者把锁住的互斥量传给该函数,
           函数自动把调用线程放到等待条件的线程列表上,对互斥量解锁。
           pthread_cond_wait返回时,互斥量再次被锁住。
条件变量通知:int pthread_cond_signal(pthread_cond_t *cond);
                         int pthread_cond_broadcast(pthread_cond_t *cond);

假设现有线程A和B,当线程A执行到一定程度时才能让线程B执行,这个时候就需要用到条件变量,设置一个全局变量P,初始条件为假,线程B等待条件为P为真,向下执行,条件由线程A设置为真。 线程B一直在轮询条件,但是在轮询的过程中会一直消耗CPU,导致资源的浪费,所以就设置了通知机制,当线程A将条件设为真之后通知B。 
条件变量一般和互斥锁一起使用,线程B在等待条件的时候是不占用锁的,此时线程A占用锁,当条件满足,线程B向下执行时会获得锁的所有权,此时线程A应该释放锁,因为线程A和线程B是严格按顺序执行的,所以不能同时都访问的临界资源(锁内资源),所以我们要加锁。

条件变量的详细介绍:https://blog.csdn.net/ithomer/article/details/6031723

线程安全
线程安全:
由于一个进程中的所有线程共享进程的全局数据、堆区数据、文件描述符,而线程又是并发执行的,所以有可能相同的初始数据,执行多次,得到的结果却不同。 因此在编译的过程中我们必须对线程访问的这些共享资源做同步控制
线程安全的方式:线程同步控制、可重入函数(主要针对系统调用函数和库函数)

可重入函数:如果一个函数在同一个时刻被多个线程安全的调用,则称该函数是线程安全的,否则,需要使用其可重入版本的函数保证线程安全。
例: 不保证线程安全的函数:      char  *  strtok(char  *buff,   char *flag);
 保证线程安全的可重入版本:      char  *strtok_r(char  *buff,   char  *flag,  char **ptrptr); 

多线程下创建进程以及锁的继承:
多线程中一个线程调用fork函数创建子进程,子进程中只有调用fork的线程被启动,其他线程不会执行,子进程会继承父进程中的锁及状态,所以有可能会造成子进程发生死锁,
解决子进程的死锁:在fork之前调用pthread_atfork函数
int  pthread_atfork(void (*parpare)(),void (*father)(),   void (*child)()); 此函数的作用是保证fork之后,父子进程空间中所有的锁都是解锁状态的

线程的编译要链接线程库:gcc -o main main.c -lpthread

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值