c++webserver/第三章 Linux线程开发

1.线程

写在前面补充:

1.当linux和Windows中,主线程以return 0结束时,程序会在主线程运行完毕后结束.
2.当linux中,主线程以pthread_exit(NULL)作为返回值,则主线程会等待子线程.
1.定义
  1. 与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。**一个进程可以包含多个线程。**同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的UNIX进程只是多线程程序的一个特例,该进程只包含一个线程)
  2. 进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。
  3. 线程是轻量级的进程(LWP: Light weight Process) ,在 Linux环境下线程的本质仍是进程。
  4. 查看指定进程的 LwP 号: ps -Lf pid

2.线程(存在意义)

  1. 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
  2. 调用fork ()来创建进程的代价相对较高,即便利用写时复制技术,仍热需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销依然不菲。
  3. 线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。
  4. 创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。

3.共享和非共享

  1. 栈和.text 会被分块,多个线程多份
  2. 信号掩码:阻塞信号集,未决信号集等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C5U84eLA-1646818322232)(第三章 Linux线程开发.assets/image-20220221220702357.png)]

4.线程函数

!!!注意编译时需要额外增加编译的库 -lpthread

1.创建一个线程

一般情况下main函数为主线程,其余的称之为子线程.子线程不共享主线程代码.子线程只是函数内容

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
/*
	1.	传出参数,线程创建成功后的线程ID.
	2.	设置线程的属性,一般使用默认(NULL)
	3.	函数指针,子线程需要处理的逻辑代码(线程执行的代码)
	4.	给函数的参数
	返回值:成功返回0,失败返回错误号,与errno不同;
		  获取错误号的信号:char *strerror(int errnum);[string.h]
*/

void * callback(void *arg){... return null;}
2.终止线程

终止一个线程,哪个线程中调用就终止哪个线程;

如果主线程退出,那么子线程也会退出

void pthread_exit(void *retval);
/*
	参数
	1.	需要传递一个指针,作为一个返回值,可以给pthread_join()中获取到;如果不需要就置NULL;
		!!注意不能返回一个局部变量!!.
*/
3.获取当前线程的线程ID
pthread_t pthread_self(void);
/*
	返回一个ID,长整型(ld);
*/
4.比较两个线程号是否相等

不同操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的是用结构体实现的;所以得用函数

int pthread_equal(pthread_t t1, pthread_t t2);
5.和一个已经终止的子线程进行连接/回收子线程资源

阻塞线程,调用一次阻塞直到回收一个子线程,且只能回收一个子线程,

int pthread_join(pthread_t thread, void **retval);
/*
	参数
	1.	需要回收的子线程的ID
	2.	接受子线程退出时的返回值(传出参数,就是exit退出时或者返回的值)
	返回值:成功0,失败返回非0(错误号) ;
*/
6.分离

分离一个线程,让线程自己运行.结束时自己会释放掉.

  1. 不能多次分类一个线程,会产生不可预料的行为
  2. 不能去连接一个已经分离的线程,会报错(不会崩溃)
int pthread_detach(pthread_t thread);
/*
	参数
	1.	需要分离线程的id
	返回值:成功返回0,失败返回-1;
*/
7.线程取消

取消线程,让线程终止,(任务终止);

能不能取消取决于 线程的state和type(在创建线程时候设置的).

并非立法终止,当执行到取消点(系统规定好的,大概就是用户区到内核区的切换)的时候才可以取消线程

int pthread_cancel(pthread_t thread);
/*
	1.	需要取消线程的id
	返回值:成功0,失败返回非0(错误号) ;
*/

5.线程属性

1.初始化线程状态
pthread_attr_t attr; //状态结构体
int pthread_attr_init(pthread_attr_t *attr);
2.释放线程属性的资源
int pthread_attr_destroy(pthread_attr_t *attr);
3.获取线程分离的状态属性

设置好后给pthread_create 创建时用

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
/*
	参数
	1.	属性变量
	2.	状态:[PTHREAD_CREATE_DETACHED(分离的) | PTHREAD_CREATE_JOINABLE(默认的,不分离)]
*/
4.设置线程分离的状态属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

6.线程同步

1.临界区

临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应终断该片段的执行。

lockunlock之间,临界资源 = 共享资源

2.线程同步

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作.其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。

3.互斥量
  1. 为避免线程更新共享变量时出现问题,可以使用互斥量(mutex是mutual exclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。
  2. 互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法。
  3. 一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议:
    1. 针对共享资源锁定互斥量
    2. 访问共享资源
    3. 对互斥量解锁

7.互斥锁相关函数

//互斥锁类型
pthread_mutex_t	mxid;
1.初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
/*
- 参数 :
    - mutex : 需要初始化的互斥量变量
    - attr : 互斥量相关的属性,一般置NULL
      restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
    			 可以防止其它指针修改指向的内容的值,保证只能通过自己修改
*/

2.销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.加锁
//直接加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
int pthread_mutex_lock(pthread_mutex_t *mutex);

//尝试加锁.不会阻塞,会直接返回。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
4.解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

8.死锁

1.定义

两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

2.产生场景/原因
  • 忘记释放锁
  • 重复加锁(不同函数加锁同一个,没释放)
  • 多线程多锁,抢占锁资源(顺序问题,两个资源加锁顺序不同.)

9.读写锁

1.定义
  • 当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。
  • 在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。读写锁的特点:
    • 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作
    • 如果有其它线程写数据,则其它线程都不允许读、写操作。
    • 写是独占的,写的优先级高。
2.函数
1.类型
pthread_rwlock_t rwlock;
2.初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
3.释放读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
4.加上读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
5.尝试加上读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
6.加上写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
7.尝试加上写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
8.解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

10.条件变量

1.条件变量类型
条件变量的类型 pthread_cond_t
2.初始化和释放
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
3.线程等待

调用了该函数,线程会阻塞。!用wait会先临时解除锁! 直到被唤醒才重新加锁

//一直等待,知道生产者生成数据
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/*
	参数:
	1.	条件变量类型
	2.	互斥量		
*/
//等待指定时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict abstime);
4.唤醒线程
//唤醒一个或者多个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);

//唤醒所有的等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

11.信号量

进程线程均可!

1.信号量的类型
sem_t sem;
2.初始化和释放
//初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
/*
    参数:
    sem : 信号量变量的地址
    pshared : 0 用在线程间 ,非0用在进程间
    value : 信号量中的值
*/

//释放资源
int sem_destroy(sem_t *sem);
3.加锁

记得先判断信号量再加锁,否则可能会死锁!

//对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞(先检查是否为0)
int sem_wait(sem_t *sem);

//尝试加锁
int sem_trywait(sem_t *sem);

//死锁流程如下
/*
	如果生产者拿到互斥锁,但是没有信号量.那么就是阻塞在信号量这里.互斥锁也不会解开
	这时候如果生产者会遇到互斥锁被占用,然后阻塞的情况,造成死锁
*/
4.解锁
//对信号量解锁,调用一次对信号量的值+1
int sem_post(sem_t *sem);
5.等待时间
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
6.获取信号量当前值
int sem_getvalue(sem_t *sem, int *sval);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

公仔面i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值