线程的创建与同步
1、线程的概念与实现方式
(1)线程的概念
- 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在UnixSystem V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernelthread),而把用户线程(user thread)称为线程。
- 线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX
Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。 - 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
- 一个进程可以有很多线程,每条线程并行执行不同的任务。
- 在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
- 线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。
(2)线程的实现方式
Linux中线程的实现:
Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的task_ struct(pcb进程控制块), 所以在内核中,它看起来就像是一一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。
(3)进程与线程的区别
- 进程是资源分配的最小单位,线程是CPU调度的最小单位
- 进程有自己的独立地址空间,线程共享进程中的地址空间
- 进程的创建消耗资源大,线程的创建相对较小
- 进程的切换开销大,线程的切换开销相对较小
2、线程使用
(1)线程库中的接口介绍
- int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void
*(start_routine)(void),void *arg)
pthread_ create()用于创建线程
thread:接收创建的线程的ID
attr:指定线程的属性/默认(NULL)
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线程退出时,指定的退出信息
- gcc编译方式
gcc -o main main.c -lpthread//编译
(2)多线程代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void* thread_fun(void * arg)//该函数以指向void(任意类型)的指针函数 返回值也是void的指针
{
int i=0;
for( ; i<5;i++)
{
printf("fun run\n");
sleep(1);
}
pthread_exit("fun over\n");
}
int main()
{
pthread_t id;//id pthread_t类型数据
int res = pthread_create(&id, NULL,thread_fun,NULL);//&id 指向pthread_t类型的指针(线程创建时,该指针指向的变量将被写入一个标识符,用来引用新的线程)/NULL设置线程的属性/thread_fun线程将要启动执行的函数/NULL线程传递给该函数的参数
if( res !=0)
{
printf("creat thread err\n");
exit(0);
}
int i=0;
for( ; i<5; i++)
{
printf("main run\n");
sleep(1);
}
char *s =NULL;
pthread_join(id,(void**)&s);//等价于进程用来收集信息的wait函数/id 指定将要等待的线程,通过pthread_create返回的标识符来指定/(void**)&s一个指针指向另一个指针 后者指向线程的返回值
printf("s=%s\n",s);
exit(0);
}
注意:编译方式
(3)线程并发运行
- 示例1
- 创建五个线程打印0 1 2
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *fun(void *arg)
{
int index =*(int *)arg;
for(int i=0;i<3;i++)
{
printf("index=%d\n",index);
sleep(1);
}
}
int main()
{
pthread_t id[5];//创建5个线程id
int i=0;
for( ; i<5;i++)
{
pthread_create(&id[i],NULL,fun,&i);
}
for(i=0;i<5;i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
无顺序打印;占用线程时间不同
- 示例2:
使用三个线程打印ABCABCABCABCABC
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
sem_t sem1, sem2,sem3;
void* fun1(void* arg)
{
int i = 0;
for( ; i<5;i++ )
{
sem_wait(&sem1);
printf("A");
fflush(stdout);
sem_post(&sem2);
sleep(1);
}
}
void* fun2(void* arg)
{
int i = 0;
for( ; i<5;i++ )
{
sem_wait(&sem2);
printf("B");
fflush(stdout);
sem_post(&sem3);
sleep(1);
}
}
void* fun3(void* arg)
{
int i = 0;
for( ; i<5;i++ )
{
sem_wait(&sem3);
printf("C");
fflush(stdout);
sem_post(&sem1);
sleep(1);
}
}
int main()
{
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
sem_init(&sem3,0,0);
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);
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
}
3、线程同步
线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。线程同步的方法有四种:互斥锁、信号量、条件变量、读写锁。
(1)互斥锁
接口介绍
- int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t
*attr); - int pthread_mutex_lock(pthread_mutex_t *mutex);
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
- int pthread_mutex_destroy(pthread_mutex_t *mutex);
代码示例
示例代码如下:
主线程和函数线程模拟访问打印机,主线程输出第一个字符‘a’表示开始使用打印机,输出第二个字符‘a’表示结束使用,函数线程操作与主线程相同。(由于打印机同一时刻只能被一个线程使用,所以输出结果不应该出现 abab):
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
pthread_mutex_t mutex;
void * thread_fun( void* arg)
{
int i = 0;
for( ;i < 5; i++ )
{
pthread_mutex_lock(&mutex);
write(1,"B",1);
int n = rand() % 3;
sleep(n);
write(1,"B",1);
pthread_mutex_unlock(&mutex);
n = rand() % 3;
sleep(n);
}
pthread_exit(NULL);
}
int main()
{
pthread_t id;
pthread_mutex_init(&mutex,NULL);
pthread_create(&id,NULL, thread_fun,NULL);
int i = 0;
for( ;i < 5; i++ )
{
pthread_mutex_lock(&mutex);
write(1,"A",1);
int n = rand() % 3;
sleep(n);
write(1,"A",1);
pthread_mutex_unlock(&mutex);
n = rand() % 3;
sleep(n);
}
pthread_join(id,NULL);
pthread_mutex_destroy(&mutex);
exit(0);
}
(2)信号量
专栏里有详细介绍
(3)条件变量
接口介绍
- int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t
*attr);
//pthread _mutex_lock(&mutex);//先加锁
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex//释放 阻塞&&unlock&&lock);
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程
int pthread_cond_destroy(pthread_cond_t *cond);
代码示例
#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);//阻塞 释放 unlock
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);
pthread_cond_wait(&cond,&mutex);
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(&id1,NULL,fun2,buff);
while(1)
{
fgets(buff,128 ,stdin);
if(strncmp(buff,"end",3)==0)
{
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
break;
}
else
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
exit(0);
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/23765cc38283415290ebba51176522e7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4pWvIEluc3RpbmN0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
(4)读写锁
接口介绍
- int pthread_rwlock_init(pthread_rwlock_t *rwlock,
pthread_rwlockattr_t *attr); - int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
- int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
- int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
- int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
代码示例
#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) 在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。
- 不保证线程安全的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* PthreadFun( void *arg)
{
char buff[] = "a b c d e f g h i";
char *p = strtok(buff, " ");
while(p != NULL)
{
printf("fun:: %c\n", *p);
p = strtok(NULL, " ");
sleep(1);
}
}
int main()
{
pthread_t id;
int res = pthread_create(&id, NULL, PthreadFun, NULL);
assert(res == 0);
char buff[] = "1 2 3 4 5 6 7 8 9";
char *p = strtok(buff, " ");
while(p != NULL)
{
printf("main:: %c\n", *p);
p = strtok(NULL, " ");
sleep(1);
}
}
- 保证线程安全的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* PthreadFun( void *arg)
{
char buff[] = "a b c d e f g h i";
char *q = NULL;
char *p = strtok_r(buff, " ", &q);
while(p != NULL)
{
printf("fun:: %c\n", *p);
p = strtok_r(NULL, " ", &q);
sleep(1);
}
}
int main()
{
pthread_t id;
int res = pthread_create(&id, NULL, PthreadFun, NULL);
assert(res == 0);
char buff[] = "1 2 3 4 5 6 7 8 9";
char *q = NULL;
char *p = strtok_r(buff, " ", &q);
while(p != NULL)
{
printf("main:: %c\n", *p);
p = strtok_r(NULL, " ", &q);
sleep(1);
}
}