文章目录
一、线程的概念
线程:进程内部的一条执行路径或序列,一个进程可以包含多条线程。(CPU调度和执行的基本单位)
例如:main()函数从第一行开始运行直到主函数结束
进程:一个正在进行的程序 (资源分配的基本单位)
二、线程的实现方式
在操作系统中,线程的实现有以下三种方式:
◼ 内核级线程
◼ 用户级线程
◼ 组合级线程
用户级与内核级的区别:
用户级:创建开销小,由线程库直接管理。无法使用多处理器资源
内核级:创建开销大,由内核直接管理。可以使用多处理器的资源
Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux把所有的线程都当作进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的task_struct,所以在内核中,它看起来就就像是一个普通的进程(只是该进程和其他一些进程共享某些资源,如地址空间)。
ps -eLf 可以看到进程中的线程
三、线程库中的接口介绍
#include <pthread.h>//头文件
int pthread_create(pthread_t *thread, const pthread_attr_t*attr,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 线程退出时,指定的退出信息
*/
四、线程的简单使用
4.1.主函数创建一个线程
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* fun(void*arg)
{
int i = 0;
for(;i < 10;i++)
{
printf("fun hello\n");
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
int i = 0;
for(;i < 5;i++)
{
printf("main hello\n");
sleep(1);
}
}
问题:main()跟fun()并发进行,但是main()结束时,fun()并没有完成自己的工作,也跟着main()结束。
解决办法:使用pthread_exit()以及pthread_join()让主线程main等待fun结束之后再结束。
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* fun(void*arg)
{
int i = 0;
for(;i <10;i++)
{
printf("fun hello\n");
sleep(1);
}
pthread_exit("fun over");
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
int i = 0;
for(;i < 5;i++)
{
printf("main hello\n");
sleep(1);
}
char *s = NULL;
pthread_join(id,(void**)&s);
printf("s = %s\n",s);
}
4.2.主函数创建多个线程
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* thread(void * arg)
{
int index = *(int *)arg;
int i = 0;
for(;i<3;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,(void*)&i);
}
for(i = 0;i<5;i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
通过运行代码,发现运行的结果不是像我们预料的那样,每隔1秒,输出0-4(顺序可能会乱)。
实际的输出结果并不是这样的,原因是pthread_creat参数传入的是i的地址,而i的值在变化,当进程要获取i的值的时候,可能已经不是当初传进来的那个值,所以就会出现这种情况。
解决:传入i的值而不是地址
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* thread(void * arg)
{
int index = (int)arg;
int i = 0;
for(;i<3;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,(void*)i);
}
for(i = 0;i<5;i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
4.3.多进程对一个变量进行操作
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
int g = 0;
void* thread(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,NULL);
}
for(i = 0;i<5;i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
多次运行之后,发现某一次g没有加到5000,原因是再某一时刻,两个线程或多个线程同时对g进行+1操作,而最终两个线程或多个线程只是对g进行了一次+1操作,导致g没有加到5000
解决:使用互斥锁,信号量等
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
int g = 0;
pthread_mutex_t mutex;
void* thread(void * arg)
{
int i = 0;
for(;i<1000;i++)
{
pthread_mutex_lock(&mutex);
printf("g = %d\n",++g);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id[5];
int i = 0;
for(;i<5;i++)
{
pthread_create(&id[i],NULL,thread,NULL);
}
for(i = 0;i<5;i++)
{
pthread_join(id[i],NULL);
}
pthread_mutex_destroy(&mutex);
exit(0);
}
~
五、线程安全
线程安全:简单来说线程安全就是多个线程并发同一段代码时(不管线程的运行顺序如何),都会出现想要的结果,我们就可以说该线程是安全的。
strtok()函数不能在多线程中使用
例如下面的函数:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void* fun(void * arg)
{
char buff[] = {"1 2 3 4 5 6 7 8 9"};
char *s = strtok(buff," ");
while( s != NULL )
{
printf("fun s = %s\n",s);
sleep(1);
s = strtok(NULL," ");
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
char str[] = {"a b c d e f g k l"};
char *p = strtok(str," ");
while( p != NULL)
{
printf("p = %s\n",p);
sleep(1);
p = strtok(NULL," ");
}
pthread_join(id,NULL);
}
改进方法:使用strtok_r()函数:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void* fun(void * arg)
{
char buff[] = {"1 2 3 4 5 6 7 8 9"};
char * ptr = NULL;
char *s = strtok_r(buff," ",&ptr);
while( s != NULL )
{
printf("fun s = %s\n",s);
sleep(1);
s = strtok_r(NULL," ",&ptr);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
char str[] = {"a b c d e f g k l"};
char *ptr = NULL;
char *p = strtok_r(str," ",&ptr);
while( p != NULL)
{
printf("p = %s\n",p);
sleep(1);
p = strtok_r(NULL," ",&ptr);
}
pthread_join(id,NULL);
}
六、线程创建
6.1.创建进程上限
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void* fun(void * arg)
{
while(1)
{
sleep(1);
}
}
int main()
{
pthread_t id;
int i = 0;
for(l;i<10000;i++)
{
int res = pthread_create(&id,NULL,fun,NULL);
if(res != 0)
{
break;
}
printf("i = %d\n",i);
}
pthread_join(id,NULL);
}
6.2.线程+fork()
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *fun(void* arg)
{
fork();
int i = 0;
for(;i<5;i++)
{
printf("fun run pid = %d\n",getpid());
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
int i = 0;
for(;i<5;i++)
{
printf("main run pid = %d\n",getpid());
sleep(1);
}
}
6.3.线程+互斥锁+fork
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<sys/wait.h>
pthread_mutex_t mutex;
void *fun(void* arg)
{
pthread_mutex_lock(&mutex);
printf("fun lock!\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
pthread_t id;
pthread_mutex_init(&mutex,NULL);
pthread_create(&id,NULL,fun,NULL);
sleep(1);
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
printf("child lock start\n");
pthread_mutex_lock(&mutex);
printf("child lock success!\n");
pthread_mutex_unlock(&mutex);
exit(0);
}
wait(NULL);
printf("main over\n");
}
fork()会复制父进程的锁,初始状态由复制时父进程中锁的状态决定。所以该程序会阻塞
解决办法:使用pthread_atfork()对fork进行限制,fork之前加锁,之后解锁。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<sys/wait.h>
pthread_mutex_t mutex;
void fun_lock()
{
pthread_mutex_lock(&mutex);
}
void fun_unlock()
{
pthread_mutex_unlock(&mutex);
}
void *fun(void* arg)
{
pthread_mutex_lock(&mutex);
printf("fun lock!\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
pthread_atfork(fun_lock,fun_unlock,fun_unlock);
pthread_t id;
pthread_mutex_init(&mutex,NULL);
pthread_create(&id,NULL,fun,NULL);
sleep(1);
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
printf("child lock start\n");
pthread_mutex_lock(&mutex);
printf("child lock success!\n");
pthread_mutex_unlock(&mutex);
exit(0);
}
wait(NULL);
printf("main over\n");
}
6.4.三个线程依次输出ABC(线程+信号量)
题目说明:三个线程,第一个线程输出A,第二个线程输出B,第三个线程输出C(要求:第一个要输出A,A输出完成之后才能输出B,B输出完成之后才能输出C。例如:ABCABCABC)
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
sem_t sem1;
sem_t sem2;
sem_t sem3;
void* fun1(void* arg)
{
int i = 0;
for(;i<5;i++)
{
sem_wait(&sem1);//ps1
printf("A");
fflush(stdout);
sem_post(&sem2);//vs2
}
}
void* fun2(void* arg)
{
int i = 0;
for(;i<5;i++)
{
sem_wait(&sem2);//ps2
printf("B");
fflush(stdout);
sem_post(&sem3);//vs3
}
}
void* fun3(void* arg)
{
int i = 0;
for(;i<5;i++)
{
sem_wait(&sem3);//ps3
printf("C");
fflush(stdout);
sem_post(&sem1);//vs1
}
}
int main()
{
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
sem_init(&sem3,0,0);
pthread_t id[3];
pthread_create(&id[0],NULL,fun1,NULL);
pthread_create(&id[1],NULL,fun2,NULL);
pthread_create(&id[2],NULL,fun3,NULL);
int i = 0;
for(;i<3;i++)
{
pthread_join(id[i],NULL);
}
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
}
输出结果:ABCABCABCABCABC