1.线程的概念与实现方式
1.1 线程的概念
线程:进程内部的一条执行路径,调度的基本单位,一个进程可以包含多个线程。
进程:一个正在运行的程序。动态,资源分配基本单位。
1.2 线程的实现方式
线程实现方式:用户 内核 组合
用户级:不管用户空间线程数目多少,内核空间只提供一个处理器(线程库管理,创建开销小,不能使用多个处理器)
内核级:用户线程数目与内核线程数目一一对应(内核管理,创建开销大,可以利用多个处理器)
组合级:先使用内核级分配模式,再使用用户及分配模式
单个处理器在当前生产工艺下,能处理的线程数目是有上限的。
Linux实现现成的机制非常独特,从内核的角度来说,他并没有线程这个概念。Linux把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据来表征线程。相反,线程仅仅被视为一个与其它进程共享某些资源的进程。每个线程都有属于自己的task_struct(PCB),所以在内核中,他看起来就像是一个普通的进程(只是该进程和其他一些进程共享某些资源,如地址空间)。
系统中的进程是双向链表形式
多进程中的pid有两个,但对外只呈现一个,要查看子进程的pid 用ps -eLf
线程的同步方式有四个:信号量 互斥锁 条件变量 读写锁
线程安全:多线程程序,无论调度顺序如何,都会得到正确的一致的结果
1.3 进程与线程的区别
1)进程是资源分配的最小单位,线程是CPU调度的最小单位
2)进程有自己的独立地址空间,线程共享进程中的地址空间
3)进程的创建消耗资源大,线程的创建相对较小
4)进程的切换开销大,线程的切换开销相对较小
2.线程使用
接口函数介绍:
#include<pthread.h>
int pthread_create(pthread_t*thread,const pthread_attr_t*arr,void*(*start_routine)(void*),void *arg);
//pthread_create()用于创建线程
//thread:接收创建的线程ID
//attr:指定线程的属性
//start_routine:指定线程函数
//arg:给线程函数传递的参数
//成功返回0,失败返回错误码
int pthread_exit(void*retval);
//pthread_exit()退出线程
//retval:指定退出信息
int pthread_join(pthread_t thread,void **retval);
//pthread_join()等待thread指定的线程退出,线程未退出时,该方法阻塞
//retval:接收thread线程退出时,指定的退出信息
多线程代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
void * pthread_fun( void *arg)
{
int i = 0;
for(; i < 5; ++i)
{
sleep(1);
printf("fun thread running\n");
}
pthread_exit("fun over");
}
int main()
{
pthread_t tid;
int res = pthread_create(&tid, NULL, pthread_fun, NULL);
assert(res == 0);
int i = 0;
for(; i < 5; ++i)
{
sleep(1);
printf("main thread running\n");
}
char *s = NULL;
pthread_join(tid, ( void **)&s);
printf("s = %s\n", s);
exit(0);
}
线程并发运行:
3.线程同步
线程同步指的是当一个线程在对某个临界资源进行操作时,其它线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协调步调,让线程按预定的先后次序进行运行。线程同步的方法有四种:互斥锁、信号量、条件变量、读写锁。
为什么有了互斥锁还要用信号量?
互斥锁只能用于互斥性场景,当信号量只有一个,即只取0和1时,二者可以互换,其他情况下只能用信号量
互斥锁和读写锁的区别:
假设有p1,p2,p3三个线程,如果使用互斥锁,则必须等待上一个线程操作完,才能进行下一个线程的操作;如果使用读写锁,就可以选择加读锁或者写锁,当加读锁时,可以让读操作的线程同时进行读取,当加写锁时,则和互斥锁一致。
条件变量:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t mutex;//等待队列的进入和出去,以及控制在进入和出去的时候不会被唤醒
pthread_cond_t cond;//
void* fun1(void* arg)
{
char* s = (char*)arg;
if ( s == NULL )
{
pthread_exit(NULL);
}
while( 1 )
{
pthread_mutex_lock(&mutex);//保证正在添加的时候不会被唤醒以及正在添加的时候不会有别的线程添加
pthread_cond_wait(&cond,&mutex);//添加到等待队列之后解除阻塞,唤醒之后要出队列加锁 ,原因同上
pthread_mutex_unlock(&mutex);//出完队列之后,解除阻塞
if ( strncmp(s,"end",3) == 0 )
{
break;
}
printf("fun1 read:%s\n",s);
}
}
void* fun2(void* arg)
{
char* s = (char*)arg;
if ( s == NULL )
{
pthread_exit(NULL);
}
while( 1 )
{
pthread_mutex_lock(&mutex);//lock
pthread_cond_wait(&cond,&mutex);//闃诲 && unlock
pthread_mutex_unlock(&mutex);
if ( strncmp(s,"end",3) == 0 )
{
break;
}
printf("fun2 read:%s\n",s);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);//锁初始化
pthread_cond_init(&cond,NULL);// 条件变量初始化
char buff[128] = {0};
pthread_t id1,id2;
pthread_create(&id1,NULL,fun1,buff);//创建线程
pthread_create(&id2,NULL,fun2,buff);
while( 1 )
{
fgets(buff,128,stdin);//从键盘中获取数据
if ( strncmp(buff,"end",3) == 0 )//如果获取的数据是end,则唤醒所有线程
{
pthread_mutex_lock(&mutex);//保证出队的时候不会被二次唤醒(防止出一半),也防止唤醒的时候没有线程正在出或正在进等待队列
pthread_cond_broadcast(&cond);// 唤醒
pthread_mutex_unlock(&mutex);//唤醒之后需要出去,谁拿到锁谁出去
break;
}
else//获取的不是end,则唤醒单个线程
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
pthread_join(id1,NULL);
//pthread_join()等待thread指定的线程退出,线程未退出时,该方法阻塞
//retval:接收thread线程退出时,指定的退出信息
pthread_join(id2,NULL);
exit(0);
}
读写锁:
fun1、fun2读,fun3写,代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_rwlock_t rwlock;
void* fun1(void* arg)
{
while( 1 )
{
pthread_rwlock_rdlock(&rwlock);
printf("fun1 read...start\n");
sleep(1);
printf("fun1 read...end\n");
pthread_rwlock_unlock(&rwlock);
int n = rand() % 3;
sleep(n);
}
}
void* fun2(void* arg)
{
while( 1 )
{
pthread_rwlock_rdlock(&rwlock);
printf("fun2 read...start\n");
sleep(2);
printf("fun2 read...end\n");
pthread_rwlock_unlock(&rwlock);
int n = rand() % 3;
sleep(n);
}
}
void* fun3(void* arg)
{
while( 1 )
{
pthread_rwlock_wrlock(&rwlock);
printf("fun3 write start\n");
sleep(2);
printf("fun3 write end\n");
pthread_rwlock_unlock(&rwlock);
int n = rand() % 3;
sleep(n);
}
}
int main()
{
pthread_rwlock_init(&rwlock,NULL);
pthread_t id1,id2,id3;
pthread_create(&id1,NULL,fun1,NULL);
pthread_create(&id2,NULL,fun2,NULL);
pthread_create(&id3,NULL,fun3,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_join(id3,NULL);
pthread_rwlock_destroy(&rwlock);
}
如果父进程里边有锁,复制之后子进程里边也有锁,且和父进程状态一致 ,如果父进程已经加锁还要在子进程里边加锁
代码如下:
在这里插入代码片
4.线程安全
线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的。那么说这些线程是安全的。
要保证线程安全需要做到:
1)对线程同步,保证同一时刻只有一个线程访问临界资源。
2)在多线程中使用线程安全的函数,所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竞态条件,则我们称他是线程安全的。
5.线程与fork
多线程里边的fork,在哪条路径,就启用哪条执行路径。
附加知识点:三次握手在connect之后完成,三次握手完成之后存放在listen中,由accept接收listen中的链接