一、进程和线程:
1、进程和线程的相关概念:
进程:是一个正在运行的程序,是个动态的概念。一个进程可以实现多个线程。
线程:是进程内部的一条执行路径或称为执行序列,不同平台下线程的实现机制不相同,但都被称为线程。
2、进程和线程的区别:
进程是资源分配的最小单位,线程是CPU 调度的最小单位。
进程有自己的独立地址空间,线程共享进程中的地址空间。
进程的创建消耗资源大,线程的创建相对较小。
进程的切换开销大,线程的切换开销相对较小。
二、线程实现方法
在操作系统中,线程的实现有以下三种方式:
用户级线程:开销小,但无法使用多个处理器;
内核级线程:相对来讲开销大,可以利用多个处理器;
组合级模型
Linux中线程的实现:
Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。
三、线程的使用
1、头文件
#include <pthread.h>
编译时需要带上 -lpthread
2、创建线程 --pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t*attr,void*(*start_routine)(void *), void *arg);
pthread_create() :用于创建线程
thread :接收创建的线程的
IDattr :指定线程的属性
start_routine :指定线程函数
arg :给线程函数传递的参数,成功返回0,失败返回错误码
3、退出线程–pthread_exit
int pthread_exit(void *retval);
pthread_exit() :退出线程。
retval :指定退出信息。
4、等待线程结束/合并线程–pthread_join()
int pthread_join(pthread_t thread, void**retval);
pthread_join() :等待thread指定的线程退出,线程未退出时,该方法阻塞;
retval :接收thread 线程退出时,指定的退出信息
四、多线程代码实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
//线程函数fun
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()
{
//创建线程id
pthread_t id;//该地址由pthread_create创建,其创建后会自动写入,不需要自己定义
//创建线程
//pthread_create(&id,NULL,pthread_fun,NULL);
int res = pthread_create(&id,NULL,pthread_fun,NULL);//创建了fun线程函数
assert(res == 0);
int i = 0;
for(; i < 5; i++)
{
sleep(1);
printf("main thread running\n");
}
//等待thread指定的线程退出,线程未退出时,该方法阻塞;
char *s = NULL;
pthread_join(id,(void **)&s);//将二级指针强转成void类型的
printf("s = %s\n",s);
exit(0);
}
运行结果:
不加sleep,难以观察到并发运行的发生:
五、线程并发运行(不加同步)
1、并发和并行
并发和并行(并行需要多个处理器,但多个处理器不一定是并行运行,因为要共享资源):
2、一次创建5个线程,让每个线程打印自己是第几个被创建的。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* thread_fun(void* arg)
{
int index = *((int *)arg);
int i = 0;
printf("index = %d\n",index);
sleep(1);
}
int main()
{
//创建五个线程
pthread_t id[5];
int i = 0;
for(; i < 5 ; i++)
{
pthread_create(&id[i],NULL,thread_fun,(void*)&i);
}
for( i = 0; i < 5 ; i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
运行结果:
发现输出的值每次运行都太不一样。
这里输出的index 是其运行输出函数时,那一刻所获取的 i 的值。而i的值该程序有三个函数再使用 i。而这个循环的执行速度非常快,所以获取i的值就会出错。
后面的0,较大概率是最后一个for循环的0。因为这里有pthread_join() (等待thread指定的线程退出,线程未退出时,该方法阻塞)。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* thread_fun(void* arg)
{
int index = *((int *)arg);
int i = 0;
for(; i < 5; i++)
{
printf("index = %d\n",index);
sleep(1);
}
}
int main()
{
//创建五个线程
pthread_t id[5];
int i = 0;
for(; i < 5 ; i++)
{
pthread_create(&id[i],NULL,thread_fun,(void*)&i);
}
for( i = 0; i < 5 ; i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
3、创建5个线程,同时对一个全局变量进行++,加到5000
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
int g = 0;
void* thread_fun(void* arg)
{
int i = 0;
for(; i < 1000; i++)
{
printf("g = %d\n",++g);
}
}
int main()
{
pthread_t id[5];
int i = 0;
for(; i < 5; i++)
{
pthread_create(&id[i],NULL,thread_fun,NULL);
}
for( i = 0; i < 5; i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
运行结果:
多处理器测试:
发现最终所得值小于等于5000;
单处理器测试:
结果都为5000
对于main.c ,计算机不能直接执行,编译好生成的main.exe/main ,程序由一条条指令构成,内部指令的排序格式:ELF(linux)、PE(Windows)。所以对于一个a+b运行时,最终最终转换成指令会是多条指令。而我们要将内存中的一个值进行++操作时,需要将其读到cpu里,由cpu内部的加法器进行++操作后,再写回内存。所以对于上面的代码,对g进行++的时候,对于多个并行线程对其++,就有可能在一个线程进行++的过程中,还没完成时,另一个线程错误获取了g,导致两个对其同时进行++,只+1;而循环次数却+2。所以回小于5000。