C/C++ 从创建线程到多线程的使用(加锁,解锁)

1、首先介绍什么是线程,为什么提出线程,优点在哪?

有人会把线程和进程搞混,线程虽然不是进程,但可以理解为轻量级的进程,进程中可以包含多个线程,且这些线程可以执行不同的操作。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效。

2、创建线程

pthread_t  pid;
pthread_create(&pid, NULL, (void *)&thread_function, (void *) &some_argument);

线程创建函数包含四个变量,分别为:

第一个参数:一个线程变量名,被创建线程的标识
第二个参数:线程的属性指针,一般NULL即可
第三个参数:被创建线程的程序代码
第四个参数:程序代码的参数 ,一般是需要传的参数如:char *some_argument;1个参数传该参数的地址即可,多个参数用结构体地址来传递;

3、结束线程

 void *retval;
 pthread_join(pthread_t pid, &retval;); 
 pthread_t pid //pid是要等待结束的线程的标识
 &retval; //指针thread_return指向的位置存放的是终止线程的返回状态。
 根据需要书写,也可以这样:
 pthread_join(pid, NULL);

pthread_join以阻塞的方式等待pid线程结束,并回收线程资源。多线程中,新线程和老线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度,如果我们需要等待指定线程结束,需要使用pthread_join函数。

4、案例分析:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void print_message_function (void *ptr);

int main()
{
    int tmp1, tmp2;
    pthread_t thread1, thread2;
    char *message1 = "thread1";
    char *message2 = "thread2";

    int ret_thrd1, ret_thrd2;

    ret_thrd1 = pthread_create(&thread1, NULL, (void *)&print_message_function, (void *) message1);
    ret_thrd2 = pthread_create(&thread2, NULL, (void *)&print_message_function, (void *) message2);

    // 线程创建成功,返回0,失败返回失败号
    if (ret_thrd1 != 0) {
        printf("线程1创建失败\n");
    } else {
        printf("线程1创建成功\n");
    }

    if (ret_thrd2 != 0) {
        printf("线程2创建失败\n");
    } else {
        printf("线程2创建成功\n");
    }

    //同样,pthread_join的返回值成功为0
    tmp1 = pthread_join(thread1, NULL);
    printf("thread1 return value(tmp) is %d\n", tmp1);
    if (tmp1 != 0) {
        printf("cannot join with thread1\n");
    }
    printf("thread1 end\n");

    tmp2 = pthread_join(thread1, NULL);
    printf("thread2 return value(tmp) is %d\n", tmp1);
    if (tmp2 != 0) {
        printf("cannot join with thread2\n");
    }
    printf("thread2 end\n");

}

void print_message_function( void *ptr ) {
    int i = 0;
    for (i; i<5; i++) {
        printf("%s:%d\n", (char *)ptr, i);
    }
}

这是一个简单的两个线程的运行程序,保存成一个.c文件,编译的时候链接库-lpread:

gcc nihao.c -o nihao -lpthread

之后运行三次出现以下的界面:
第一次:

thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
线程1创建成功
线程2创建成功
thread1 return value(tmp) is 0
thread1 end
thread2 return value(tmp) is 0
cannot join with thread2
thread2 end

第二次:
线程1创建成功

thread2:0
thread2:1
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
线程2创建成功
thread1 return value(tmp) is 0
thread1 end
thread2 return value(tmp) is 0
cannot join with thread2
thread2 end
thread2:thread2:2
thread2:3
thread2:4

第三次:

thread1:0
thread1:1
thread1:2
线程1创建成功
线程2创建成功
thread1:3
thread1:4
thread2:0
thread2:1
thread2:2
thread2:3
thread2:4
thread1 return value(tmp) is 0
thread1 end
thread2 return value(tmp) is 0
cannot join with thread2
thread2 end

运行结果分析:1、哪一个线程谁先执行,谁后执行用户是不知道的,这一块取决与操作系统对线程的调度。2、另外,我们看到,在thread2的join结果出现了错误,打印出cannot join with thread2这个是个小错误,因为,我pthread_join传进去的th是thread1,把第二个改为thread2就不会报错了,也验证了pthread_join(thread1, &retval)确实等待了thread1的结束。

在实际应用中,由于多个线程往往会访问共享的资源(典型的是访问同一个全局变量),因此多个线程间存在着竞争的关系,这就需要对多个线程进行同步,对其访问的数据予以保护。

5、多线程运行机制:

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

int sharedi = 0;
void increse_num(void);

int main(){
    int ret;
    pthread_t thrd1, thrd2, thrd3;

    ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
    ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
    ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);

    pthread_join(thrd1, NULL);
    pthread_join(thrd2, NULL);
    pthread_join(thrd3, NULL);

    printf("sharedi = %d\n", sharedi);

    return 0;

}

void increse_num(void) {
    long i,tmp;
    for(i=0; i<=100000; i++) {
        tmp = sharedi;
        tmp = tmp + 1;
        sharedi = tmp;
    }
}

我们no_mutex每次的运行结果都不一致,而且,运行结果也不符合我们的预期,出现了错误的结果。 原因就是三个线程竞争访问全局变量sharedi,并且都没有进行相应的同步。

举个例子,当线程thrd1访问到sharedi的时候,sharedi的值是1000,然后线程thrd1将sharedi的值累加到了1001,可是线程thrd2取到sharedi的时候,sharedi的值是1000,这时候线程thrd2对sharedi的值进行加1操作,使其变成了1001,可是这个时候,sharedi的值已经被线程thrd1加到1001了,然而,thrd2并不知道,所以又将sharedi的值赋为了1001,从而导致了结果的错误。

这样,我们就需要一个线程互斥的机制,来保护sharedi这个变量,让同一时刻,只有一个线程能够访问到这个变量,从而使它的值能够保证正确的变化。

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

int sharedi = 0;
void increse_num(void);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int main(){
    int ret;
    pthread_t thrd1, thrd2, thrd3;

    ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
    ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
    ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);

    pthread_join(thrd1, NULL);
    pthread_join(thrd2, NULL);
    pthread_join(thrd3, NULL);

    printf("sharedi = %d\n", sharedi);

    return 0;

}
void increse_num(void) {
    long i,tmp;
    for(i=0; i<=100000; i++) {
    /*加锁*/
        if (pthread_mutex_lock(&mutex) != 0) {
           perror("pthread_mutex_lock");
           exit(EXIT_FAILURE);
        }
        tmp = sharedi;
        tmp = tmp + 1;
        sharedi = tmp;
    /*解锁锁*/
        if (pthread_mutex_unlock(&mutex) != 0) {
            perror("pthread_mutex_unlock");
            exit(EXIT_FAILURE);
        }
    }
}

这一次,我们的结果是正确的,锁有效得保护了我们的数据安全。然而:

1、锁保护的并不是我们的共享变量(或者说是共享内存),对于共享的内存而言,用户是无法直接对其保护的,因为那是物理内存,无法阻止其他程序的代码访问。事实上,锁之所以对关键区域进行了保护,在本例中,是因为所有线程都遵循了一个规则,那就是在进入关键区域钱加同一把锁,在退出关键区域钱释放同一把锁

2、我们从上述运行结果中可以看到,加锁是会带来额外的开销的,加锁的代码其运行速度,明显比不加锁的要慢一些,所以,在使用锁的时候,要合理,在不需要对关键区域进行保护的场景下,我们便不要画蛇添足,为其加锁了

6、多线程对同一文件的读写:

#include<stdio.h>
#include<pthread.h>
 
#include<sys/types.h>
#include<sys/syscall.h>
#include<unistd.h>
 
#include<stdlib.h>
#include<assert.h>
 
const int buf_size=1024;
const int buf_size2=1024*2;
 
/*获取线程的id必须使用syscall,不能直接使用pthread_t
*pthread_t 的结构体实际是:
*typedef struct {
*   void * p;                   // Pointer to actual object 
*   unsigned int x;            // Extra information - reuse count etc 
*} ptw32_handle_t;
*/
pid_t gettid()
{
    return syscall(__NR_gettid);
}

/*文件读取线程,实现对一个或多个文件的读写*/
void * func(void * args){
    FILE* fp=(FILE*)args;
    int count=0;
    int read_count=0;
    char * buf;
    int size;
    pid_t tid=gettid();
    int sleep_time;
 
    if(tid%2==0){
        size=buf_size;
        sleep_time=50;
    }
    else{
        size=buf_size2;
        sleep_time=100;
    }
 
    buf=(char *)malloc(size);
    printf("the tid is %d, malloc size is %d\n", tid, size);
    while(!feof(fp)){
        count+=fread(buf, 1, size, fp);
        read_count++;
        usleep(sleep_time);
    }
    printf("thread [%d] read count is %d, read size is %d\n", tid, read_count, count);
    pthread_exit("thread exit");
}
 
void main()
{
    FILE* fd=fopen("hello.h264","rb");
    pthread_t ntid[4];
    int err;
    int i=0;
	
	/*多个线程同时对一个文件执行读操作,最后每个线程*/
    for(i=0; i<4; i++){
        err=pthread_create(&ntid[i], NULL, func, fd);
        if(err!=0){
            printf("can't create a new thread: %d\n", strerror(err));
        }
    }
    for(i=0; i<4; i++)
        pthread_join(ntid[i], NULL);
}

或者参考一下这个代码的实现,线程传递的是结构体:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//#define USE_CLIB

#define TEST_FILE	"./tmp.txt"

#define LOOPS		(1000000)


#ifdef USE_CLIB
struct thr_data {
	FILE *fp;
	const char *data;
};

static void * write_data(void *data)
{
	struct thr_data *d;
	size_t len;
	int i;

	d = data;
	len = strlen(d->data);
	for (i = 0; i < LOOPS; ++i) {
		fwrite(d->data, len, 1, d->fp);
	}

	return NULL;
}

#else
struct thr_data {
	int fd;
	const char *data;
};

static void *write_data(void *data)
{
	struct thr_data *d;
	int i;
	size_t len;

	d = data;
	len = strlen(d->data);
	for (i = 0; i < LOOPS; ++i) {
		write(d->fd, d->data, len); 
	}

	return NULL;
}
#endif



int main(void)
{
	pthread_t t1, t2, t3;
	struct thr_data d1, d2, d3;

#ifdef USE_CLIB
	FILE *fp = fopen(TEST_FILE, "w");
	d1.fp = d2.fp = d3.fp = fp;
#else
	//int fd = open(TEST_FILE, O_WRONLY|O_TRUNC);
	int fd = open(TEST_FILE, O_WRONLY|O_TRUNC|O_APPEND);
	d1.fd = d2.fd = d3.fd = fd;
#endif

	d1.data = "aaaaaa\n";
	d2.data = "bbbbbb\n";
	d3.data = "cccccc\n";

	pthread_create(&t1, NULL, write_data, &d1);
	pthread_create(&t2, NULL, write_data, &d2);
	pthread_create(&t3, NULL, write_data, &d3);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);

#ifdef USE_CLIB
	fclose(fp);
#else
	close(fd);
#endif

	return 0;
}

两个线程调用同一个函数:
两个线程中的函数的局部变量由于是保存在不同的线程中,因此不需要进行互斥处理(除非有非栈内存在捣乱,这种情况必须要有互斥锁)。

两个不同进程中的两个线程调用同一个处理函数:
两个线程中的函数的局部变量由于是保存在不同的线程中,因此不需要进行互斥处理。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值