概念:线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。线程是进行资源调度和分配的基本单位 。
(1)每个进程至少有一条执行路径,所以一个进程至少有一个线程。
(2)每个进程都有一个主线程。
在操作系统中,线程的实现有以下三种方式:
(1)用户级线程:由线程库中的代码进行管理,处理 ,销毁。用户自己创建的多线程,即多个处理路径,无法使用多处理器的资源,在内核眼里就只是一条路径。
(2)内核级线程:由内核直接创建、直接管理、直接调度,直接结束。开销大,可以利用处理器的资源。
(3)组合级线程:内核空间允许其使用多处理器的资源。比如用户创建多个线程,内核可以创建两个线程来处理这些线程,以达到可以有效使用处理器资源的目的。
Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。
(1)进程是资源分配的最小单位,线程是 CPU 调度的最小单位。
(2)进程有自己的独立地址空间,线程共享进程中的地址空间。
(3)进程的创建消耗资源大,线程的创建相对较小。
(4)进程的切换开销大,线程的切换开销相对较小。
#include
/*
pthread_create()用于创建线程
thread: 接收创建的线程的 ID
attr: 指定线程的属性//一般传NULL
start_routine:指定线程函数
arg: 给线程函数传递的参数
成功返回 0, 失败返回错误码
*/
int pthread_create(pthread_t * thread, const pthread_attr_t *attr,void *(*start_routine) ( void *),void *arg);
/*
pthread_exit()退出线程
retval:指定退出信息
*/
int pthread_exit( void *retval);
/*
pthread_join()等待 thread 指定的线程退出,线程未退出时,该方法阻塞
retval:接收 thread 线程退出时,指定的退出信息
*/
int pthread_join(pthread_t thread, void **retval);
如下简单写一个多线程代码:
#include
#include
#include
#include
#include
#include
void* pthread_fun(void* arg)
{
for(int i = 0; i < 5; i++)
{
printf("fun run\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
int res = pthread_create(&tid,NULL,pthread_fun,NULL);
assert(res == 0);
for(int i = 0; i < 10; i++)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
运行结果(注意编译链接需要带上库 -lpthread):
如果子线程循环10次,主线程循环5次呢?代码及运行结果如下:
#include
#include
#include
#include
#include
#include
void* pthread_fun(void* arg)
{
for(int i = 0; i < 10; i++)
{
printf("fun run\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
int res = pthread_create(&tid,NULL,pthread_fun,NULL);
assert(res == 0);
for(int i = 0; i < 5; i++)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
运行结果:
发现主线程结束后,子线程并没有打印完也紧跟着结束了。
所以,主线程不会因为其他线程的结束而结束,但是其它线程的结束会因为主线程的结束而结束。这是因为主线程结束后会退出进程,所以进程里的其他线程都会终止结束。所以为了正常运行程序,一般我们都会让主线程等待其他线程结束后再结束。代码如下:
#include
#include
#include
#include
#include
#include
void* pthread_fun(void* arg)
{
for(int i = 0; i < 10; i++)
{
printf("fun run\n");
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
pthread_t tid;
int res = pthread_create(&tid,NULL,pthread_fun,NULL);
assert(res == 0);
for(int i = 0; i < 5; i++)
{
printf("main run\n");
sleep(1);
}
char* s = NULL;
pthread_join(tid,(void**)&s);
exit(0);
}
运行结果:
其他线程没有结束的话,主线程会在pthread_join()处阻塞。所以主线程会在其他线程结束之后再结束,程序正常退出。
示例代码 1:
#include
#include
#include
#include
#include
#include
void* pthread_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,pthread_fun,(void*)&i);
}
for(i = 0; i < 5; i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
运行结果1:
运行结果2:
为什么会产生这种情况呢?线程并发问题。
这是因为我们向pthread_fun传入i的地址。首先来说说为什么会出现多个线程拿到同一个i的值。线程创建在计算机中需要很多个步骤,我们进入for循环传入i的地址后就去进行下一个for循环,创建的线程还没有从地址中获取打印i的值,主函数就继续创建后面的线程了,导致多个线程并发,拿到同一个i值,而且不是创建该线程的时候i的值。
注意到打印第一个运行结果都是打印0,这是因为主函数第一个for循环已经结束了,后面一个for循环将i又置为0,而这些线程在主函数第一个for循环执行的时候,都没有回获取i的值打印,直到下一个for循环,这些线程才获取i值打印,所以打印出来 都是0。
示例代码 2:多线程并发访问同一块内存的问题
#include
#include
#include
#include
#include
#include
int g = 0;
void* pthread_fun(void* arg)
{
for(int i = 0; i < 1000; i++)
{
printf("g = %d\n",++g);
}
pthread_exit(NULL);
}
int main()
{
pthread_t id[5];
for(int i = 0; i < 5; i++)
{
pthread_create(&id[i],NULL,pthread_fun,NULL);
}
for(int j = 0; j < 5; j++)
{
char* s = NULL;
pthread_join(id[j],(void**)&s);
}
exit(0);
}
运行结果最后可能是5000,也可能是4900多,这是怎么回事呢?
看一下本人的虚拟机设置,处理器数量2个,每个处理器2个内核。
原因就是linux的线程是内核级线程。程序中对g++并不是原子操作,对g++,计算机需要 很多次操作 ,比如将内存中的g读取到寄存器中,再从寄存器中读走进行++,再回头进行写入等等一系列操作。可能一个线程拿到了内存中的g,还没来得及++再写回去,另一个线程被分配到另一个处理器上,读取了相同值的g进行++。所以我们得到的值有时候会比5000要小。
解决方法有:
(1)将处理器设置为单核处理器;
(2)进行线程同步。