【C语言】多线程编程

多线程的优点:
①与进程相比,成本低。创建和启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式;而多个线程运行于一个进程中,彼此间使用相同的地址空间,共享进程的数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
②通信机制方便,适合大量数据传送。因为无需跨越进程边界,适合各线程间大量数据的传送,多线程还可以共享同一进程里的共享内存和变量。
③提高应用程序响应。当一个操作耗时很长时,单线程的话整个系统都会等待这个操作,此时程序不会响应其他操作,而使用多线程技术,可以采取相应的调度策略来避免CPU空闲操作。
④使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
⑤ 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
多线程的缺点:
①虽然多线程逻辑控制比较简单,但是却需要复杂的线程同步和加锁控制等机制。
②不如进程健壮安全。一个进程崩溃不影响其他进程,子进程崩溃也不会影响到主进程,因为每个进程有独立的系统资源。多线程比较脆弱,一个线程崩溃很可能影响到整个程序,因为多个线程是在一个进程里一起合作干活的。
③要考虑竞争和同步的问题。线程创建了就会同等地竞争CPU、内存等资源,不加以控制可能会导致很多错误操作。

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。在linux和windows下C语言编程的不同之处主要在于一些库和API的不同,如果没有涉及系统独有的库函数或者编程方法(即只用符合ANSI C标准的代码和方法),就没有区别。

一个简单的linux下c语言多线程编程例子:创建两个线程操作一个数

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int number=0;
void thread1(void)
{
    
    
    while(1)
   {
    
    
		number++;
		printf("%d:thread1\n",number);
		sleep(1);
	}
}

void thread2(void)
{
    
    
	while(1)
	{
    
    
	    number++;
	    printf("%d:thread2\n",number);
	    sleep(1);
	}
}

int main()
{
    
    
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,(void*)(&thread1),NULL);			//创建两个线程
    pthread_create(&tid2,NULL,(void*)(&thread2),NULL);			
    pthread_join(tid1,NULL);							//等待两个线程结束
    pthread_join(tid2,NULL);
    while(1)
    {
    
    
    printf("%d:main\n",++number);
    usleep(1000000);
    }
    return 0;
}


结果:
注:编译的使用要用到libpthread.a库,所以编译时要加上-lpthread。
可以看出两个创建的线程都会操作一个数,多运行几次会发现线程的运行顺序是不同的,这反映了不加以任何处理的话线程是会无序地竞争资源的;另外可以发现主进程的打印信息是永远不会被执行的,这是因为前面调用了pthread_join,具体函数的功能看下方。
在这里插入图片描述

常用的线程操作:
1.创建线程:pthread_create
原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
2.等待一个线程的结束:pthread_join
原型:int pthread_join(pthread_t thread, void **retval);
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
3.结束线程:pthread_exit
原型:void pthread_exit(void *retval);
参数是函数的返回代码,只要pthread_join中的第二个参数retval不是NULL,这个值将被传递给retval。
4.获取自身线程号:pthread_self
原型:pthread_t pthread_self(void);
5.非阻塞回收线程资源:pthread_detach
创建一个线程默认的状态是joinable,如果一个线程结束运行但没有被join,会有一部分资源没有被回收(退出状态码),所以创建线程者应该pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid)。但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

猜你喜欢

转载自blog.csdn.net/yechongbinbin/article/details/114279709
今日推荐