——文章部分摘自《Linux程序设计 中文第四版》
线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。
Linux把所有的线程都当作进程来实现。线程有自己的进程描述符以及堆栈空间,只是与其他一些进程共享某些资源,比如:地址空间,数据段。所以线程的创建和普通进程类似。
线程的实现方式有三种:
①内核级线程
②用户级线程
③组合级线程
建议参考:线程和进程的区别(面试必备)
读了上面这位博主写的文章我总结了进程与线程的区别主要概括为四点:
①内存空间层面:
进程有独立的堆栈空间和数据段,启动新的进程就会分配给它独立的地址空间,建立众多数据表来维护代码段、数据段和堆栈段,导致系统创建和切换进程的开销大。
线程拥有独立的堆栈空间,共享进程的地址空间、数据段,因此开销较小。
所以进程是资源分配的最小单位,线程是CPU调度的最小单位。
②通信机制层面:
进程间的通信机制有:管道、信号量、共享内存、消息队列。(具体可参考:进程间通信)
线程间的通信机制有:全局变量、Message消息机制、CEvent对象。(具体可参考:linux系统线程通信的几种方式,Linux进程间通信-线程间通信)
③CUP系统层面
④程序结构层面
优点:
①让程序看起来好像同时在做多件事情。
例如:我们使用word写文章,线程1负责处理用户的输入并执行文本编辑工作,线程2不断刷新单词计数变量,让用户了解自己的工作进度。
②线程可以让进程在等待连接之类的事情的同时能够执行一些其他的事情,从而能够提高程序的执行性能。
例如:包含输入、输出、计算的程序,将这三部分分为三个线程执行,当输入、输出的线程等待连接的时候,计算这个线程可以继续执行。
③多个线程对资源的需要远小于多个进程。
缺点:
①需要非常仔细的设计,避免因时序上的细微偏差或无意造成的变量共享而因为错误。
②多线程的调试比较难。
③线程的阻塞会影响整个进程的运行。
该函数用于创建线程,成功返回0,失败返回错误代码。
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void*(*start_coutine)(void*),void* arg);
第一个参数:指向pthread_t类型数据的指针,线程被创建时,这个指针指向的变量中将被写入一个标识符,用该标识符来引用新线程。
第二个参数:设置线程属性
第三个参数:线程将要启动执行的函数
第四个参数:传递给该函数的参数
该函数将会终止执行当前线程,并返回一个指向某个对象的指针(不能指向局部变量,因为线程结束,局部变量地址空间会被释放)。
int pthread_exit(void* retval);
第一个参数:返回一个指向某个对象的指针(不能指向局部变量,因为线程结束,局部变量地址空间会被释放)。
此函数作用时等待线程结束并收集线程返回的信息
成功返回0,失败返回错误码
int pthread_join(pthread_t th,void** thread_return);
第一个参数指定将要等待的线程,线程通过pthread_create返回的标识符来指定
第二个参数是一个指针,它指向另一个指针,而后指向线程的返回值。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <assert.h>
5 #include <pthread.h>
6 void* pthread_fun(void* arg)
7 {
8 for(int i=0;i<5;++i)
9 {
10 sleep(1);
11 printf("fun run\n");
12 }
13
14 pthread_exit("fun over\n");
15 }
16 int main()
17 {
18 pthread_t id;
19 pthread_create(&id,NULL,pthread_fun,NULL);
20
21 for(int i=0;i<5;++i)
22 {
23 sleep(1);
24 printf("main run\n");
25 }
26
27 char* s=NULL;
28 pthread_join(id,(void**)&s);
29 printf("%s\n",s);
30
31 exit(0);
32 }
并发线程示例1:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5 void* pthread_fun(void* arg)
6 {
7 int index=*((int *)arg);
8 for(int i=0;i<5;++i)
9 {
10 printf("%d\n",index);
11 sleep(1);
12 }
13 }
14 int main()
15 {
16 pthread_t id[5];
17 for(int i=0;i<5;++i)
18 {
19 pthread_create(&id[i],NULL,pthread_fun,(void*)&i);//这里传递的是i的地址,由于程序执行较快,当线程接受参数并打印的时候,i在很大的几率上都是++(i++)之后下一个值,所以打印出来是乱的,并不是理想情况下的0123
20 }
21
22 for(int i=0;i<5;++i)
23 {
24 pthread_join(id[i],NULL);
25 }
26
27 exit(0);
28 }
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <assert.h>
5 #include <pthread.h>
6 int g=0;
7 void* pthread_fun(void* arg)
8 {
9 for(int i=0;i<1000;++i)
10 {
11 printf("%d\n",++g);//打印结果<=5000,原因是多个线程进行,有可能某两个线程同时调用g,并对g同时++,那么返回相同的值,这就导致最后执行结束并不一定打印5000,可能小与5000
12 }
13 }
14 int main()
15 {
16 pthread_t id[5];
17 for(int i=0;i<5;++i)
18 {
19 pthread_create(&id[i],NULL,pthread_fun,NULL);
20 }
21
22 for(int i=0;i<5;++i)
23 {
24 pthread_join(id[i],NULL);
25 }
26 exit(0);
27 }
线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。
#include
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)
1 #include <stdio.h>
2 #include <assert.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <pthread.h>
6 pthread_mutex_t mutex;
7 void* pthread_fun(void* arg)
8 {
9 for(int i=0;i<5;++i)
10 {
11 pthread_mutex_lock(&mutex);
12 write(1,"B",1);
13 int n=rand()%3;
14 sleep(n);
15 write(1,"B",1);
16 pthread_mutex_unlock(&mutex);
17 n=rand()%3;
18 sleep(n);
19 }
20 pthread_exit(NULL);
21 }
22
23 int main()
24 {
25 pthread_mutex_init(&mutex,NULL);
26 pthread_t id;
27 pthread_create(&id,NULL,pthread_fun,NULL);
28
29 for(int i=0;i<5;++i)
30 {
31 pthread_mutex_lock(&mutex);
32 write(1,"A",1);
33 int n=rand()%3;
34 sleep(n);
35 write(1,"A",1);
36 pthread_mutex_unlock(&mutex);
37 n=rand()%3;
38 sleep(n);
39 }
40 printf("\n");
41 return 0;
42 }
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_destroy(sem_t *sem)
1 #include <stdio.h>
2 #include <string.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <pthread.h>
7 #include <assert.h>
8 #include <semaphore.h>
9
10 sem_t sem1;
11 sem_t sem2;
12 char buff[128];
13 void* thread_fun(void* arg)//线程负责从buff中读取内容写入到a.txt文件中
14 {
15 int fd=open("a.txt",O_RDWR|O_CREAT,0600);
16 assert(fd!=-1);
17
18 while(1)
19 {
20 sem_wait(&sem2);
21
22 if(strncmp(buff,"end",3)==0)
23 {
24 break;
25 }
26
27 write(fd,buff,strlen(buff));
28 memset(buff,0,sizeof(buff));
29 sem_post(&sem1);
30 }
31
32 sem_destroy(&sem1);
33 sem_destroy(&sem2);
34 }
35 int main()//主进程从输入流读取用户内容到buff中
36 {
37 pthread_t id;
38 int res=pthread_create(&id,NULL,thread_fun,NULL);
39 assert(res==0);
40 sem_init(&sem1,0,1);//将sem1信号初始化为1
41 sem_init(&sem2,0,0);//将sem2信号初始化为0
42
43 while(1)
44 {
45 sem_wait(&sem1);
46 printf("请输入:");
47 fflush(stdout);
48 fgets(buff,128,stdin);
49 buff[strlen(buff)-1]=0;
50 sem_post(&sem2);
51 if(strncmp(buff,"end",3)==0)
52 {
53 break;
54 }
55 }
56
57 exit(0);
58 }
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
#include
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程
int pthread_cond_destroy(pthread_cond_t *cond)
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <assert.h>
6 #include <pthread.h>
7 //设置互斥锁变量
8 pthread_mutex_t mutex;//锁主要控制对于条件变量在等待队列的进入、出去,以及保证在等待队列中进入或者出去的时候不会被其他线程唤醒
9 //设置条件变量
10 pthread_cond_t cond;
11 void* fun1(void* arg)
12 {
13 char* s=(char*)arg;
14
15 while(1)
16 {
17 pthread_mutex_lock(&mutex);//加入条件队列
/*(加17行这个锁的目的:
1、正在往条件队列中添加条件变量的时候,此线程不会被唤醒。
2、当前正在往条件队列中添加的时候,不会有其他条件变量也往进添加。)
*/
18 pthread_cond_wait(&cond,&mutex);//unlock 等待 被唤醒之后出等待队列 lock
/*条件变量完整添加进等待队列之后,就会解除阻塞,这句话会执行unlock,
出队列的时候这句话就会执行加锁,然后让条件变量出等待队列,出去之后19句就会释放锁。
一旦此线程被唤醒,这行代码就会使线程解除阻塞,然后执行printf,如果是end就会直接跳出循环,线程退出。
*/
19 pthread_mutex_unlock(&mutex);//unlock
20
21 if(strncmp(s,"end",3)==0)
22 {
23 break;
24 }
25
26 printf("fun1 read:%s\n",s);
27 }
28 }
29 void* fun2(void* arg)
30 {
31 char* s=(char*)arg;
32
33 while(1)
34 {
35 pthread_mutex_lock(&mutex);//加入条件队列
36 pthread_cond_wait(&cond,&mutex);//unlock 等待 被唤醒之后出等待队列 lock
37 pthread_mutex_unlock(&mutex);//unlock
38
39 if(strncmp(s,"end",3)==0)
40 {
41 break;
42 }
43
44 printf("fun2 read:%s\n",s);
45 }
46 }
47 int main()
48 {
49 //设置buff为了保存输入流的数据
50 char buff[128]={0};
51
52 //初始化锁
53 pthread_mutex_init(&mutex,NULL);
54 //初始化条件变量
55 pthread_cond_init(&cond,NULL);
56
57 //创建两个线程
58 pthread_t id1;
59 pthread_t id2;
60 pthread_create(&id1,NULL,fun1,(void*)buff);
61 pthread_create(&id2,NULL,fun2,(void*)buff);
62
63 while(1)
64 {
65 fgets(buff,128,stdin);
66
67 if(strncmp(buff,"end",3)==0)
68 {
69 pthread_mutex_lock(&mutex);//在进行唤醒条件变量的时候加锁的目的在于:不会在唤醒条件变量的同时其他操作回往等待队列中添加条件变量。
70 pthread_cond_broadcast(&cond);//这里唤醒所有的线程,从而保证所有的线程都可以退出
71 pthread_mutex_unlock(&mutex);
72 break;
73 }
74 else
75 {
76 pthread_mutex_lock(&mutex);
77 pthread_cond_signal(&cond);
78 pthread_mutex_unlock(&mutex);
79 }
80 }
81
82 //等待两个线程结束
83 pthread_join(id1,NULL);
84 pthread_join(id2,NULL);
85
86 //销毁
87 pthread_cond_destroy(&cond);
88 pthread_mutex_destroy(&mutex);
89
90 exit(0);
91 }
#include
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)
读写锁的使用和互斥锁差不多,只是“读锁”用于控制多个读功能的线程,而“写锁”用于控制写功能的线程
在pthread_create()函数的第二个参数是传入指向线程属性的指针,所以线程属性究竟如何创建并使用呢?
比如:我们主线程继续为用户提供服务的同时创建了第二个线程,这个新线程的作用是将用户编辑的数据文件进行备份存储,完成备份之后这个线程就终止,不需要再回到主线程。所以这类线程成为“脱离线程”那么脱离线程就可以通过设置线程属性实现。
1)pthread_attr_init()函数
用于创建线程属性对象
成功返回0,失败返回错误码
int pthread_attr_init(pthread_attr_t *attr)
2)pthread_attr_deatory()函数
用于对线程属性对象进行清理和回收
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <pthread.h>
5 #include <assert.h>
6 int thread_finished=0;//设置标志用来检测子线程是否已经结束
7 char message[]="hello world";
8 void* fun(void* arg)
9 {
10 char* message=(char*)arg;
11 printf("%s\n",message);
12 sleep(4);
13 printf("thread setting finished flag,and exiting now\n");
14 thread_finished=1;
15 pthread_exit(NULL);
16 }
17 int main()
18 {
19 int res;
20
21 pthread_attr_t attr;
22 res=pthread_attr_init(&attr);
23 if(res!=0)
24 {
25 perror("attribute creation failed");
26 exit(0);
27 }
28 //datachedstate这个属性允许我们对线程进行重新合并,第二个参数是标志,用来决定所需状态,PTHREAD_CREATE_DETACH ED这个标志使得不能调用pthread_join来获得另一个线程的退出状态
29 res=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
30 if(res!=0)
31 {
32 perror("setting detached attribute failed");
33 exit(0);
34 }
35
36 pthread_t id;
37 res=pthread_create(&id,&attr,fun,(void *)message);
38 if(res!=0)
39 {
40 perror("thread creation failed");
41 exit(0);
42 }
43 (void)pthread_attr_destroy(&attr);
44
45 while(!thread_finished)
46 {
47 printf("waiting for thread to finished\n");
48 sleep(1);
49 }
50 printf("thread finished\n");
51 exit(0);
52 }
一个线程可以要求另外一个线程终止,类似于发送一个信号。
int pthread_cancel(pthread_t thread);
利用上面的函数发送给指定的线程让其终止,那么指定的那个线程接收到这个信号之后首先需要用pthread_setcancelstate设置自己的取消状态,如果取消被接受就会利用pthread_setcanceltype设置取消类型。
int pthread_setcancelstate(int state,int *oldstate);
第一个参数:
如果取值为PTHREAD_CANCEL_ENABLE,这个值允许线程接收取消请求
如果取值为PTHREAD_CANCEL_DISABLE,他的作用是忽略取消请求
第二个参数:用于获取先前的取消状态
int pthread_setcanceltype(int type,int* oldtype);
第一个参数:
如果取值为PTHREAD_CANCEL_ASYNCHRONOUS,它将使得在接受取消请求之后立即采取行动
如果取值为PTHREAD_CANCEL_DEFERRED,它将使得在接收到取消请求之后,一直等待到线程执行了某些函数(参考Linux程序设计中文第四版十二章,这里不详细讲述)之后才采取行动。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <pthread.h>
5 void* fun(void* arg)
6 {
7 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//设置取消状态为接受取消
8 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);//设置取消类型为等待相关函数执行完毕结束
9 printf("fun running\n");
10 for(int i=0;i<10;++i)
11 {
12 printf("fun run %d\n",i);
13 sleep(1);
14 }
15
16 pthread_exit(NULL);
17 }
18
19 int main()
20 {
21 pthread_t id;
22 pthread_create(&id,NULL,fun,NULL);
23
24 sleep(3);
25
26 printf("canceling thread\n");
27 pthread_cancel(id);
28
29 printf("waiting for fun cancel\n");
30 pthread_join(id,NULL);
31 exit(0);
32 }
要保证线程安全需要做到:
1)对线程同步,保证同一时刻只有一个线程访问临界资源。
2)在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。
(1)多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?
不会把所有的线程都复制到子进程,只会把当前使用的线程复制到子进程。
(2)父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁?
是。
这样就会出现一种情况:其他线程使用锁这个资源,子进程将这个线程的锁也给复制了,那么父进程中那个线程会把这个锁释放了,但是子进程并不会把这个复制过来的锁给释放,那么子进程自己如果还要加锁,就会一值加锁不成功,导致进程阻塞。
在fork之前加锁,从而避免其他线程用锁,当其他线程释放锁之后,能够加锁,然后执行fork,之后父子进程同时释放锁。
演示上述情况的代码:
1 #include <stdio.h>
2 #include <sys/wait.h>
3 #include <unistd.h>
4 #include <pthread.h>
5 #include <sys/types.h>
6 #include <stdlib.h>
7 #include <assert.h>
8 pthread_mutex_t mutex;
9 void lock()
10 {
11 pthread_mutex_lock(&mutex);
12 }
13 void child_unlock()
14 {
15 pthread_mutex_unlock(&mutex);
16 }
17 void parent_unlock()
18 {
19 pthread_mutex_unlock(&mutex);
20 }
21 void* fun(void* arg)
22 {
23 printf("fun lock\n");
24 pthread_mutex_lock(&mutex);
25 sleep(5);
26 printf("fun unlock\n");
27 pthread_mutex_unlock(&mutex);
28
29 }
30 int main()
31 {
32 pthread_mutex_init(&mutex,NULL);
33
34 pthread_t id;
35 pthread_create(&id,NULL,fun,NULL);
36
37 sleep(1);
38
39 //下面的函数会在fork之前调用lock,在fork之后分别调用父子unlock函数
40 pthread_atfork(lock,parent_unlock,child_unlock);
41 pid_t pid=fork();
42 assert(pid!=-1);
43 if(pid==0)//子进程
44 {
45 printf("child lock\n");
46 pthread_mutex_lock(&mutex);
47 sleep(1);
48 printf("child unlock\n");
49 pthread_mutex_unlock(&mutex);
50
51 exit(0);
52 }
53
54 wait(NULL);
55 printf("main over\n");
56 pthread_mutex_destroy(&mutex);
57 pthread_join(id,NULL);
58 exit(0);
59 }