C语言中平行的世界——线程

假如你的程序一边从网络读取数据,一边向网络发送数据。如何在代码中同时执行几个不同的任务?根据前面的知识,我们知道可以通过创建多几个子进程来做这些事。但是创建进程很花时间,而且不同子进程之间共享数据很不方便。这时我们需要线程来帮我们解决这个问题。

如何创建线程呢?

我们有很多线程库可以用,其中最流行的就是POSIX线程库,也叫pthread 。可以在类Unix系统上使用。
假设我想在独立的线程中运行以下这两个函数:

void one(void *a){
        int i = 0;
        for(i = 0;i<5;i++){
                sleep(1);
                puts("one one one");
        }       
        return NULL;
}

void two(void *b){
        int i = 0;
        for(i = 0;i < 5; i++){
                sleep(1);
                puts("two two two");
        }       
        return NULL:
}               

注意:线程函数的返回类型必须是void,void指针可以指向存储器中任何类型的数据。*

用pthread_create创建线程

我们将创建两个线程,每个线程都需要把信息保存在一个叫pthread_t的结构体中,然后就可以用pthread_create()创建并运行线程。

 		pthread_t t0;//保存了线程的所有信息
        pthread_t t1;//保存了线程的所有信息
        if(pthread_create(&t0,NULL,one,NULL) == -1){//创建线程
                error1("无法创建线程t0");
        }
        if(pthread_create(&t1,NULL,two,NULL) == -1){//创建线程
                error1("无法创建线程t1");
        }

代码将以独立线程运行这两个函数。如果程序运行完这段代码就结束了,线程也会随之灭亡,因此程序必须等待线程结束。加上以下的代码,就可以让程序等线程执行完成,再结束。否则线程还没来得及执行,程序就结束了。

		void* result; //函数返回 的void指针会保存在这里
        if(pthread_join(t0,&result) == -1){
                error1("无法收回线程t0");
        }
        if(pthread_join(t1,&result) == -1){
                error1("无法收回线程t1");
        }

pthread_join()函数会等待线程结束并接收线程函数的返回值,并把它保存在一个void指针变量中。一旦两个线程都结束了,程序就可以顺利退出。

编译时要使用 pthread库,所以必须在编译程序时链接它:

~/Desktop/MyC$ gcc testThread.c -lpthread -o thread

运行程序:

 ~/Desktop/MyC$ ./thread
one one one
two two two
two two two
one one one
two two two
one one one
two two two
one one one
two two two
one one one

线程不安全

线程最大的优点就是多个不同任务可以同时运行,并访问相同数据,而不同任务可以访问相同数据恰恰也是线程的缺点。怎么办呢?我们可以通过互斥锁来防止不同线程同时访问共享资源,即不同线程不能同时读取相同数据,并把它写回。
为了保护某段代码的安全,我们需要创建互斥锁:

pthread_mutex_t a_lock = PTHREAD_MUTEX_INITIALIZER;

互斥锁必须对所有可能发生冲突的线程可见,因此它必须是一个全局变量 。PTHREAD_MUTEX_INITIALIZER实际上是一个宏,当编译器看到它时,就会插入创建互斥锁的代码。

pthread_mutex_lock()函数只允许一个线程通过,其他线程运行到这行代码时必须等待。当线程到达代码的尾部就要调用pthread_mutex_unlock()释放互斥锁。

pthread_mutex_lock(&a_lock);//含有共享数据的代码从这里开始
	.... //含有共享数据的代码
pthread_mutex_unlock(&a_lock);//释放互斥锁,其他线程就可以入这段含有共享数据的代码。

我们给出一个完整示例testThread.c:

#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void error1(char *msg);
void* sell_tickets(void *a);

pthread_mutex_t a_lock = PTHREAD_MUTEX_INITIALIZER;

int tickets = 500;

int main(){
        pthread_t threads[5];
        long t;
        for(t = 0;t < 5;t++){

        pthread_create(&threads[t],NULL,sell_tickets,(void*)t);

        }
        void* result;
        for(t = 0;t<5;t++){

        pthread_join(threads[t],&result);

        }
        return 0;
}
void error1(char *msg){
        fprintf(stderr,"%s:%s\n",msg,strerror(errno));
        exit(1);
}

void* sell_tickets(void *a){
        long thread_no = (long)a;
        int i;
        for(i = 0;i<100;i++){

          pthread_mutex_lock(&a_lock);//含有共享数据的代码从这里开始
          tickets = tickets - 1;
          pthread_mutex_unlock(&a_lock);//释放互斥锁,其他线程就可以入这段含有共享数据的代码。

        }
        printf("Window No.%ld  TICKETS:%d\n",thread_no,tickets);
        return NULL;
}

编译运行:

~/Desktop/MyC$ gcc testThread.c -lpthread -o thread
~/Desktop/MyC$ ./thread
Window No.0  TICKETS:398
Window No.1  TICKETS:300
Window No.2  TICKETS:200
Window No.3  TICKETS:100
Window No.4  TICKETS:0

线程函数可以接收一个void指针作为参数,并返回一个void指针值,通常你希望把某个整型值传给线程,并让它返回某个整型值,一种方法是用long,因为它的大小和void指针相同 ,可以把它保存在void指针变量中,如下面程序thread1.c:

#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void* do_stuff(void* param);
int  main(){
        pthread_t threads[3];
        long t;
        for(t = 0;t<3;t++){
                pthread_create(&threads[t],NULL,do_stuff,(void*)t);//将long型变量转化为void指针类型
        }
        void* result;
        for( t = 0;t<3;t++){
                pthread_join(threads[t],&result);
                printf("Thread %ld returned %ld\n",t,(long)result);//result使用前先把它转化为long
        }
        return 0;
}
void* do_stuff(void* param){
        long thread_no = (long)param;
        printf("Thread number %ld\n",thread_no);
        return (void*)(thread_no+1);
}

编译运行:

~/Desktop/MyC$ gcc thread1.c -lpthread -o thread1
~/Desktop/MyC$ ./thread1
Thread number 0
Thread number 1
Thread number 2
Thread 0 returned 1
Thread 1 returned 2
Thread 2 returned 3

操作系统怎样运行多个线程呢?其实操作系统会在多个线程之间快速切换,看起来就好像在同时做多件事。

线程不一定能让程序更快,虽然线程可以帮助你利用机器上更多的处理器和核,但你还是需要控制代码中互斥锁的数量,用得太多互斥锁,代码可能会像单线程程序一样慢。

怎样才能让多线程程序运行得更快?首先要减少线程需要访问的共享数据的数量。如果线程无需访问很多共享数据,那么多个线程等一个线程的情况就会很少出现,速度就会大大提高。

线程通常比进程更快,因为创建进程要比创建线程花更多时间。

死锁是如何发生的?假设你有两个线程,它们都想得到互斥锁A和B,如果第一个线程得到了A,第二个线程得到了B,这两个线程就会陷入死锁。因为第一个线程无法得到B,第二个线程无法得到A,它们都停滞不前。

谢谢阅读!

猜你喜欢

转载自blog.csdn.net/weixin_40763897/article/details/87862600
今日推荐