You’ve defined the requirements, but not explicitly the given condition. Without a given condition, you are probably looking at a brute force approach that considers all possible givens. It wouldn’t be pretty.
However, you mentioned you want to display the results on single-service.php. This indicates a single service post — a given! You can use get_posts() for this. In fact, get_posts() is mainly a wrapper function for instantiating a WP_Query object. I would just as soon deal directly with the class. If you like, the same arguments will work for get_posts(). But with get_posts(), you lose the ability to use class methods and properties to manage the output.
Before we get into query arguments, we need to know what taxonomy terms are assigned to the current post. We are looking at a service post, but it can work the other way, from worker to service. Use wp_get_object_terms(). You can get terms for both taxonomies at once, or get them individually to keep them organized. The taxonomy of each term is an object property, so you can go either way.
It seems likely there can be multiple terms of each taxonomy assigned to either service or worker posts. This is fine, but it impacts how we construct the tax query we need to get relevant posts.
Back to getting terms. We need the ID of the current post. On single templates or within a Loop, get_the_ID() usually works. The array of taxonomies required for the second parameter is straight forward. The $args parameter allows for many different options that can be used to tailor the results. Unless you know differently, accepting all the defaults should be fine, so don’t provide a third parameter. You will get in return an array of WP_term objects.
Now to build the WP_Query arguments. We know 'post_type'=>'worker',
will be one. For the most part we always use object names or slugs if not using IDs as parameters. Names are alphanumeric, all lowercase, no accents or non-latin chars. The only symbol allowed is a hyphen. Always avoid using mixed case labels or titles. Be careful with plural slugs. If you do that, do so consistently. I always use singular. Plural labels can be used, but my slugs are always singular.
Aside from taxonomy terms, probably all the other arguments can fall back to the defaults. We want to create a tax_query argument array. It’s very flexible, but structured. While it’s usually not too complicated, care must be taken. One small lapse in the structure and the entire argument will be ignored.
We want to assign an array of arrays to wp_query, which itself is also in an array of arguments. I believe you want any worker posts that have any one of the terms assigned to the current post. Other constraints can be accommodated, but my suggestions are based on this assumption. The overall relation between the inner arrays will be ‘OR’. This must be specified because the default is AND.
Each inner array relates to a single taxonomy. More than one array can have the same taxonomy if there are varying conditions within the taxonomy. The field you want to use would generally be term_id or slug. Both are available in WP_Term objects, so it’s your choice. You can see now why keeping term taxonomies separate might be an advantage.
The operator you want is ‘IN’, the default. I’d specify it anyway. All that’s left is building an array of IDs or slugs for the ‘terms’ argument. You could loop through all the terms returned by wp_get_object_terms() and push each applicable ID or slug into an array. I think we may have been able to merely get certain term properties instead of term objects, which would have made this step easier. I’ll leave it to you to figure out ways to refine this.
If this query does not return the results you expect, I’ve found the easiest way to debug it is to get the WP_Query::request property, which is the SQL query the object built. By examining how it’s constructed with what values will usually point to what the problem is. This should get you well on your way. Enjoy!