linux线程创建与同步问题(附demo)

所有的demo我都放在主页上,需要的可以前往下载

demo内容

我将常用的一些头文件单独罗列了出来,来看一下吧

#ifndef _HEAD_FILE_H__
#define _HEAD_FILE_H__

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

#endif
linux线程
	linux原本没有线程,后来在windows多线程编程影响下linux内核开发者在进程基础上在功能上做出了类似windows线程的linux版本的线程,linux线程归根到底还是进程,只不过是轻量级的进程,开销比真正进程要小得多,大家还是要明白linux与windows在线程方面功能虽然类似,但是底层实现是非常不同的。
linux进程大概实现原理
	在进程的基础上创建线程,原本进程PCB将会随着线程数量的增加被均分,原本进程的主人将会退化成主线程,但是这些线程所有的PCB所占总和相当于原来进程PCB空间。内核在任务切换时只识别PCB因此线程在效率上相比进程没有什么优势,对于内核而言线程就是进程,唯一的好处就是线程之间通信、同步比进程要方便的多,进程间共享的资源也更多,在系统开销方面也更小,在编写程序时能够更加灵活,例如原来全局变量父子进程不能共享,在线程上线程之间可以共享全局变量,但是有利有弊,像原来进程中经常使用的perror exit函数在多线程内并不能随意使用,如果某个线程调用exit函数,就会像原来杀死进程一样,杀死PCB中所有线程,杀伤范围太大。

资源分配
	主线程和子线程
	共享:
    .text
    .bss
    .data
    堆
    动态库加载区
    环境变量
    命令行参数

	进程通信:可采取全局变量、堆的形式
	不共享:
    栈(每个线程栈区是独立的,记录着每个线程各自独立的栈信息,因此线程通信不能通过局部变量进行通信)

多进程和多线程的区别
        始终共享的资源
            代码
            文件描述符
            内存映射区 --mmap
        线程共享
            堆
            全局变量
        线程节省资源
        在编译时需要在编译选项中增加-lpthread,调用标准线程库
线程操作相关函数
获得线程号
	#include <pthread.h>
		pthread_t pthread_self(void);
创建进程
	#include <pthread.h>
		int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                  void *(*start_routine) (void *), void *arg);
参数说明:

    thread:线程号的传出参数,用来存储线程号,类型为·pthread_t *·类型
    attr:线程属性无高级用法情况下填写NULL即可
    void (*start_routine) (void ):看起来比较复杂其实是一个参数为void *类型,返回值为void *类型的函数指针
    arg:是第三个函数的具体参数

返回值:

    成功返回0,失败返回错误号
    perror()不能使用该函数打印错误信息
    注意:

主线程先退出(使用exit、return形式),子线程会被强制结束

程序 01return.cpp

通过程序运行结果,我们可以看到父线程结束退出时,尽管子线程还没运行结束,但是随着父线程的结束子线程被迫结束了。

程序 02共享.cpp

线程打印错误信息
	在进程里面,根据函数返回值,判断某些操作执行失败,然后调用perror函数就可以打印错误信息,但是多线程挤在同一区域,错误信息各个线程是共享的即使有错误发生,打印出来错误信息,也不见得是本个线程自身的错误信息可能是别的线程有错误发生还没来得及打印,被别的线程抢先打印了,所以线程的错误信息不能调用perror函数打印,那么怎样打印线程的错误信息呢?,我们查看pthread_create函数帮助手册,手册在返回值这章介绍说,如果函数执行成功则返回0,执行失败则返回错误号,打印错误信息我们可以通过错误号去进一步寻找错误发生的具体原因,由于错误号各个线程是相互独立的,所以根据错误号检索出来的错误信息就跟线程对应上了。具体做法如下:
使用函数:strerror(int errnum);
单个线程退出
	void pthread_exit(void *retval);
	retval: void *类型,这个参数就是线程结束时向外传递的信息,可以让别的线程获得自己结束时的相关信息,需要说明的是,这个参数是指针类型,不能使用栈指针,因为线程结束对应栈信息被回收,所以这个参数必须指向全局变量或者堆
	注意:
	线程退出一定不要用exit,exit针对进程,如果在线程调用,那么在原来进程空间里所有的线程将都会被干掉,杀伤范围太大。

程序 03pthread_exit.cpp
	程序运行结果表明,父线程打印部分没有执行,证明父线程提前结束,父线程退出没有造成子线程强制退出,这是因为父线程退出行为不是由return或者exit造成,杀伤范围没有那么大。没有造成子线程的退出。
阻塞等待线程退出函数
	int pthread_join(pthread_t thread, void **retval);
程序 04pthread_join.cpp
	程序运行结果显示父线程等待子线程退出后,在执行父线程以后的代码,说明这个函数是阻塞性质的,而且获得了指定线程的退出时对外传递的相关信息。

线程分离
	int pthread_detach(pthread_t thread);
	作用:可以指定线程结束时自己回收自己的资源,不用再使用pthread_join函数。

在创建线程就设置分离属性
	在极端情况下有时候创建了线程,还没为其设置分离属性,线程就退出了,所以我们可以在线程创建时就可以其创建分离属性,具体方法如下:
	前面在线程创建时属性参数设置为NULL,这里我们可以利用这个属性为线程设置分离属性
    创建线程属性变量:pthread_attr_t attr
    对线程属性变量进行初始化
    int pthread_attr_init(pthread_attr_t *attr)
    设置线程分离属性
    int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate)
    参数:
    attr :线程属性变量
    detachstate:
    PTHREAD_CREATE_DETACHED(分离)
    PTHREAD_CREAT_JOINABLE(非分离)
    释放线程属性占用资源函数
    int pthread_attr_destroy(pthread_attr_t *attr)

杀死线程
	int pthread_cancel(pthread_t thread);
	注意:被指定要杀死的线程必须执行一次系统调用才能被杀死,如果线程内部处理函数没有系统调用函数,该线程不能被杀死,如果必须要结束该线程,可调用pthread_testcancel()函数,该函数无实际意义,但是执行的是系统调用,可以作为线程线程结束点。
程序 05pthread_cancel.cpp
	子线程执行两次就被父线程杀死,因为子线程存在cout函数,所以存在线程终止点,子线程被顺利杀死。

线程同步
	直接上程序 06线程同步1.cpp
	每个线程都对全局变量num自加一万次,因此理论上num值最终应该是20000,但是程序运行结果却不是这样。
	因为,当一个线程运行时,虽然for循环中i的值已经执行过了,但在for循环体中执行相关操作时CPU被抢夺了,赋值动作未完成,另一个线程取得的num值并不是经过增加num值,虽然i的值有序增加,但num因各种阴差阳错并没有真正累加到自身。造成了数据最后偏小的缘故,这样就出现了一个很现实的问题,就是对共享资源的保护问题,这就需要线程执行时要考虑同步问题。
线程同步思想
	多个线程对同一资源进行访问时,为确保每个线程对资源操作有效性,线程拿到资源时对资源加锁,其他线程不能对其进行操作,对资源操作结束后,开锁其他线程可以利用,使线程并发执行变成有序的顺序执行,使步调协调。

锁
互斥锁
	互斥锁的类型
	创建一把锁:pthread_mutex_t mutex;
	互斥锁的特点:多个线程访问共享数据的时候是串行的
	使用互斥锁缺点:效率低

	互斥锁使用步骤:
    创建互斥锁
    初始化互斥锁
    在线程访问共享数据处加锁
    线程访问共享数据结束后解锁
    所有线程使用完成后记得销毁锁

	函数:
		初始化:pthread_mutex_init(pthread_mutex_t *restrict mutex,
                    consst pthread_mutexattr_t *restrict attr);
		销毁:
			pthread_mutex_destroy(pthread_mutex_t *restrict mutex);
		加锁:
			pthread_mutex_lock(pthread_mutex_t *restrict mutex);
				mutex
				没有被上锁,当前线程会把这把锁锁上
				被锁上了:当前线程阻塞
				锁打开后线程解除阻塞
		尝试加锁:
			pthread_mutex_trylock(pthread_mutex_t *restrict mutex);
		解锁:
			pthread_mutex_unlock(pthread_mutex_t *restrict mutex);
程序 06线程同步2.cpp
	死锁:
		死锁就是线程自己锁定自己不能向下执行,造成死锁的原因:

    自己锁自己
	    自己锁了自己两次,线程在第二次上锁位置卡死

    线程有两把锁
    	有两把锁A1、A2,线程1上了A1锁,线程2上了A2锁,线程1想访问A2锁定资源,线程2想访问A1锁定资源,造成互相阻塞

    解决死锁
        让线程按照一定的顺序访问共享资源
        在访问其他锁的时候,需要先将自己的锁解开
        使用trylock函数

读写锁
	创建一把锁
	pthread_rwlock_t rwlock;
读写锁的特点
    读锁:对内存做读操作
    写锁:对内存做写操作
读写锁的特性:

    线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功
        读共享 -并行处理
    线程A加写锁成功,又来了三个线程,做读操作,三个线程阻塞
        写独占
    线程A加读锁成功,又来了B线程加写锁阻塞,又来了C线程加读锁阻塞
    
	
		读写不能同时
        写的优先级高


读写锁的场景练习

    线程A加写锁成功,线程B请求读锁
        线程B阻塞
    线程A持有读锁,线程B请求写锁
        线程B阻塞
    线程A拥有读锁,线程B请求读锁
        线程B加锁成功
    线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
        B阻塞 C阻塞-写的优先级高
        A解锁,B线程加锁成功,C继续阻塞
        B解锁,C加读锁成功
    线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
        线程B阻塞,线程C阻塞
        线程A解锁,线程C加锁成功,线程B阻塞
        线程C解锁,线程B加锁成功

读写锁适用场景

    互斥锁 - 读写串行
    读写锁:
        读:并行
        写:串行
    程序中读操作大于写操作的情况

主要操作函数

    读写锁初始化函数
    pthread_rwlock_init(pthread_rwlock_t *restrick rwlock,const pthread_relockattr_t *restrick attr);
    销毁读写锁
    pthread_rwlock_destroy(pthread_rwlock_t * rwlock);
    加读锁
    pthread_rwlock_rdlock(pthread_rwlock_t * rwlock);
    尝试加读锁
    pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock);
    加写锁
    pthread_rwlock_wrlock(pthread_rwlock_t * rwlock);
    尝试加写锁
    pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock);
    解锁
    pthread_rwlock_unlock(pthread_rwlock_t * rwlock);


条件变量(与互斥锁一起用)
	条件变量不是锁

    线程同步需要 条件变量+互斥锁
        互斥锁:保护一块共享数据
        条件变量:引起阻塞
            生产者和消费者模型

粗糙的例子说明
	生产者生产烧饼,消费者购买烧饼,条件变量就是有无烧饼,有烧饼消费者可以买(线程不阻塞),没烧饼消费者等待(线程阻塞),但是烧饼是公共的大家都可以买(为避免冲突烧饼有人买了其他人不能再买(加互斥锁保护))
	
	条件变量的两个小心思

    条件不满足,阻塞线程
    当条件满足,通知阻塞线程开始工作

条件变量类型:pthread_cond_t;
主要函数

    初始化一个变量
    pthread_cond_init(pthread_cond_t *restric cond,const pthread_condattr_t*restric attr);
    销毁一个变量
    pthread_cond_destroy(pthread_cond_t *cond);
    阻塞等待一个条件变量注意第二个参数为互斥锁类型
    pthread_cond_wait(pthread_cond_t *restric cond,pthread_mutex_t*restric mutex);

    阻塞线程
    将已上锁的mutex解锁
    唤醒至少一个阻塞在条件变量上的线程
    pthread_cond_signal(pthread_cond_t *cond);
    唤醒全部阻塞在条件变量上的线程
    pthread_cond_broadcast(pthread_cond_t *cond);

信号量
	semaphore.h
粗糙理解
可以理解成一个车库,车库有N个停车位(信号量个数),当有人占了一个车位就会加锁(信号量减一),当车从车位离开(信号量加一),当车库没有停车位即信号量为0,在想占有停车位资源只能等待(阻塞)
信号量类型

    sem_t sem
    加强版互斥锁

主要函数

    初始化信号量
    sem_init(sem_t *sem,int pshared,unsigned int value);

        sem:信号量
        pshared:0:线程同步 1:进程同步
        value:最多有几个线程操作共享数据

	销毁信号量
	sem_destroy(sem_t *sem);
	加锁
	sem_wait(sem_t *sem);
	调用一次相当于对于sem进行了一次减减操作,如果sem值为0,线程将会阻塞
	限时尝试加锁
	sem_timedwait(sem_t*sem,xxxxxx);
	尝试加锁
	sem_trywait(sem_t*sem)
	解锁
	sem_post(sem_t*sem)
	对sem进行加加操作

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值