这是我自己的一个Linux系统编程学习路上的一个学习笔记,学习的过程中看过一些视频+博客,所以在学习过后根据记录的笔记来完成代码实现的过程中,可能会出现一大段文章内容和别人写的一样或者某些思想也会相同,如有侵权,请联系删除或者添加引用。(本文章不会作为商业用途)
线程: LWP(light weight process),轻量级进程,本质仍然是个进程(仅限于Linux操作系统成立)。
区别: 进程有独立的地址空间,拥有PCB。线程也拥有独立的PCB,但是没有独立的地址空间,它使用的是共享地址空间。在 Linux 操作系统下,线程是最小的执行单位,进程是最小分配资源的单位,可看成只有一个线程的进程。
ps -lf pid 指令查看指定某PID进程下的 LWP 线程号。
线程的优缺点:
1.优点:提高了程序的并发性、开销小、数据通信和共享数据方便。
2.缺点:库函数不稳定、gdb不支持调试、不支持信号。
总的来说,线程的优势非常显著,缺点对整个程序的影响不是很大。
pthread_t pthread_self(void); //获取当前进程的 ID int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); //创建线程 //thread:传出参数,创建成功后返回该线程的id。 //attr:设置线程属性,一般传NULL。使用系统默认属性。 //void *(*start_routine) (void *):线程真正工作的函数 //arg:线程工作函数的参数 void pthread_exit(void *retval); //将当前单个线程退出 //retval:表示线程退出状态,通常传NULL。 exit(0); //退出整个进程,该进程下所有的线程都会退出 return ; //返回到调用位置 int pthread_join(pthread_t thread, void **retval); //阻塞等待thread线程退出。此时该线程才算真正结束,它的内存空间也会被释放(被调用线程是非分离的) //retval:传出参数,获取线程退出状态。 int pthread_cancel(pthread_t thread); //杀死(取消) thread 线程 //在要杀死的子线程对应的处理的函数的内部, 必须做过一次系统调用 //该函数杀死线程需要一个契机,当函数进入内核(到达一个取消点)杀死一个进程。 //如果没有到达取消点,该函数无效。但是可以在线程函数内部使用pthread_testcancel()添加一个取消点。 //成功被pthread_cancel()杀死的线程,能够被pthread_join()函数回收。 int pthread_detach(pthread_t thread); //主线程与子线程分离,子线程结束后,资源自动回收。
线程属性: 对用户半透明,不想用户看到底层结构,但是可以通过相关函数对它进行配置。
int pthread_attr_init(pthread_attr_t *attr); //初始化线程属性 int pthread_attr_destroy(pthread_attr_t *attr); //销毁线程属性所占用的资源 int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); //设置线程的属性是分离或者非分离 //attr:需要设置的线程属性变量 //detachstate:1.PTHREAD_CREATE_DETACHED 设置为分离属性;2.PTHREAD_CREATE_JOINABLE 设置属性为非分离 int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); //获取线程属性变量的属性 //detachstate:传出参数,线程属性变量的属性值 也可以使用pthread_join()函数判断thread线程是否为分离状态,join能成功阻塞回收,说明没有分离;若是失败了,则说明已经分离了。
线程使用需要注意:
1.主线程退出而其他线程不退出,主线程应调用 pthread_exit() 函数。
2.避免僵尸线程:pthread_join()、pthread_detach、create时指定分离属性、不应当返回被回收线程栈中的值。
3.new() 和 mmap() 申请的内存可以被其他线程释放。
4.应避免在多线程模型中调用fork(),除非马上exec,子进程中只有调用fork()的线程存在,其他线程在子进程中均自动pthread_exit()掉。(不推荐线程中创建进程)
5.信号的复杂语义很难和多线程共存,应避免在多线程中引入信号机制。用信号就不用多线程,用线程就不要用信号!
同步: 协同步调,线程按照预定的先后次序执行。
指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其他线程为保证数据的一致性,不能调用该功能。
程序中数据混乱原因: 1.资源共享(独享资源则不会);2.调度随机(意味着数据访问会出现竞争);3.线程间缺乏同步机制。
线程同步方法: 1.互斥量 mutex,也称为互斥锁; 2.条件变量 cond ; 3.信号量
互斥锁 mutex 的使用 1. pthread_mutex_t lock; //创建锁。本质为一个结构体,但在使用的过程中可以看作一个整数,取值为 0、1 。 2. int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //初始化锁 //restrict:关键字,用来限定指针,表示由该指针指向的内存地址中内容的操作,只能由本指针完成 //attr:设置互斥锁的属性,一般传NULL,表示使用默认的互斥锁属性,默认属性为快速互斥锁 。 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态创建互斥锁,其属性为默认属性。同pthread_mutex_init()的 attr 参数传NULL。 3.int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁,表示要使用某个共享资源了 //如果把锁 lock 看成一个整数,加锁等同于 lock-- 操作。(此时 lock == 0) 4.访问共享数据 5.int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁,表示自己对共享资源的使用已经完成,其他的线程可以使用该共享数据了。 //如果把锁 lock 看成一个整数,解锁等同于 lock++ 操作。(此时 lock == 1) 6.int pthread_mutex_destroy(pthread_mutex_t *mutex); //销毁互斥锁。由主线程决定什么时候销毁这把锁。 补充: 1.int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试加锁 //加锁成功的话,lock-- ,此时等同于pthread_mutex_lock();加锁失败:返回,同时设置错误号errno为BUSY 2.读写锁:写独占,读共享 锁只有一把。但可以有两种模式: (1). 以读的方式给数据加锁——读锁。 (2). 以写的方式给数据加锁——写锁。 写锁的优先级更高。 pthread_rwlock_t rwlock; //创建读写锁 pthread_rwlock_init(&rwlock, NULL); //初始化读写锁 pthread_rwlock_wrlock(&rwlock); //加写锁。改变共享数据的值的时候加锁,且只能加锁一次,即写独占。 pthread_rwlock_rdlock(&rwlock); //加读锁。读取共享数据的值的时候加锁,可以加无限制个,即读共享。(在我看来,在读数据的时候加不加这个锁似乎都不影响,希望大佬指正) pthread_rwlock_unlock(&rwlock); //解锁 pthread_rwlock_destroy(&rwlock); //销毁锁 3.死锁:不是一种锁,而是使用锁不恰当导致的现象: (1)对一个锁反复加锁 (2)两个线程各自持有一把锁,去请求另一把锁。 所以我个人觉得,一个程序中尽量不要设置太多的锁。
互斥锁使用技巧:
尽量保证锁的粒度,越小越好。(访问共享数据前一步加锁,访问结束后立即解锁。)
条件变量的使用:条件变量本身不是锁,但通常结合锁的使用方式来使用。 1. pthread_cond_t cond; //定义一个条件变量 2. int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); //初始化条件变量 //attr:条件变量属性,一般传NULL,使用系统默认。 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//等同于pthread_cond_init()函数的attr参数传NULL 3. int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁 4. int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); //阻塞等待 cond 条件变量不为0。 //mutex:互斥量。因为pthread_cond_wait()函数做了以下三件事情: //(1).阻塞线程 //(2).释放(解锁)已经掌握的互斥量 mutex; //(3).当条件变量 cond 满足(cond != 0 )时,线程被唤醒,同时对 mutex 进行加锁处理 5. int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁 6. int pthread_cond_signal(pthread_cond_t *cond); //如果把pthread_cond_wait()看成是让 cond-- 的函数,那么它让 cond++ //pthread_cond_signal——唤醒睡眠的线程,一次只能唤醒一个线程 int pthread_cond_broadcast(pthread_cond_t *cond); //如果把pthread_cond_wait()看成是让 cond-- 的函数,那么它让 cond++ //pthread_cond_broadcast——唤醒睡眠的线程,一次唤醒所有睡眠的线程 例子:生产者和消费者模型:https://xmuli.blog.csdn.net/article/details/105885580
信号量的使用: 信号量:相当于初始化为 N 的互斥量。信号量和信号没有任何关系,可用于线程也可用于进程间同步 1.sem_t sem;//定义一个信号量 2.int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化信号量 //pshared:=0:为当前进程的所有线程共享; 其值不为0时,此信号量在进程间共享 //value:信号量的初始值 3.int sem_wait(sem_t *sem); //相当于加锁,sem-- 4.int sem_trywait(sem_t *sem);//相当于尝试加锁 5.int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //abs_timeout 指定一个阻塞的时间上限,如果调用因不能立即执行递减而要阻塞。 6.int sem_post(sem_t *sem); //相当于解锁,sem++ 7.int sem_destroy(sem_t *sem);//销毁信号量,一般由主线程来完成信号量的销毁。 例子:同样可以使用生产者消费者模型。