Linux20 多线程编程

(一)多线程编程

线程:
是轻量级的进程。是进程内部的一条执行指令(一组有序指令),或者说是执行流。一个进程至少由一条线程,就是main函数所代表的执行序列。称之为主线程。通过线程库可以创建线程----函数线程。
主线程仅仅代表进程执行的第一条线程而已。当主线程通过线程库创建出主函数线程以后,所有线程就没有任何区别。
主线程默认结束,结束是整个进程。
进程和线程的区别:
1、 进程是资源分配的单位,线程是CPU调度执行的单位
2、 多进程进程间资源是独立的,同一进程中的多线程,资源是共享的
3、 进程fork后父子进程并发执行,同一个进程中的线程也是并发执行,执行顺序由系统的调度算法决定。
4、 Linux中进程的底层实现和线程的底层实现是一样的,根据参数有些事情做,有些事情不做,比如4G虚拟内存空间就不做。创建线程不会向操作系统申请资源。线程更加轻便、小巧,线程创建、调度、切换效率高。线程必须依赖于进程.

线程库的使用:
头文件#include<pthread.h>
(1)创建线程:
int pthread_create(pthread_t* id,pthread_attr_t* arr,void*(pthread_fun(void),void* arg);
id:线程id,线程创建时分配的线程id,传递一个地址
attr:线程属性,默认为空
pthred_fun:线程函数,新创建的线程的执行体,函数地址,指定新线程从哪里开始执行。
arg:传递给线程函数的参数,会传给pthread_fun函数。
成功返回0,失败返回错误码。
创建时给函数线程传参数的两种方式:
值传递
最多传递4字节数据,指针
将传递的值直接强转成void*
arg:类型是void*,记录传递的值。

pthread_create(&id[i],NULL,thread_fun,(void*)i);
void * thread_fun(void * arg){ int index = (int)arg;}

地址传递
将要传递的值的地址转化为void*
arg:类型是void*
记录的是传递的地址

pthread_create(&id[i],NULL,thread_fun,(void*)&i);
void * thread_fun(void * arg){ int index = *(int*)arg;}//解引用

主线程中后期对值的修改可能影响函数线程中获取值。
函数线程中通过地址对变量修改,也会影响线程中变量的值。
所以线程共享进程的4G虚拟内存空间。
(2)结束线程
void pthread_exit(void* reval);
主函数结束时进程结束,线程依赖于进程,进程结束所有线程都结束。这样是不合理的。
(3)等待线程结束来获取线程退出码
int pthread_join(pthread_t id,void** retval);
(4)取消一个线程
int pthread_cancel(pthread_t id);
仅仅是提出取消请求,并不会等待或者阻塞。attr可以修改取消选项。
线程库里面还有很多函数。设置线程属性的也在里面。
为什么使用多线程
同时做多件事
利用多处理器的资源

# include <pthread.h>
void * thread_fun(void *arg)
{
	int i=0;
	for(;i<10;i++)
	{
          printf("thread\n");
		  
	}
	pthread_exit(NULL);
}
int main()
{
	pthread_t id;
	pthread_create(&id,NULL,thread_fun,NULL);
	int i=0;
	for(;i<5;i++)
	{
		printf("main\n");
	}
	char *s=NULL;
	pthread_join(id,(void**)&s);
	pthread_exit(NULL);
}

(二) 线程的实现方式

1、 用户级进程
多线程是在用户态实现的,内核不支持多线程,用户代码就必须实现线程的创建、调度、销毁等工作。内核简单,线程切换速度快,因为不需要陷入内核。缺点:用户代码复杂,如果一条线程阻塞,则整个进程阻塞。
2、 内核级进程
3、 混合级进程

同一进程中的所有线程对于数据的共享。

(三) 多线程的数据共享

1、 全局:共享,虚拟地址相同,同一进程中的多个线程共享进程的4G虚拟地址空间页表
2、 栈区:不共享,创建线程时,将栈区数据的地址传递函数线程,只会在进程资源中申请栈区资源,用于线程函数的执行。进程中的数据区域,堆区多线程都是使用同一份,都是共享的。
3、 堆区:共享
4、 文件描述符:共享

(四) 线程同步

线程之间协同工作,用于多线程访问临界资源时,必须按照一定的先后顺序访问执行,因为线程中共享的数据过于多。
1、 线程级的信号量
进程间同步的信号量是系统V信号量,线程间同步的信号量POSIX,两个信号量不可以交互使用。

在这里插入图片描述
2、 互斥锁(自旋锁、读写锁)互斥量,加锁 解锁,一个临界资源进行控制
(原子操作的底层实现:在操作前屏蔽中断、操作后恢复中断,屏蔽和恢复由操作系统实现。)

在这里插入图片描述

读写锁:加锁的时候分两种,释放都一样,两个人都是读锁两个人都可以访问,写锁就不可以。但是一般不用,只用于提高效率。
3、 条件变量:当条件满足就通知线程做事情,没满足就等着。
初始化:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.
动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.
当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论。
等待条件:

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

成功则返回0, 出错则返回错误编号.
这两个函数分别是阻塞等待和超时等待.
等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.
当pthread_cond_wait返回时, 互斥量再次被锁住.
通知条件:

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.
这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号。必须注意, 一定要在改变条件状态以后再给线程发送信号。
条件变量与互斥锁、信号量的区别:
1.互斥锁必须总是由给它上锁的线程解锁,信号量的挂出即不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,而另一个线程可以挂出该信号灯。
2.互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)。
3.由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。
4.互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。

(五) 线程安全

相同条件,同一份程序,多次执行结果不同,执行结果有二义性
1、 i++从内存中取i
2、 CPU执行加加
3、 将结果写入内存
原因:(一)线程并发(二)在并发系统上非原子操作(三)并行系统上操作是非互斥的(四)操作是同一个对象,线程之间数据共享。(二三)线程同步

(六) 多线程环境下创建进程

1、由一个线程调用fork函数创建子进程,在子进程中,只有调用fork的线程会被启动,其他线程不执行
2、多线程环境下进行加锁,然后创建进程,则子进程中对锁加锁可能会出现死锁。(保证子进程复制父进程的锁是解锁)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值